gex 0.2.1

Git workflow improvement CLI tool inspired by Magit
use crossterm::{
    cursor,
    event::{self, Event, KeyCode},
    terminal::{self, ClearType},
};
use std::{
    io::{stdin, stdout, BufRead, Write},
    path::Path,
    process::{self, Command, Stdio},
};

mod branch;
pub mod parse;
mod status;

use branch::BranchList;
use status::Status;

pub trait Expand {
    fn toggle_expand(&mut self);
    fn expanded(&self) -> bool;
}

enum State {
    Status,
    Branch,
}

fn main() {
    if !Path::new("./.git").is_dir() {
        print!("Not a git repository. Initialise one? [y/N]");
        let _ = stdout().flush();
        if let Some(Ok(input)) = stdin().lock().lines().next() {
            if input.to_lowercase() != "y" {
                process::exit(0);
            }

            Command::new("git")
                .arg("init")
                .output()
                .expect("failed to run `git init`");
        }
    }

    let mut status = Status::new();
    let mut branch_list = BranchList::new();

    crossterm::execute!(stdout(), terminal::EnterAlternateScreen)
        .expect("failed to enter alternate screen");
    terminal::enable_raw_mode().expect("failed to put terminal in raw mode");
    print!("{}", cursor::Hide);

    let mut state = State::Status;

    loop {
        match state {
            State::Status => {
                print!(
                    "{}{}{}{}",
                    cursor::MoveToRow(0),
                    terminal::Clear(ClearType::All),
                    status,
                    cursor::MoveToColumn(0)
                );
            }
            State::Branch => {
                print!(
                    "{}{}{}",
                    cursor::MoveToRow(0),
                    terminal::Clear(ClearType::All),
                    branch_list
                );
            }
        }
        if let Event::Key(event) = event::read().unwrap() {
            match state {
                State::Status => match event.code {
                    KeyCode::Char('j') | KeyCode::Down => status.down(),
                    KeyCode::Char('k') | KeyCode::Up => status.up(),
                    KeyCode::Char('s') => status.stage(),
                    KeyCode::Char('S') => {
                        Command::new("git")
                            .args(["add", "."])
                            .output()
                            .expect("couldn't run `git add .`");
                        status.fetch();
                    }
                    KeyCode::Char('u') => status.unstage(),
                    KeyCode::Char('U') => {
                        Command::new("git")
                            .arg("reset")
                            .output()
                            .expect("failed to run `git reset`");
                        status.fetch();
                    }
                    KeyCode::Tab => status.expand(),
                    KeyCode::Char('c') => {
                        crossterm::execute!(stdout(), terminal::LeaveAlternateScreen)
                            .expect("failed to leave alternate screen");
                        Command::new("git")
                            .arg("commit")
                            .stdout(Stdio::inherit())
                            .stdin(Stdio::inherit())
                            .stderr(Stdio::inherit())
                            .output()
                            .expect("failed to run `git commit`");
                        status.fetch();
                        crossterm::execute!(stdout(), terminal::EnterAlternateScreen, cursor::Hide)
                            .expect("failed to enter alternate screen");
                    }
                    KeyCode::Char('b') => {
                        branch_list.fetch();
                        state = State::Branch;
                    }
                    KeyCode::Char('r') => status.fetch(),
                    KeyCode::Char('q') => {
                        terminal::disable_raw_mode().unwrap();
                        crossterm::execute!(
                            stdout(),
                            terminal::LeaveAlternateScreen,
                            cursor::Show,
                            cursor::MoveToColumn(0)
                        )
                        .expect("failed to leave alternate screen");
                        process::exit(0);
                    }
                    _ => {}
                },
                State::Branch => match event.code {
                    KeyCode::Char('k') | KeyCode::Up => {
                        branch_list.cursor = branch_list.cursor.saturating_sub(1);
                    }
                    KeyCode::Char('j') | KeyCode::Down => {
                        branch_list.cursor += 1;
                        if branch_list.cursor >= branch_list.branches.len() {
                            branch_list.cursor = branch_list.branches.len() - 1;
                        }
                    }
                    KeyCode::Char(' ') | KeyCode::Enter => {
                        branch_list.checkout();
                        status.fetch();
                        state = State::Status;
                    }
                    KeyCode::Char('b') => {
                        BranchList::checkout_new();
                        status.fetch();
                        state = State::Status;
                    }
                    KeyCode::Esc => state = State::Status,
                    KeyCode::Char('q') => {
                        terminal::disable_raw_mode().unwrap();
                        crossterm::execute!(
                            stdout(),
                            terminal::LeaveAlternateScreen,
                            cursor::Show,
                            cursor::MoveToColumn(0)
                        )
                        .expect("failed to leave alternate screen");
                        process::exit(0);
                    }
                    _ => {}
                },
            };
        }
    }
}