cuj-tui 0.1.0

cui — a read-only TUI browser for cuj vaults
Documentation
//! The help overlay: the full binding table (rendered from KEYMAP,
//! so it cannot drift) plus the ex-command reference.

use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Clear, Paragraph};

use crate::keymap::KEYMAP;
use crate::state::Focus;

const EX_COMMANDS: &[(&str, &str)] = &[
    (":q  :quit", "quit"),
    (":help", "this overlay"),
    (":reload", "rebuild the snapshot from the vault"),
    (":profile <name|all>", "set the profile scope"),
    (
        ":find ..tag ::cat --todo --since D --until D --filter E",
        "",
    ),
    (":search <words>", "full text (zetetes hybrid, else bm25)"),
    (":goto <ref>", "jump to --N or --profile--N"),
    (":backlinks [<ref>]", "jots referencing a jot"),
];

pub fn draw(f: &mut Frame) {
    let mut lines = vec![section("keys")];
    for b in KEYMAP.iter().filter(|b| b.bar.is_some()) {
        let ctx = match (b.when.focus, b.when.results_only) {
            (_, true) => " (results)",
            (Some(Focus::List), _) => " (list)",
            (Some(Focus::View), _) => " (view)",
            (None, _) => "",
        };
        lines.push(Line::from(vec![
            Span::styled(
                format!("  {:>6}  ", b.key_label),
                Style::new().add_modifier(Modifier::BOLD),
            ),
            Span::raw(b.action_label),
            Span::styled(ctx, Style::new().add_modifier(Modifier::DIM)),
        ]));
    }
    lines.push(Line::default());
    lines.push(section("ex commands"));
    for (cmd, what) in EX_COMMANDS {
        lines.push(Line::from(vec![
            Span::styled(
                format!("  {cmd}"),
                Style::new().add_modifier(Modifier::BOLD),
            ),
            Span::styled(
                if what.is_empty() {
                    String::new()
                } else {
                    format!("{what}")
                },
                Style::new().add_modifier(Modifier::DIM),
            ),
        ]));
    }

    let area = f.area();
    let w = 64.min(area.width.saturating_sub(2)).max(1);
    let h = (lines.len() as u16 + 2)
        .min(area.height.saturating_sub(1))
        .max(1);
    let rect = Rect {
        x: area.x + (area.width.saturating_sub(w)) / 2,
        y: area.y + (area.height.saturating_sub(h)) / 2,
        width: w,
        height: h,
    };
    f.render_widget(Clear, rect);
    f.render_widget(
        Paragraph::new(lines).block(Block::bordered().title(" help ")),
        rect,
    );
}

fn section(title: &'static str) -> Line<'static> {
    Line::from(Span::styled(
        title,
        Style::new().add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
    ))
}