1pub mod app;
12pub mod events;
13pub mod features;
14pub mod layout;
15pub mod utils;
16pub mod widgets;
17
18use std::io;
19use std::panic;
20use std::path::PathBuf;
21use std::time::Duration;
22
23use anyhow::Result;
24use crossterm::event::{self, Event};
25use crossterm::execute;
26use crossterm::terminal::{
27 disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
28};
29use ratatui::backend::CrosstermBackend;
30use ratatui::Terminal;
31
32use crate::app::App;
33
34pub fn run(mut repo_path: Option<PathBuf>) -> Result<()> {
39 if repo_path.is_none() {
41 repo_path = gitkraft_core::features::persistence::get_last_repo()
42 .ok()
43 .flatten();
44 }
45
46 let default_hook = panic::take_hook();
50 panic::set_hook(Box::new(move |info| {
51 let _ = restore_terminal();
52 default_hook(info);
53 }));
54
55 enable_raw_mode()?;
56 let mut stdout = io::stdout();
57 execute!(stdout, EnterAlternateScreen)?;
58 let backend = CrosstermBackend::new(stdout);
59 let mut terminal = Terminal::new(backend)?;
60
61 let result = run_app(&mut terminal, repo_path);
62
63 restore_terminal()?;
64 terminal.show_cursor()?;
65 result
66}
67
68pub fn run_app<B: ratatui::backend::Backend>(
70 terminal: &mut Terminal<B>,
71 repo_path: Option<PathBuf>,
72) -> Result<()>
73where
74 B::Error: Send + Sync + 'static,
75{
76 let mut app = App::new();
77
78 if let Some(path) = repo_path {
79 app.open_repo(path);
81 } else {
82 if let Ok(settings) = gitkraft_core::features::persistence::load_settings() {
84 let paths: Vec<PathBuf> = settings
85 .open_tabs
86 .into_iter()
87 .filter(|p| p.exists())
88 .collect();
89 let active = settings.active_tab_index;
90 if !paths.is_empty() {
91 app.tabs.clear();
92 for _ in &paths {
93 app.tabs.push(crate::app::RepoTab::new());
94 }
95 for (i, path) in paths.into_iter().enumerate() {
97 app.active_tab_index = i;
98 app.open_repo(path);
99 }
100 app.active_tab_index = active.min(app.tabs.len().saturating_sub(1));
102 app.screen = crate::app::AppScreen::Main;
103 }
104 }
105 }
106
107 loop {
108 app.tick_count = app.tick_count.wrapping_add(1);
109
110 app.poll_background();
114
115 terminal.draw(|frame| layout::render(&mut app, frame))?;
116
117 if event::poll(Duration::from_millis(33))? {
121 if let Event::Key(key) = event::read()? {
122 if key.kind == crossterm::event::KeyEventKind::Press {
124 events::handle_key(&mut app, key);
125 }
126 }
127 }
128
129 if app.should_quit {
130 break;
131 }
132 }
133
134 Ok(())
135}
136
137fn restore_terminal() -> Result<()> {
140 disable_raw_mode()?;
141 execute!(io::stdout(), LeaveAlternateScreen)?;
142 Ok(())
143}