1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
pub mod app;
pub mod cli;
pub mod git;
pub mod inputs;
pub mod tui;
pub mod ui;

use anyhow::Result;
use app::{App, AppReturn};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use inputs::events::{EventHandler, InputEvent};
use ratatui::{backend::CrosstermBackend, widgets::TableState, Terminal};
use std::{
    io::{self},
    time::Duration,
};
use ui::{body::parse_diff_rows, render};

pub type CrosstermTerminal = ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stdout>>;

pub fn start_tui(app: App) -> Result<()> {
    let backend = CrosstermBackend::new(io::stdout());
    let mut terminal = Terminal::new(backend)?;

    term_startup(&mut terminal)?;
    let status = run(&mut terminal, app);
    term_shutdown(&mut terminal)?;
    status?;

    Ok(())
}

fn term_startup(terminal: &mut CrosstermTerminal) -> Result<()> {
    enable_raw_mode()?;
    crossterm::execute!(io::stdout(), crossterm::terminal::EnterAlternateScreen)?;
    terminal.clear()?;
    terminal.hide_cursor()?;
    Ok(())
}

fn term_shutdown(terminal: &mut CrosstermTerminal) -> Result<()> {
    // Restore the terminal and close application
    crossterm::execute!(io::stdout(), crossterm::terminal::LeaveAlternateScreen)?;
    terminal.clear()?;
    terminal.hide_cursor()?;
    disable_raw_mode()?;
    Ok(())
}

fn create_diff_state() -> TableState {
    let mut diff_state = TableState::default();
    diff_state.select(Some(0));

    diff_state
}

fn run(terminal: &mut CrosstermTerminal, mut app: App) -> Result<()> {
    let tick_rate = Duration::from_millis(200);
    let events = EventHandler::new(tick_rate);

    let mut diff_one_state = create_diff_state();
    let mut diff_two_state = create_diff_state();

    // todo Want this whole app clone gone
    // let app_clone = app.clone();
    let diff_one = app.state().diff().unwrap().diff_one().clone();
    let diff_two = app.state().diff().unwrap().diff_two().clone();
    let diff_one_rows = parse_diff_rows(&diff_one);
    let diff_two_rows = parse_diff_rows(&diff_two);

    loop {
        // Render ui
        terminal.draw(|rect| {
            render(
                rect,
                &app,
                &mut diff_one_state,
                &mut diff_two_state,
                &diff_one_rows,
                &diff_two_rows,
            )
        })?;

        // Handle inputs
        let input_result = match events.next()? {
            InputEvent::Input(key) => app.do_action(
                key,
                &mut diff_one_state,
                &mut diff_two_state,
                &diff_one_rows,
                &diff_two_rows,
            ),
            InputEvent::Tick => app.update_on_tick(),
        };

        // Check if we should exit
        if input_result == AppReturn::Exit {
            break;
        }
    }

    Ok(())
}