mod app;
mod event;
mod state;
mod stats;
mod ui;
pub use app::App;
pub use state::FileState;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use color_eyre::Result;
use pagers_core::events::Event as CoreEvent;
use pagers_core::mincore::PageMap;
use pagers_core::ops::Stats;
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::{TerminalOptions, Viewport};
const MAX_DISPLAY_FILES: u16 = 8;
const MAX_DISPLAY_PAGES: usize = 32;
const FRAME_BUDGET: Duration = Duration::from_millis(100);
pub fn run<PM: PageMap + Send + 'static>(
rx: mpsc::Receiver<CoreEvent<PM>>,
term: Arc<AtomicBool>,
core_stats: Arc<Stats>,
label: &str,
action_sign: isize,
start: Instant,
) -> Result<()> {
color_eyre::install()?;
let viewport_height = MAX_DISPLAY_FILES + stats::SUMMARY_LINES;
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(std::io::stdout(), crossterm::cursor::Hide)?;
let backend = CrosstermBackend::new(std::io::stdout());
let mut terminal = Terminal::with_options(
backend,
TerminalOptions {
viewport: Viewport::Inline(viewport_height),
},
)?;
let term_cleanup = Arc::clone(&term);
let tui_rx = event::spawn_event_threads(rx, term);
let mut app = App::new();
let mut quit = false;
let mut done = false;
let mut file_rows_hwm: u16 = 0;
loop {
match tui_rx.recv_timeout(FRAME_BUDGET) {
Ok(evt) => match app.handle_event(evt) {
app::ControlFlow::Continue => {}
app::ControlFlow::Done => done = true,
app::ControlFlow::Quit => quit = true,
},
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
if !done && !quit {
while let Ok(next) = tui_rx.try_recv() {
match app.handle_event(next) {
app::ControlFlow::Continue => {}
app::ControlFlow::Done => {
done = true;
break;
}
app::ControlFlow::Quit => {
quit = true;
break;
}
}
}
}
let elapsed = start.elapsed().as_secs_f64();
let files = app.visible_files(MAX_DISPLAY_FILES as usize);
file_rows_hwm = file_rows_hwm.max(files.len().min(MAX_DISPLAY_FILES as usize) as u16);
terminal.draw(|frame| {
let [files_area, stats_area] = ui::layout(file_rows_hwm, frame.area());
ui::render_refs_to_buf(&files, file_rows_hwm, files_area, frame.buffer_mut());
stats::render_summary(
&core_stats,
elapsed,
label,
action_sign,
stats_area,
frame.buffer_mut(),
);
})?;
if done || quit {
break;
}
}
if quit {
term_cleanup.store(true, std::sync::atomic::Ordering::Relaxed);
}
if !quit {
let elapsed = start.elapsed().as_secs_f64();
let files = app.visible_files(MAX_DISPLAY_FILES as usize);
file_rows_hwm = file_rows_hwm.max(files.len().min(MAX_DISPLAY_FILES as usize) as u16);
let total_lines = file_rows_hwm + stats::SUMMARY_LINES;
let _ = terminal.insert_before(total_lines, |buf| {
let [files_area, stats_area] = ui::layout(file_rows_hwm, buf.area);
ui::render_refs_to_buf(&files, file_rows_hwm, files_area, buf);
stats::render_summary(&core_stats, elapsed, label, action_sign, stats_area, buf);
});
}
crossterm::execute!(std::io::stdout(), crossterm::cursor::Show)?;
crossterm::terminal::disable_raw_mode()?;
Ok(())
}