use super::*;
pub type AsyncClosure<T> = Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<T, Box<dyn std::error::Error>>> + Send>> + Send + Sync>;
#[derive(Clone)]
pub struct AsyncChoice<T> {
pub name: String,
pub description: String,
action: AsyncClosure<T>,
}
impl<T> Pickable for AsyncChoice<T> {
fn get_title(&self) -> String {
self.name.clone()
}
fn get_description(&self) -> String {
self.description.clone()
}
}
impl<T> AsyncChoice<T> {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
action: AsyncClosure<T>,
) -> Self {
AsyncChoice {
name: name.into(),
description: description.into(),
action,
}
}
pub async fn run(&self) -> Result<T, Box<dyn std::error::Error>> {
(self.action)().await
}
}
pub async fn async_menu<T: Clone>(message: impl Into<String>, options: &Vec<AsyncChoice<T>>) -> Option<AsyncChoice<T>> {
let message = message.into();
let mut filter = String::new();
for _ in 0..options.len() + 2 {
println!();
}
let (_, y) = cursor::position().unwrap();
let mut corrected_y = y - options.len() as u16 - 2;
terminal::enable_raw_mode().unwrap();
let mut selected_index = 0;
read().unwrap();
loop {
execute!(
io::stdout(),
cursor::MoveTo(0, corrected_y),
terminal::Clear(terminal::ClearType::FromCursorDown)
)
.unwrap();
execute!(io::stdout(), cursor::MoveTo(0, corrected_y)).unwrap();
showln!(yellow_bold, "╭─ ", cyan_bold, message, yellow_bold, " ─");
let filtered_options: Vec<&AsyncChoice<T>> = options
.iter()
.filter(|o| {
filter.is_empty()
|| o.get_title().to_lowercase().contains(&filter.to_lowercase())
|| o.get_description().to_lowercase().contains(&filter.to_lowercase())
})
.collect();
for (index, option) in filtered_options.iter().enumerate() {
if index == selected_index {
showln!(
yellow_bold,
"│",
yellowbg,
format!(" {} ", option.get_title()),
white,
" ",
yellow_bold,
option.get_description()
);
} else {
showln!(
yellow_bold,
"│",
white,
format!(" {} ", option.get_title()),
white,
" ",
gray_dim,
option.get_description()
);
}
}
show!(yellow_bold, "╰─→ ", white_bold, filter);
io::stdout().flush().unwrap();
match read().unwrap() {
event::Event::Key(KeyEvent { code, kind, .. }) => match kind {
KeyEventKind::Press => match code {
KeyCode::Up => {
if selected_index > 0 {
selected_index -= 1;
}
}
KeyCode::Down => {
if selected_index < filtered_options.len() - 1 {
selected_index += 1;
}
}
KeyCode::Enter => {
execute!(
io::stdout(),
cursor::MoveTo(0, corrected_y + 1),
terminal::Clear(terminal::ClearType::FromCursorDown)
)
.unwrap();
showln!(
yellow_bold,
"╰→ ",
white_bold,
filtered_options[selected_index].get_title()
);
terminal::disable_raw_mode().unwrap();
return Some(filtered_options[selected_index].clone());
}
KeyCode::Char(c) => {
filter.push(c);
selected_index = 0;
}
KeyCode::Backspace => {
filter.pop();
selected_index = 0;
}
KeyCode::Esc => {
terminal::disable_raw_mode().unwrap();
return None;
}
_ => {}
},
_ => {}
},
_ => {}
}
}
}
#[macro_export]
macro_rules! async_choice {
($name:expr, $description:expr, $action:expr) => {
$crate::async_impl::AsyncChoice::new(
$name,
$description,
std::sync::Arc::new(|| Box::pin($action())),
)
};
($name:expr, $action:expr) => {
$crate::async_impl::AsyncChoice::new(
$name,
"",
std::sync::Arc::new(|| Box::pin($action())),
)
};
}
#[macro_export]
macro_rules! async_selection {
($message:expr, $choices:expr) => {
$crate::async_impl::async_menu($message, $choices).await
};
($choices:expr) => {
$crate::async_impl::async_menu("", $choices).await
};
}