Skip to main content

cui/ui/
help.rs

1//! The help overlay: the full binding table (rendered from KEYMAP,
2//! so it cannot drift) plus the ex-command reference.
3
4use ratatui::Frame;
5use ratatui::layout::Rect;
6use ratatui::style::{Modifier, Style};
7use ratatui::text::{Line, Span};
8use ratatui::widgets::{Block, Clear, Paragraph};
9
10use crate::keymap::KEYMAP;
11use crate::state::Focus;
12
13const EX_COMMANDS: &[(&str, &str)] = &[
14    (":q  :quit", "quit"),
15    (":help", "this overlay"),
16    (":reload", "rebuild the snapshot from the vault"),
17    (":profile <name|all>", "set the profile scope"),
18    (
19        ":find ..tag ::cat --todo --since D --until D --filter E",
20        "",
21    ),
22    (":search <words>", "full text (zetetes hybrid, else bm25)"),
23    (":goto <ref>", "jump to --N or --profile--N"),
24    (":backlinks [<ref>]", "jots referencing a jot"),
25];
26
27pub fn draw(f: &mut Frame) {
28    let mut lines = vec![section("keys")];
29    for b in KEYMAP.iter().filter(|b| b.bar.is_some()) {
30        let ctx = match (b.when.focus, b.when.results_only) {
31            (_, true) => " (results)",
32            (Some(Focus::List), _) => " (list)",
33            (Some(Focus::View), _) => " (view)",
34            (None, _) => "",
35        };
36        lines.push(Line::from(vec![
37            Span::styled(
38                format!("  {:>6}  ", b.key_label),
39                Style::new().add_modifier(Modifier::BOLD),
40            ),
41            Span::raw(b.action_label),
42            Span::styled(ctx, Style::new().add_modifier(Modifier::DIM)),
43        ]));
44    }
45    lines.push(Line::default());
46    lines.push(section("ex commands"));
47    for (cmd, what) in EX_COMMANDS {
48        lines.push(Line::from(vec![
49            Span::styled(
50                format!("  {cmd}"),
51                Style::new().add_modifier(Modifier::BOLD),
52            ),
53            Span::styled(
54                if what.is_empty() {
55                    String::new()
56                } else {
57                    format!("  — {what}")
58                },
59                Style::new().add_modifier(Modifier::DIM),
60            ),
61        ]));
62    }
63
64    let area = f.area();
65    let w = 64.min(area.width.saturating_sub(2)).max(1);
66    let h = (lines.len() as u16 + 2)
67        .min(area.height.saturating_sub(1))
68        .max(1);
69    let rect = Rect {
70        x: area.x + (area.width.saturating_sub(w)) / 2,
71        y: area.y + (area.height.saturating_sub(h)) / 2,
72        width: w,
73        height: h,
74    };
75    f.render_widget(Clear, rect);
76    f.render_widget(
77        Paragraph::new(lines).block(Block::bordered().title(" help ")),
78        rect,
79    );
80}
81
82fn section(title: &'static str) -> Line<'static> {
83    Line::from(Span::styled(
84        title,
85        Style::new().add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
86    ))
87}