diff --git a/README.md b/README.md index bf2768d..2fa851f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [Number](#number) - [Select](#select) - [MultiSelect](#multiselect) + - [Confirm](#confirm) - [Autocomplete](#autocomplete) - [Themes](#themes) - [MinimalTheme](#minimaltheme) @@ -113,27 +114,48 @@ To implement your original prompt, please see the [Build your own Prompt](#build ### Input -![Input Screenshot](./assets/prompt_input.png) +![Input Demo](./assets/prompt_input.gif) A prompt for general text input. ```rust -let name = p.prompt(Input::new("What is your accout name?").with_hint("e.g. wadackel"))?; +let name = p.prompt( + Input::new("What is your accout name?") + .with_placeholder("username") + .with_hint("Only alphanumeric characters are allowed.") + .with_validator(|value: &String| { + if value.chars().all(|c| c.is_alphanumeric()) { + Ok(()) + } else { + Err("Invalid format".into()) + } + }), +)?; ``` ### Password -![Password Screenshot](./assets/prompt_password.png) +![Password Demo](./assets/prompt_password.gif) A text input prompt where the input is not displayed. ```rust -let secret = p.prompt(Password::new("What is your password?").with_required(false))?; +let secret = p.prompt( + Password::new("Set a password for your account") + .with_hint("Please enter more than 6 alphanumeric characters.") + .with_validator(|value: &String| { + if value.len() < 6 { + Err("Password must be at least 6 characters long".into()) + } else { + Ok(()) + } + }), +)?; ``` ### Number -![Number Screenshot](./assets/prompt_number.png) +![Number Demo](./assets/prompt_number.gif) A prompt for inputting only integer values. @@ -143,7 +165,7 @@ let age = p.prompt(Number::new("How old are you?").with_min(0).with_max(120))?; ### Select -![Select Screenshot](./assets/prompt_select.png) +![Select Demo](./assets/prompt_select.gif) A prompt for selecting a single element from a list of options. @@ -155,27 +177,43 @@ let color = p.prompt( SelectOption::new("Red", "#ff0000"), SelectOption::new("Green", "#00ff00").with_hint("recommended"), SelectOption::new("Blue", "#0000ff"), - ]) - .with_page_size(5) + ], + ) + .as_mut(), )?; ``` ### MultiSelect -![MultiSelect Screenshot](./assets/prompt_multi_select.png) +![MultiSelect Demo](./assets/prompt_multi_select.gif) A prompt for selecting multiple elements from a list of options. ```rust let color = p.prompt( MultiSelect::new( - "What is your favorite color?", + "What are your favorite colors?", vec![ MultiSelectOption::new("Red", "#ff0000"), MultiSelectOption::new("Green", "#00ff00").with_hint("recommended"), MultiSelectOption::new("Blue", "#0000ff"), - ]) - .with_page_size(5) + ], + ) + .as_mut(), +)?; +``` + +### Confirm + +![Confirm Demo](./assets/prompt_confirm.gif) + +A prompt for inputting a Yes/No choice. + +```rust +let like = p.prompt( + Confirm::new("Do you like dogs?") + .with_hint("This is just a sample prompt :)") + .with_default(true), )?; ``` @@ -196,12 +234,30 @@ MinimalTheme is similar to [Inquirer](https://github.com/SBoudrias/Inquirer.js). ![MinimalTheme Screenshot](./assets/theme_minimal.png) +```rust +use promptuity::themes::MinimalTheme; + +fn main() { + let mut theme = MinimalTheme::default(); + // ... +} +``` + ### FancyTheme FancyTheme is similar to [clack](https://github.com/natemoo-re/clack). It provides a rich UI. ![FancyTheme Screenshot](./assets/theme_fancy.png) +```rust +use promptuity::themes::FancyTheme; + +fn main() { + let mut theme = FancyTheme::default(); + // ... +} +``` + ## Customize This section provides guidance on how to construct original prompts and Themes. diff --git a/assets/prompt_confirm.gif b/assets/prompt_confirm.gif new file mode 100644 index 0000000..6fdfa11 Binary files /dev/null and b/assets/prompt_confirm.gif differ diff --git a/assets/prompt_confirm.tape b/assets/prompt_confirm.tape new file mode 100644 index 0000000..43020a9 --- /dev/null +++ b/assets/prompt_confirm.tape @@ -0,0 +1,25 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_confirm.gif + +Type@40ms "cargo run --example prompt_confirm" Enter +Sleep 3s + +Right Sleep 1.5s +Left Sleep 1.5s +Enter + +Sleep 5s diff --git a/assets/prompt_input.gif b/assets/prompt_input.gif new file mode 100644 index 0000000..3d2fa88 Binary files /dev/null and b/assets/prompt_input.gif differ diff --git a/assets/prompt_input.png b/assets/prompt_input.png deleted file mode 100644 index 88d1ff6..0000000 Binary files a/assets/prompt_input.png and /dev/null differ diff --git a/assets/prompt_input.tape b/assets/prompt_input.tape new file mode 100644 index 0000000..c3f7845 --- /dev/null +++ b/assets/prompt_input.tape @@ -0,0 +1,25 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_input.gif + +Type@40ms "cargo run --example prompt_input" Enter +Sleep 3s + +Type "foo bar" Sleep 1s Enter +Sleep 3s Ctrl+W Sleep 100ms Ctrl+W Sleep 500ms Enter +Sleep 3s Type "wadackel" Sleep 2s Enter + +Sleep 5s diff --git a/assets/prompt_multi_select.gif b/assets/prompt_multi_select.gif new file mode 100644 index 0000000..e597aa6 Binary files /dev/null and b/assets/prompt_multi_select.gif differ diff --git a/assets/prompt_multi_select.png b/assets/prompt_multi_select.png deleted file mode 100644 index aee5252..0000000 Binary files a/assets/prompt_multi_select.png and /dev/null differ diff --git a/assets/prompt_multi_select.tape b/assets/prompt_multi_select.tape new file mode 100644 index 0000000..142e47b --- /dev/null +++ b/assets/prompt_multi_select.tape @@ -0,0 +1,25 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_multi_select.gif + +Type@40ms "cargo run --example prompt_multi_select" Enter +Sleep 3s + +Down Type "j" Sleep 1s +Space Type "k" Sleep 1s +Space Sleep 1s Enter + +Sleep 5s diff --git a/assets/prompt_number.gif b/assets/prompt_number.gif new file mode 100644 index 0000000..00e87df Binary files /dev/null and b/assets/prompt_number.gif differ diff --git a/assets/prompt_number.png b/assets/prompt_number.png deleted file mode 100644 index 02c920c..0000000 Binary files a/assets/prompt_number.png and /dev/null differ diff --git a/assets/prompt_number.tape b/assets/prompt_number.tape new file mode 100644 index 0000000..9139e67 --- /dev/null +++ b/assets/prompt_number.tape @@ -0,0 +1,26 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_number.gif + +Type@40ms "cargo run --example prompt_number" Enter +Sleep 3s + +Type "123" Sleep 1s Enter +Sleep 3s Ctrl+U Sleep 1s +Type "30" Sleep 1s +Up Sleep 1s Enter + +Sleep 5s diff --git a/assets/prompt_password.gif b/assets/prompt_password.gif new file mode 100644 index 0000000..b4d6521 Binary files /dev/null and b/assets/prompt_password.gif differ diff --git a/assets/prompt_password.png b/assets/prompt_password.png deleted file mode 100644 index 1a09a79..0000000 Binary files a/assets/prompt_password.png and /dev/null differ diff --git a/assets/prompt_password.tape b/assets/prompt_password.tape new file mode 100644 index 0000000..df21cfa --- /dev/null +++ b/assets/prompt_password.tape @@ -0,0 +1,25 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_password.gif + +Type@40ms "cargo run --example prompt_password" Enter +Sleep 3s + +Type "123" Sleep 1s Enter +Sleep 3s Ctrl+U Sleep 1s Enter +Sleep 3s Type "secret123" Sleep 2s Enter + +Sleep 5s diff --git a/assets/prompt_select.gif b/assets/prompt_select.gif new file mode 100644 index 0000000..25f5f23 Binary files /dev/null and b/assets/prompt_select.gif differ diff --git a/assets/prompt_select.png b/assets/prompt_select.png deleted file mode 100644 index 371aafa..0000000 Binary files a/assets/prompt_select.png and /dev/null differ diff --git a/assets/prompt_select.tape b/assets/prompt_select.tape new file mode 100644 index 0000000..676a629 --- /dev/null +++ b/assets/prompt_select.tape @@ -0,0 +1,24 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 500 +Output assets/prompt_select.gif + +Type@40ms "cargo run --example prompt_select" Enter +Sleep 3s + +Down Type "j" Sleep 1s +Up Sleep 1s Enter + +Sleep 5s diff --git a/assets/quick_start.gif b/assets/quick_start.gif index ee64060..6202829 100644 Binary files a/assets/quick_start.gif and b/assets/quick_start.gif differ diff --git a/assets/quick_start.tape b/assets/quick_start.tape new file mode 100644 index 0000000..6838b1d --- /dev/null +++ b/assets/quick_start.tape @@ -0,0 +1,26 @@ +Require cargo +Set Shell "zsh" +Set Theme "Dracula+" +Set WindowBar Colorful +Set WindowBarSize 60 +Set Margin 24 +Set MarginFill "#f0f4f7" +Set Padding 36 +Set BorderRadius 16 +Set CursorBlink false +Set FontSize 30 +Set TypingSpeed 120ms + +Set Width 1500 +Set Height 900 +Output assets/quick_start.gif + +Type@40ms "cargo run --example quick_start" Sleep 300ms Enter +Sleep 4s + +Type "empty test" Sleep 1s Backspace 10 Sleep 500ms Enter Sleep 3s +Type "wadackel" Sleep 2s Enter +Sleep 2s Right Sleep 2s Left Sleep 2s Enter +Sleep 2s Down 11 Sleep 2s Up 11 Sleep 2s Enter + +Sleep 5s diff --git a/examples/README.md b/examples/README.md index 4c05bec..b1c50d3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,7 +7,47 @@ This document is a list of implementation examples for Promptuity. This is a quick start implementation. It demonstrates the most basic usage of Promptuity. ```bash -$ cargo run --example quick_start.rs +$ cargo run --example quick_start +``` + +## Prompts + +This is a sample of the basic usage of built-in prompts. + +### Input + +```bash +$ cargo run --example prompt_input +``` + +### Password + +```bash +$ cargo run --example prompt_password +``` + +### Number + +```bash +$ cargo run --example prompt_number +``` + +### Select + +```bash +$ cargo run --example prompt_select +``` + +### MultiSelect + +```bash +$ cargo run --example prompt_multi_select +``` + +### Confirm + +```bash +$ cargo run --example prompt_confirm ``` ## Error Handling @@ -15,7 +55,7 @@ $ cargo run --example quick_start.rs This is an implementation example of handling prompt interruptions. ```bash -$ cargo run --example error_handling.rs +$ cargo run --example error_handling ``` ## Autocomplete @@ -27,7 +67,7 @@ This is a reference implementation of `Autocomplete`. It is useful for deepening - Displaying multiple items (**Body**) ```bash -$ cargo run --example autocomplete.rs +$ cargo run --example autocomplete ``` Of course, you can also copy and paste this reference implementation to adapt it for your project. @@ -37,7 +77,7 @@ Of course, you can also copy and paste this reference implementation to adapt it This is an example of extending built-in prompts and customizing key bindings and rendering. ```bash -$ cargo run --example extend_prompt.rs +$ cargo run --example extend_prompt ``` ## Custom Format @@ -45,7 +85,7 @@ $ cargo run --example extend_prompt.rs This is an example of customizing the format of built-in prompts. ```bash -$ cargo run --example custom_format.rs +$ cargo run --example custom_format ``` ## Custom Theme @@ -53,7 +93,7 @@ $ cargo run --example custom_format.rs This is a reference implementation example of an original Theme. ```bash -$ cargo run --example custom_theme.rs +$ cargo run --example custom_theme ``` ## Packages @@ -61,7 +101,7 @@ $ cargo run --example custom_theme.rs This is an implementation example that mimics version bumping in a Monorepo. It aids in understanding realistic use cases. ```bash -$ cargo run --example packages.rs +$ cargo run --example packages ``` ## Survey Fancy @@ -69,7 +109,7 @@ $ cargo run --example packages.rs This is an implementation example of a survey using the `FancyTheme`. ```bash -$ cargo run --example survey_fancy.rs +$ cargo run --example survey_fancy ``` ## Survey Minimal @@ -77,5 +117,5 @@ $ cargo run --example survey_fancy.rs This is an implementation example of a survey using the `MinimalTheme`. ```bash -$ cargo run --example survey_minimal.rs +$ cargo run --example survey_minimal ``` diff --git a/examples/prompt_confirm.rs b/examples/prompt_confirm.rs new file mode 100644 index 0000000..a9ac1f7 --- /dev/null +++ b/examples/prompt_confirm.rs @@ -0,0 +1,22 @@ +use promptuity::prompts::Confirm; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let like = p.prompt( + Confirm::new("Do you like dogs?") + .with_hint("This is just a sample prompt :)") + .with_default(true), + )?; + p.finish()?; + + println!("\nresult: {:?}", like); + + Ok(()) +} diff --git a/examples/prompt_input.rs b/examples/prompt_input.rs new file mode 100644 index 0000000..cfd8130 --- /dev/null +++ b/examples/prompt_input.rs @@ -0,0 +1,29 @@ +use promptuity::prompts::Input; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let name = p.prompt( + Input::new("What is your accout name?") + .with_placeholder("username") + .with_hint("Only alphanumeric characters are allowed.") + .with_validator(|value: &String| { + if value.chars().all(|c| c.is_alphanumeric()) { + Ok(()) + } else { + Err("Invalid format".into()) + } + }), + )?; + p.finish()?; + + println!("\nresult: {:?}", name); + + Ok(()) +} diff --git a/examples/prompt_multi_select.rs b/examples/prompt_multi_select.rs new file mode 100644 index 0000000..84fd4bc --- /dev/null +++ b/examples/prompt_multi_select.rs @@ -0,0 +1,28 @@ +use promptuity::prompts::{MultiSelect, MultiSelectOption}; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let color = p.prompt( + MultiSelect::new( + "What are your favorite colors?", + vec![ + MultiSelectOption::new("Red", "#ff0000"), + MultiSelectOption::new("Green", "#00ff00").with_hint("recommended"), + MultiSelectOption::new("Blue", "#0000ff"), + ], + ) + .as_mut(), + )?; + p.finish()?; + + println!("\nresult: {:?}", color); + + Ok(()) +} diff --git a/examples/prompt_number.rs b/examples/prompt_number.rs new file mode 100644 index 0000000..e3fbf9c --- /dev/null +++ b/examples/prompt_number.rs @@ -0,0 +1,18 @@ +use promptuity::prompts::Number; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let age = p.prompt(Number::new("How old are you?").with_min(0).with_max(120))?; + p.finish()?; + + println!("\nresult: {:?}", age); + + Ok(()) +} diff --git a/examples/prompt_password.rs b/examples/prompt_password.rs new file mode 100644 index 0000000..670cee0 --- /dev/null +++ b/examples/prompt_password.rs @@ -0,0 +1,28 @@ +use promptuity::prompts::Password; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let secret = p.prompt( + Password::new("Set a password for your account") + .with_hint("Please enter more than 6 alphanumeric characters.") + .with_validator(|value: &String| { + if value.len() < 6 { + Err("Password must be at least 6 characters long".into()) + } else { + Ok(()) + } + }), + )?; + p.finish()?; + + println!("\nresult: {:?}", secret); + + Ok(()) +} diff --git a/examples/prompt_select.rs b/examples/prompt_select.rs new file mode 100644 index 0000000..63e42d9 --- /dev/null +++ b/examples/prompt_select.rs @@ -0,0 +1,28 @@ +use promptuity::prompts::{Select, SelectOption}; +use promptuity::themes::MinimalTheme; +use promptuity::{Error, Promptuity, Term}; + +fn main() -> Result<(), Error> { + let mut term = Term::default(); + let mut theme = MinimalTheme::default(); + let mut p = Promptuity::new(&mut term, &mut theme); + + p.term().clear()?; + p.begin()?; + let color = p.prompt( + Select::new( + "What is your favorite color?", + vec![ + SelectOption::new("Red", "#ff0000"), + SelectOption::new("Green", "#00ff00").with_hint("recommended"), + SelectOption::new("Blue", "#0000ff"), + ], + ) + .as_mut(), + )?; + p.finish()?; + + println!("\nresult: {:?}", color); + + Ok(()) +} diff --git a/src/themes/fancy.rs b/src/themes/fancy.rs index 606122a..be0da77 100644 --- a/src/themes/fancy.rs +++ b/src/themes/fancy.rs @@ -62,14 +62,12 @@ impl FancyTheme { } fn fmt_placeholder(&self, placeholder: String) -> String { - let mut chars = placeholder.char_indices(); - let cursor = chars.nth(0).unwrap_or((0, ' ')); - let second = chars.nth(1).unwrap_or((0, ' ')); - let rest = &placeholder[second.0..]; + let input = InputCursor::new(placeholder, 0); + let (_, cursor, right) = input.split(); format!( "{}{}", - Styled::new(cursor.1).rev(), - Styled::new(rest).fg(Color::DarkGrey), + Styled::new(cursor).rev(), + Styled::new(right).fg(Color::DarkGrey), ) } @@ -91,7 +89,7 @@ impl FancyTheme { self.fmt_line(color, input) } PromptInput::Cursor(c) => { - let input = if c.is_empty() { + let input = if c.value().is_empty() { self.fmt_placeholder(placeholder.unwrap_or_default()) } else { self.fmt_cursor(c) diff --git a/src/themes/minimal.rs b/src/themes/minimal.rs index 14d142b..7e76d60 100644 --- a/src/themes/minimal.rs +++ b/src/themes/minimal.rs @@ -36,14 +36,12 @@ impl MinimalTheme { } fn fmt_placeholder(&self, placeholder: String) -> String { - let mut chars = placeholder.char_indices(); - let cursor = chars.nth(0).unwrap_or((0, ' ')); - let second = chars.nth(1).unwrap_or((0, ' ')); - let rest = &placeholder[second.0..]; + let input = InputCursor::new(placeholder, 0); + let (_, cursor, right) = input.split(); format!( "{}{}", - Styled::new(cursor.1).rev(), - Styled::new(rest).fg(Color::DarkGrey), + Styled::new(cursor).rev(), + Styled::new(right).fg(Color::DarkGrey), ) } @@ -62,7 +60,7 @@ impl MinimalTheme { self.fmt_input_layout(input) } PromptInput::Cursor(c) => { - let input = if c.is_empty() { + let input = if c.value().is_empty() { self.fmt_placeholder(placeholder.unwrap_or_default()) } else { self.fmt_cursor(c)