pagers-tui 0.1.4

Terminal UI for pagers page cache diagnostics
Documentation
use std::sync::atomic::Ordering;

use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::macros::horizontal;
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Widget;

use pagers_core::ops::Stats;
use pagers_core::output::{pretty_elapsed, pretty_size};

pub(crate) const SUMMARY_LINES: u16 = 5;

const LABEL_WIDTH: u16 = 17;

pub(crate) fn render_summary(
    stats: &Stats,
    elapsed: f64,
    label: &str,
    action_sign: isize,
    area: Rect,
    buf: &mut Buffer,
) {
    let page_size = *pagers_core::pagesize::PAGE_SIZE;
    let total_pages = stats.total_pages.load(Ordering::Relaxed);
    let initial = stats.initial_pages_in_core.load(Ordering::Relaxed);
    let action_pages = stats.action_pages.load(Ordering::Relaxed);
    let total_files = stats.total_files.load(Ordering::Relaxed);
    let total_dirs = stats.total_dirs.load(Ordering::Relaxed);

    let total_size = total_pages * page_size;
    let action_size = action_pages * page_size;
    let signed_action = (action_pages as isize) * action_sign;
    let resident_pages = initial.saturating_add_signed(signed_action);
    let resident_size = resident_pages * page_size;

    let label_style = Style::default().fg(Color::DarkGray);

    let mut cap = label.to_string();
    if let Some(c) = cap.get_mut(0..1) {
        c.make_ascii_uppercase();
    }

    let has_action = action_sign != 0;
    let mut rows: Vec<(String, Line)> = vec![
        ("Files:".into(), Line::from(format!("{total_files}"))),
        ("Directories:".into(), Line::from(format!("{total_dirs}"))),
    ];

    if has_action {
        rows.push((
            format!("{cap} Pages:"),
            pct_line(action_pages, total_pages, action_size, total_size),
        ));
    }

    rows.push((
        "Resident Pages:".into(),
        pct_line(resident_pages, total_pages, resident_size, total_size),
    ));

    rows.push(("Elapsed:".into(), Line::from(pretty_elapsed(elapsed))));

    let constraints: Vec<Constraint> = rows.iter().map(|_| Constraint::Length(1)).collect();
    let line_areas = Layout::vertical(constraints).split(area);
    for (i, (label_text, value_line)) in rows.into_iter().enumerate() {
        let [label_area, _, value_area] = horizontal![==LABEL_WIDTH, ==1, *=1].areas(line_areas[i]);

        Line::from(Span::styled(label_text, label_style))
            .alignment(Alignment::Right)
            .render(label_area, buf);

        value_line.render(value_area, buf);
    }
}

fn pct_line(pages: usize, total_pages: usize, size: usize, total_size: usize) -> Line<'static> {
    let pct = if total_pages > 0 {
        100.0 * pages as f64 / total_pages as f64
    } else {
        0.0
    };
    let mut spans = vec![Span::raw(format!(
        "{pages}/{total_pages}  {}/{}",
        pretty_size(size),
        pretty_size(total_size)
    ))];
    if total_pages > 0 {
        spans.push(Span::raw(format!("  {pct:.3}%")));
    }
    Line::from(spans)
}