Skip to content

Commit

Permalink
feat: run actions impl with a little magic
Browse files Browse the repository at this point in the history
chore: update examples and test
  • Loading branch information
Vulpesx committed Apr 29, 2024
1 parent 4ca08e1 commit 901555e
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 36 deletions.
2 changes: 1 addition & 1 deletion examples/spinner-prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use demand::{Confirm, DemandOption, Input, MultiSelect, Select, Spinner, Theme};
fn main() {
let spinner = Spinner::new("im out here");
spinner
.run(|| {
.run(|_| {
Confirm::new("confirm")
.description("it says confirm")
.run()
Expand Down
25 changes: 9 additions & 16 deletions examples/spinner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{thread::sleep, time::Duration};

use demand::{Spinner, SpinnerAction, SpinnerStyle, Theme};
use demand::{Spinner, SpinnerStyle, Theme};

fn main() {
let custom_style = SpinnerStyle {
Expand All @@ -9,27 +9,20 @@ fn main() {
],
fps: Duration::from_millis(1000 / 10),
};
let dots = SpinnerStyle::dots();
let line = SpinnerStyle::line();

let charm = Theme::charm();
let catppuccin = Theme::catppuccin();
Spinner::new("Loading Data...")
.style(custom_style)
.style(&custom_style)
.run(|s| {
sleep(Duration::from_secs(2));
let mut toggle = false;
for name in ["Files", "Data", "Your Soul"] {
let _ = s.send(SpinnerAction::Title(format!("Loading {name}...")));
let _ = s.send(SpinnerAction::Style(if toggle {
SpinnerStyle::dots()
} else {
SpinnerStyle::line()
}));
let _ = s.send(SpinnerAction::Theme(
if toggle {
Theme::catppuccin()
} else {
Theme::charm()
}
.into(),
));
let _ = s.title(format!("Loading {name}..."));
let _ = s.style(if toggle { &dots } else { &line });
let _ = s.theme(if toggle { &catppuccin } else { &charm });
toggle = !toggle;
sleep(Duration::from_secs(2));
}
Expand Down
82 changes: 63 additions & 19 deletions src/spinner.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
io::{self, Write},
marker::PhantomData,
sync::mpsc::{self, Sender, TryRecvError},
thread::sleep,
time::Duration,
Expand All @@ -9,20 +10,60 @@ use console::Term;
use once_cell::sync::Lazy;
use termcolor::{Buffer, WriteColor};

use crate::Theme;
use crate::{theme, Theme};

/// tell a prompt to do something while running
/// currently its only useful for spinner
/// but that could change
pub enum SpinnerAction {
/// change the theme
Theme(Box<Theme>),
Theme(&'static Theme),
/// change the style
Style(SpinnerStyle),
Style(&'static SpinnerStyle),
/// change the title
Title(String),
}

// SAFETY: ensure that 'spinner lives longer than any use of style or theme by spinner
pub struct SpinnerActionRunner<'spinner> {
sender: Sender<SpinnerAction>,
r: PhantomData<&'spinner ()>, // need to use 'spinner to have it on the struct
}

impl<'spinner> SpinnerActionRunner<'spinner> {
fn new(sender: Sender<SpinnerAction>) -> Self {
Self {
sender,
r: PhantomData,
}
}

/// set the spinner theme
/// will not compile if ref to theme doesn't outlast spinner
pub fn theme(
&self,
theme: &'spinner Theme,
) -> Result<(), std::sync::mpsc::SendError<SpinnerAction>> {
let theme = unsafe { std::mem::transmute(theme) };
self.sender.send(SpinnerAction::Theme(theme))
}

/// set the spinner style
/// will not compile if ref to style doesn't outlast spinner
pub fn style(
&self,
style: &'spinner SpinnerStyle,
) -> Result<(), std::sync::mpsc::SendError<SpinnerAction>> {
let style = unsafe { std::mem::transmute(style) };
self.sender.send(SpinnerAction::Style(style))
}

/// set the spinner title
pub fn title(&self, title: String) -> Result<(), std::sync::mpsc::SendError<SpinnerAction>> {
self.sender.send(SpinnerAction::Title(title))
}
}

/// Show a spinner
///
/// # Example
Expand All @@ -33,65 +74,68 @@ pub enum SpinnerAction {
///
/// let spinner = Spinner::new("Loading data...")
/// .style(&SpinnerStyle::line())
/// .run(|| {
/// .run(|_| {
/// sleep(Duration::from_secs(2));
/// })
/// .expect("error running spinner");
/// ```
pub struct Spinner {
pub struct Spinner<'a> {
// The title of the spinner
pub title: String,
// The style of the spinner
pub style: SpinnerStyle,
pub style: &'a SpinnerStyle,
/// The colors/style of the spinner
pub theme: Theme,
pub theme: &'a Theme,

term: Term,
frame: usize,
height: usize,
}

impl Spinner {
impl<'a> Spinner<'a> {
/// Create a new spinner with the given title
pub fn new<S: Into<String>>(title: S) -> Self {
Self {
title: title.into(),
style: SpinnerStyle::line(),
theme: Theme::default(),
style: &DEFAULT,
theme: &theme::DEFAULT,
term: Term::stderr(),
frame: 0,
height: 0,
}
}

/// Set the style of the spinner
pub fn style(mut self, style: SpinnerStyle) -> Self {
self.style = style;
pub fn style(mut self, style: &'a SpinnerStyle) -> Self {
self.style = &style;
self
}

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

/// Displays the dialog to the user and returns their response
pub fn run<'scope, F, T>(mut self, func: F) -> io::Result<T>
// SAFETY: 'spinner must out live 'scope
// this ensures that as long as the spinner doesnt try to access the theme
// or style outside of the scope closure the theme and style will still be valid
pub fn run<'scope, 'spinner: 'scope, F, T>(mut self, func: F) -> io::Result<T>
where
F: FnOnce(Sender<SpinnerAction>) -> T + Send + 'scope,
F: FnOnce(SpinnerActionRunner<'spinner>) -> T + Send + 'scope,
T: Send + 'scope,
{
std::thread::scope(|s| {
let (sender, receiver) = mpsc::channel();
let handle = s.spawn(|| func(sender));
let handle = s.spawn(|| func(SpinnerActionRunner::new(sender)));
self.term.hide_cursor()?;
loop {
match receiver.try_recv() {
Ok(a) => match a {
SpinnerAction::Title(title) => self.title = title,
SpinnerAction::Style(s) => self.style = s,
SpinnerAction::Theme(theme) => self.theme = *theme,
SpinnerAction::Theme(theme) => self.theme = theme,
},
Err(TryRecvError::Empty) => (),
Err(TryRecvError::Disconnected) => {
Expand Down Expand Up @@ -240,7 +284,7 @@ mod test {
SpinnerStyle::minidots(),
SpinnerStyle::ellipsis(),
] {
let mut spinner = Spinner::new("Loading data...").style(t);
let mut spinner = Spinner::new("Loading data...").style(&t);
for f in spinner.style.frames.clone().iter() {
assert_eq!(
format!("{} Loading data...", f),
Expand Down

0 comments on commit 901555e

Please sign in to comment.