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
104
105
106
107
108
109
110
//! `tui` subcommand
mod ls;
mod progress;
mod restore;
mod snapshots;
mod tree;
mod widgets;

use crossterm::event::{KeyEvent, KeyModifiers};
use progress::TuiProgressBars;
use scopeguard::defer;
use snapshots::Snapshots;

use std::io;
use std::sync::{Arc, RwLock};

use crate::commands::open_repository_indexed_with_progress;
use crate::{Application, RUSTIC_APP};

use anyhow::Result;
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::prelude::*;
use rustic_core::{IndexedFull, Progress, ProgressBars, SnapshotGroupCriterion};

struct App<'a, P, S> {
    snapshots: Snapshots<'a, P, S>,
}

pub fn run(group_by: SnapshotGroupCriterion) -> Result<()> {
    let config = RUSTIC_APP.config();

    // setup terminal
    let terminal = init_terminal()?;
    let terminal = Arc::new(RwLock::new(terminal));

    // restore terminal (even when leaving through ?, early return, or panic)
    defer! {
        reset_terminal().unwrap();
    }

    let progress = TuiProgressBars {
        terminal: terminal.clone(),
    };
    let p = progress.progress_spinner("starting rustic in interactive mode...");
    let repo = open_repository_indexed_with_progress(&config.repository, progress)?;
    p.finish();
    // create app and run it
    let snapshots = Snapshots::new(&repo, config.snapshot_filter.clone(), group_by)?;
    let app = App { snapshots };
    let res = run_app(terminal, app);

    if let Err(err) = res {
        println!("{err:?}");
    }

    Ok(())
}

/// Initializes the terminal.
fn init_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
    execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
    enable_raw_mode()?;

    let backend = CrosstermBackend::new(io::stdout());

    let mut terminal = Terminal::new(backend)?;
    terminal.hide_cursor()?;

    Ok(terminal)
}

/// Resets the terminal.
fn reset_terminal() -> Result<()> {
    disable_raw_mode()?;
    execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
    Ok(())
}

fn run_app<B: Backend, P: ProgressBars, S: IndexedFull>(
    terminal: Arc<RwLock<Terminal<B>>>,
    mut app: App<'_, P, S>,
) -> Result<()> {
    loop {
        _ = terminal.write().unwrap().draw(|f| ui(f, &mut app))?;
        let event = event::read()?;
        use KeyCode::*;

        if let Event::Key(KeyEvent {
            code: Char('c'),
            modifiers: KeyModifiers::CONTROL,
            kind: KeyEventKind::Press,
            ..
        }) = event
        {
            return Ok(());
        }
        if app.snapshots.input(event)? {
            return Ok(());
        }
    }
}

fn ui<P: ProgressBars, S: IndexedFull>(f: &mut Frame<'_>, app: &mut App<'_, P, S>) {
    let area = f.area();
    app.snapshots.draw(area, f);
}