cargo-hatch 0.1.0

Hatch new projects like a chick coming out of its egg.
Documentation
use std::{convert::TryInto, io};

use crossterm::{
    cursor::{self, MoveToNextLine, MoveToPreviousLine, RestorePosition, SavePosition},
    event::{Event, KeyCode, KeyEvent, KeyModifiers},
    execute, queue,
    style::{Color, Print, SetForegroundColor},
    terminal::{self, ScrollUp},
};

use super::{InputReader, ListSetting, ReadError};

pub struct ListReader {
    setting: ListSetting,
}

impl ListReader {
    #[must_use]
    pub fn new(setting: ListSetting) -> Self {
        Self { setting }
    }

    fn print_list(&self, index: usize) -> io::Result<()> {
        let stdout = io::stdout();
        let mut handle = stdout.lock();

        queue!(handle, RestorePosition)?;

        for (i, value) in self.setting.values.iter().enumerate() {
            queue!(
                handle,
                SetForegroundColor(if i == index {
                    Color::Green
                } else {
                    Color::Reset
                }),
                Print(if i == index { "* " } else { "  " }),
                Print(value),
                MoveToNextLine(1),
            )?;
        }

        execute!(handle, SetForegroundColor(Color::Reset))
    }
}

impl InputReader for ListReader {
    type Output = String;

    fn read(&mut self, description: &str) -> Result<Self::Output, ReadError> {
        let mut index = self
            .setting
            .values
            .iter()
            .position(|value| Some(value) == self.setting.default.as_ref())
            .unwrap_or_default();
        let length = self.setting.values.len().try_into().unwrap_or(u16::MAX);

        terminal::enable_raw_mode()?;

        prepare_area(description, length)?;
        self.print_list(index)?;

        while let Ok(event) = crossterm::event::read() {
            if let Event::Key(KeyEvent { code, modifiers }) = event {
                match code {
                    KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
                        terminal::disable_raw_mode()?;
                        return Err(ReadError::Cancelled);
                    }
                    KeyCode::Enter => break,
                    KeyCode::Up => {
                        if index == 0 {
                            index = self.setting.values.len() - 1;
                        } else {
                            index -= 1;
                        }
                        self.print_list(index)?;
                    }
                    KeyCode::Down => {
                        if index == self.setting.values.len() - 1 {
                            index = 0;
                        } else {
                            index += 1;
                        }
                        self.print_list(index)?;
                    }
                    _ => {}
                }
            }
        }

        terminal::disable_raw_mode()?;

        Ok(self.setting.values[index].clone())
    }
}

fn prepare_area(description: &str, length: u16) -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();

    let space = terminal::size()?.1 - cursor::position()?.1;

    queue!(handle, Print(format!("{}:", description)))?;

    if space > length {
        queue!(handle, MoveToNextLine(1), SavePosition)
    } else {
        queue!(
            handle,
            ScrollUp(2 + length - space),
            MoveToPreviousLine(1 + length - space),
            SavePosition,
        )
    }
}