xacli-components 0.2.1

Interactive components for XaCLI
Documentation
use std::io::Write;

use crossterm::{
    cursor,
    event::{Event, KeyCode, KeyModifiers},
    queue, style, terminal,
};
use xacli_core::{ComponentInfo, Context, Error, InputComponent, InputValue, Result};

pub struct Confirm {
    prompt: String,
    default: bool,
}

impl Confirm {
    pub fn new(prompt: impl Into<String>) -> Self {
        Self {
            prompt: prompt.into(),
            default: false,
        }
    }

    fn render(&self, ctx: &mut dyn Context, choice: bool) -> Result<()> {
        let hint = if choice { "(Y/n)" } else { "(y/N)" };

        let mut stdout = ctx.stdout();

        queue!(
            stdout,
            cursor::MoveToColumn(0),
            terminal::Clear(terminal::ClearType::CurrentLine),
            style::Print(&self.prompt),
            style::Print(" "),
            style::Print(hint),
        )?;

        stdout.flush()?;
        Ok(())
    }

    fn run_inner(&mut self, ctx: &mut dyn Context) -> Result<InputValue> {
        let mut choice = self.default;

        self.render(ctx, choice)?;

        loop {
            if let Event::Key(key) = ctx.read_event()? {
                match key.code {
                    KeyCode::Enter => {
                        return Ok(InputValue::Bool(choice));
                    }
                    KeyCode::Esc => {
                        return Err(Error::InterruptError);
                    }
                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        return Err(Error::InterruptError);
                    }
                    KeyCode::Char('y') | KeyCode::Char('Y') => {
                        choice = true;
                        self.render(ctx, choice)?;
                    }
                    KeyCode::Char('n') | KeyCode::Char('N') => {
                        choice = false;
                        self.render(ctx, choice)?;
                    }
                    KeyCode::Left | KeyCode::Right => {
                        choice = !choice;
                        self.render(ctx, choice)?;
                    }
                    _ => {}
                }
            }
        }
    }
}

impl InputComponent for Confirm {
    fn info(&self) -> ComponentInfo {
        ComponentInfo {
            name: "confirm".to_string(),
            title: "Confirmation Prompt".to_string(),
            description: self.prompt.clone(),
        }
    }

    fn run(&mut self, ctx: &mut dyn Context) -> Result<InputValue> {
        // Enable raw mode for keyboard input
        terminal::enable_raw_mode()?;
        let result = self.run_inner(ctx);
        // Always disable raw mode when done
        let _ = terminal::disable_raw_mode();
        result
    }
}