git-step 0.1.1

A tool to step through git commits
use std::io::Write;

use anyhow::Result;
use console::{Key, Style, Term};
use once_cell::sync::Lazy;

use crate::{git, Args};

struct Action {
    keybind: char,
    description: String,
}

const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

struct Styles {
    title: Style,
    primary: Style,
    secondary: Style,
}

static STYLES: Lazy<Styles> = Lazy::new(|| Styles {
    title: Style::new().cyan().bold(),
    primary: Style::new().white(),
    secondary: Style::new().dim(),
});

static ACTIONS: Lazy<[Action; 5]> = Lazy::new(|| {
    [
        Action::new('h', "show help"),
        Action::new('a', "step back"),
        Action::new('d', "step forward"),
        Action::new('c', "clear console"),
        Action::new('q', "exit"),
    ]
});

impl Action {
    fn new(keybind: char, description: &str) -> Self {
        Self {
            keybind,
            description: description.to_string(),
        }
    }

    fn get_prompt(&self) -> String {
        format_args!(
            "{} {} {}",
            STYLES.secondary.apply_to("press"),
            STYLES.primary.apply_to(self.keybind),
            STYLES
                .secondary
                .apply_to(format!("to {}", self.description))
        )
        .to_string()
    }
}

pub fn start(term: &mut Term, args: &Args, steps: &mut i32) -> Result<()> {
    let commits = git::get_commits(&args)?;
    let mut index = commits.len() - 1;

    display_menu(term, args)?;

    loop {
        if let Ok(character) = term.read_key() {
            match character {
                Key::Char('a') | Key::ArrowLeft => {
                    if index > 1 {
                        index -= 1;
                        git::checkout_target(&commits[index], args)?;
                        *steps += 1;
                    }
                }
                Key::Char('d') | Key::ArrowRight => {
                    if index < commits.len() - 1 {
                        index += 1;
                        git::checkout_target(&commits[index], args)?;
                        *steps += 1;
                    }
                }
                Key::Char('h') => display_menu(term, args)?,
                Key::Char('c') => term.clear_screen()?,
                Key::Char('q') => break,
                _ => continue,
            }
        }
    }

    Ok(())
}

fn display_menu(term: &mut Term, args: &Args) -> Result<()> {
    term.clear_screen()?;

    let head = git::parse_head(args)?;

    term.write_fmt(format_args!(
        "{} {}\n\n",
        STYLES.title.apply_to([NAME, VERSION].join(" ")),
        STYLES.secondary.apply_to(head),
    ))?;

    for action in ACTIONS.iter() {
        term.write_line(action.get_prompt().as_str())?
    }

    term.write_line("")?;

    Ok(())
}