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::{Context, Error, InputValue, Result};

pub struct Input {
    prompt: String,
    default: Option<String>,
}

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

    pub fn run(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
    }

    fn run_inner(self, ctx: &mut dyn Context) -> Result<InputValue> {
        let mut input = self.default.clone().unwrap_or_default();
        let mut cursor_pos = input.len();

        let stdout = &mut ctx.stdout();

        // 显示提示
        self.render(stdout, &input, cursor_pos)?;

        loop {
            if let Event::Key(key) = ctx.read_event()? {
                match key.code {
                    KeyCode::Enter => {
                        return Ok(InputValue::String(input));
                    }
                    KeyCode::Esc => {
                        return Err(Error::InterruptError);
                    }
                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        return Err(Error::InterruptError);
                    }
                    KeyCode::Char(c) => {
                        input.insert(cursor_pos, c);
                        cursor_pos += 1;
                    }
                    KeyCode::Backspace => {
                        if cursor_pos > 0 {
                            cursor_pos -= 1;
                            input.remove(cursor_pos);
                        }
                    }
                    KeyCode::Delete => {
                        if cursor_pos < input.len() {
                            input.remove(cursor_pos);
                        }
                    }
                    KeyCode::Left => {
                        cursor_pos = cursor_pos.saturating_sub(1);
                    }
                    KeyCode::Right => {
                        if cursor_pos < input.len() {
                            cursor_pos += 1;
                        }
                    }
                    KeyCode::Home => {
                        cursor_pos = 0;
                    }
                    KeyCode::End => {
                        cursor_pos = input.len();
                    }
                    _ => {}
                }

                self.render(stdout, &input, cursor_pos)?;
            }
        }
    }

    fn render<W: Write>(&self, stdout: &mut W, input: &str, cursor_pos: usize) -> Result<()> {
        queue!(
            stdout,
            cursor::MoveToColumn(0),
            terminal::Clear(terminal::ClearType::CurrentLine),
            style::Print(&self.prompt),
            style::Print(" "),
            style::Print(input),
            cursor::MoveToColumn((self.prompt.len() + 1 + cursor_pos) as u16)
        )?;

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