1mod app;
2mod ui;
3
4pub use app::App;
5
6use color_eyre::Result;
7use ratatui::{
8 Terminal,
9 backend::CrosstermBackend,
10 crossterm::event::{self, Event},
11};
12use std::{
13 io,
14 time::{Duration, Instant},
15};
16
17pub fn run() -> Result<()> {
27 let backend = CrosstermBackend::new(io::stdout());
29 let mut terminal = Terminal::new(backend)?;
30
31 let mut app = App::new()?;
33
34 let panic_hook = std::panic::take_hook();
36 std::panic::set_hook(Box::new(move |panic_info| {
37 crossterm::execute!(io::stdout(), crossterm::terminal::LeaveAlternateScreen).ok();
38 crossterm::terminal::disable_raw_mode().ok();
39 panic_hook(panic_info);
40 }));
41
42 crossterm::terminal::enable_raw_mode()?;
44 crossterm::execute!(
45 io::stdout(),
46 crossterm::terminal::EnterAlternateScreen,
47 crossterm::event::EnableMouseCapture
48 )?;
49
50 terminal.clear()?;
51
52 let mut last_key_time: Option<Instant> = None;
54
55 loop {
56 terminal.draw(|f| ui::draw(f, &mut app))?;
58
59 if event::poll(Duration::from_millis(50))? {
61 if let Ok(Event::Key(key)) = event::read() {
62 if key.kind == event::KeyEventKind::Press {
64 let now = Instant::now();
65
66 if let Some(last_time) = last_key_time {
68 if now.duration_since(last_time) < Duration::from_millis(50) {
69 continue;
70 }
71 }
72
73 last_key_time = Some(now);
74
75 if app.handle_key_event(key)? {
76 break;
77 }
78 }
79 }
80 }
81
82 app.tick();
84 }
85
86 crossterm::terminal::disable_raw_mode()?;
88 crossterm::execute!(
89 io::stdout(),
90 crossterm::terminal::LeaveAlternateScreen,
91 crossterm::event::DisableMouseCapture
92 )?;
93
94 terminal.show_cursor()?;
95
96 Ok(())
97}