Skip to main content

kimun_notes/components/
hints.rs

1//! The **hint registry** — the one place that turns a focus/cursor context
2//! into the key hints the status bar shows. Each surface still declares its
3//! own context hints (`hint_shortcuts`); this module owns the *global* hints
4//! (always-on actions, right-aligned on status line 1) and the shared `Hint`
5//! shape. The leader engine and which-key overlay (phases 05/06) will read
6//! from here so hint text never forks from the actual bindings.
7
8use crate::keys::KeyBindings;
9use crate::keys::action_shortcuts::ActionShortcuts;
10
11/// One key hint: `(key combo label, action label)`. An empty key renders the
12/// label alone, emphasized (used by the nvim backend's mode line).
13pub type Hint = (String, String);
14
15/// Build hints for `(action, label)` pairs, dropping actions with no binding.
16pub fn hints_for(kb: &KeyBindings, actions: &[(ActionShortcuts, &str)]) -> Vec<Hint> {
17    actions
18        .iter()
19        .filter_map(|(action, label)| {
20            kb.first_combo_for(action)
21                .map(|combo| (combo, label.to_string()))
22        })
23        .collect()
24}
25
26/// The always-on global hints, right-aligned on status line 1: the actions a
27/// user must always be able to find regardless of focus.
28pub fn global_hints(kb: &KeyBindings) -> Vec<Hint> {
29    hints_for(
30        kb,
31        &[
32            (ActionShortcuts::SearchNotes, "search"),
33            (ActionShortcuts::OpenPreferences, "prefs"),
34            (ActionShortcuts::Quit, "quit"),
35        ],
36    )
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn global_hints_resolve_from_default_bindings() {
45        let settings = crate::settings::AppSettings::default();
46        let hints = global_hints(&settings.key_bindings);
47        let labels: Vec<&str> = hints.iter().map(|(_, l)| l.as_str()).collect();
48        assert_eq!(labels, vec!["search", "prefs", "quit"]);
49        // Keys come from the real bindings, not hard-coded strings.
50        assert!(hints.iter().all(|(k, _)| !k.is_empty()));
51    }
52
53    #[test]
54    fn unbound_actions_are_dropped() {
55        let kb = KeyBindings::empty();
56        assert!(global_hints(&kb).is_empty());
57    }
58}