use std::io::Write;
use crossterm::{
cursor,
event::{Event, KeyCode, KeyModifiers},
queue,
style::Print,
terminal::{self, ClearType},
};
use xacli_core::{Context, Error, InputValue, Result};
pub struct Select {
prompt: String,
options: Vec<(String, InputValue)>,
}
impl Select {
pub fn new(prompt: impl Into<String>) -> Self {
Self {
prompt: prompt.into(),
options: Vec::new(),
}
}
pub fn option(mut self, label: impl Into<String>, value: InputValue) -> Self {
self.options.push((label.into(), value));
self
}
pub fn options(mut self, options: Vec<(String, InputValue)>) -> Self {
self.options = options;
self
}
pub fn run(mut self, ctx: &mut dyn Context) -> Result<InputValue> {
terminal::enable_raw_mode()?;
let result = self.run_inner(ctx);
let _ = terminal::disable_raw_mode();
result
}
fn run_inner(&mut self, ctx: &mut dyn Context) -> Result<InputValue> {
if self.options.is_empty() {
return Err(Error::InvalidInput("No options provided".into()));
}
let mut selected = 0;
let mut first_render = true;
let stdout = &mut ctx.stdout();
self.render(stdout, selected, first_render)?;
first_render = false;
loop {
let e = ctx.read_event()?;
if let Event::Key(key) = e {
match key.code {
KeyCode::Enter => {
self.clear_render(stdout)?;
queue!(
stdout,
cursor::MoveToColumn(0),
Print("✓ "),
Print(&self.options[selected].0),
Print("\r\n")
)?;
stdout.flush()?;
return Ok(self.options.remove(selected).1);
}
KeyCode::Esc => {
self.clear_render(stdout)?;
return Err(Error::InterruptError);
}
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.clear_render(stdout)?;
return Err(Error::InterruptError);
}
KeyCode::Up | KeyCode::Char('k') => {
if selected > 0 {
selected -= 1;
self.render(stdout, selected, first_render)?;
}
}
KeyCode::Down | KeyCode::Char('j') => {
if selected < self.options.len() - 1 {
selected += 1;
self.render(stdout, selected, first_render)?;
}
}
KeyCode::Home => {
selected = 0;
self.render(stdout, selected, first_render)?;
}
KeyCode::End => {
selected = self.options.len() - 1;
self.render(stdout, selected, first_render)?;
}
_ => {}
}
}
}
}
fn render(&self, stdout: &mut impl Write, selected: usize, first_render: bool) -> Result<()> {
if !first_render {
for _ in 0..self.options.len() + 1 {
queue!(
stdout,
cursor::MoveUp(1),
crossterm::terminal::Clear(ClearType::CurrentLine),
)?;
}
}
queue!(stdout, cursor::MoveToColumn(0))?;
queue!(stdout, Print(&self.prompt), Print("\r\n"))?;
for (i, (label, _)) in self.options.iter().enumerate() {
let marker = if i == selected { ">" } else { " " };
queue!(
stdout,
cursor::MoveToColumn(0),
Print(marker),
Print(" "),
Print(label),
Print("\r\n"),
)?;
}
stdout.flush()?;
Ok(())
}
fn clear_render(&self, stdout: &mut impl Write) -> Result<()> {
for _ in 0..self.options.len() + 1 {
queue!(
stdout,
cursor::MoveUp(1),
crossterm::terminal::Clear(ClearType::CurrentLine),
)?;
}
queue!(stdout, cursor::MoveToColumn(0))?;
stdout.flush()?;
Ok(())
}
}