1use clap::Parser;
2use crossterm::{
3 event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyEventKind},
4 terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
5};
6use std::{io, panic, time::Duration};
7
8use ratatui::{Terminal, backend::CrosstermBackend};
9
10use crate::{
11 error::Result,
12 git::kit::KitRepo,
13 tui::{state::TuiState, ui::render},
14 worker::Worker,
15};
16
17pub mod error;
18pub mod git;
19pub mod metrics;
20pub mod tui;
21pub mod worker;
22
23#[derive(Parser, Debug)]
24#[command(version, about, long_about = None)]
25pub struct GKitArgs {
26 #[arg(default_value = ".")]
27 target_path: String,
28
29 #[arg(long, hide = true)]
30 pub debug: bool,
31}
32
33pub fn run(args: GKitArgs) -> Result<()> {
34 let repo = KitRepo::open(args.target_path)?;
35
36 if args.debug {
37 println!("Debug Mode\n");
38 let x = repo.list_branch();
39 return Ok(());
42 }
43 let mut state = TuiState::new(&repo)?; let backend = CrosstermBackend::new(io::stdout());
46 let mut terminal = Terminal::new(backend)?;
47
48 terminal::enable_raw_mode()?;
49 ratatui::crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
50
51 panic::set_hook(Box::new(move |panic| {
52 let _ = terminal::disable_raw_mode();
53 let _ =
54 ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
55 eprintln!("Panic??: {}", panic);
56 }));
57
58 terminal.hide_cursor()?;
59 terminal.clear()?;
60
61 let tui_result = tui(&mut terminal, &mut state, &repo);
62
63 let _ = terminal.show_cursor();
64 let _ = ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
65 let _ = terminal::disable_raw_mode();
66
67 tui_result }
69
70pub fn tui<'a>(
71 terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
72 state: &mut TuiState,
73 repo: &'a KitRepo,
74) -> Result<()> {
75 let repo_path = repo.inner.path().to_path_buf();
76
77 let worker = Worker::start(repo_path);
78
79 while !state.is_quit {
80 terminal.draw(|frame| render(frame, state))?;
81
82 if state.refresh {
83 state.refresh = false;
84 state.loading = true;
85 worker.trigger_refresh();
86 }
87
88 if let Ok(payload) = worker.payload_rx.try_recv() {
90 state.refresh(payload);
91 }
92
93 if event::poll(Duration::from_millis(16))? {
94 if let Event::Key(key) = event::read()? {
95 if key.kind == KeyEventKind::Press {
96 state.handle_key_event(key, repo);
97 }
98 }
99 }
100 }
101
102 worker.quit();
103
104 Ok(())
105}