use std::fmt::Write;
pub const MODE_BOARD: &str = "board";
pub const MODE_DETAIL: &str = "detail";
pub const MODE_HELP: &str = "help";
pub const MODE_COMMENT: &str = "comment";
pub const MODE_ASSET: &str = "asset";
pub const MODE_FILTER: &str = "filter";
pub struct Cmd {
pub key: &'static str,
pub desc: &'static str,
pub modes: &'static [&'static str],
pub mode: &'static str,
}
pub const COMMANDS: &[Cmd] = &[
Cmd {
key: "j/k",
desc: "nav",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "n",
desc: "new",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "?",
desc: "help",
modes: &[MODE_BOARD, MODE_DETAIL],
mode: "board/detail",
},
Cmd {
key: "h/l",
desc: "column",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "enter",
desc: "open",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "d",
desc: "delete",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "< / >",
desc: "move",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "r",
desc: "refresh",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "/",
desc: "filter",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "f",
desc: "criteria",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "Tab",
desc: "switch",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "c",
desc: "toggle",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "s",
desc: "settings",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "b",
desc: "new board",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "e",
desc: "edit",
modes: &[MODE_BOARD, MODE_DETAIL],
mode: "board/detail",
},
Cmd {
key: "q",
desc: "quit",
modes: &[MODE_BOARD],
mode: MODE_BOARD,
},
Cmd {
key: "c",
desc: "comment",
modes: &[MODE_DETAIL],
mode: MODE_DETAIL,
},
Cmd {
key: "a",
desc: "asset",
modes: &[MODE_DETAIL],
mode: MODE_DETAIL,
},
Cmd {
key: "pgu/pgd",
desc: "scroll",
modes: &[MODE_DETAIL],
mode: MODE_DETAIL,
},
];
const MAX_HINT_CHARS: usize = 80;
pub fn hint_bar(active_modes: &[&str]) -> String {
let mut text = String::new();
for cmd in COMMANDS {
if !cmd.modes.iter().any(|m| active_modes.contains(m)) {
continue;
}
let _ = write!(text, "{} {} · ", cmd.key, cmd.desc);
}
let joined = text.trim_end_matches(" · ");
truncate(joined, MAX_HINT_CHARS)
}
pub fn help_overlay_list() -> Vec<(&'static str, &'static str, &'static str)> {
COMMANDS
.iter()
.map(|cmd| (cmd.key, cmd.desc, cmd.mode))
.collect()
}
fn truncate(s: &str, max_chars: usize) -> String {
if max_chars == 0 || s.chars().count() <= max_chars {
return s.to_string();
}
let take = max_chars.saturating_sub(1);
let head: String = s.chars().take(take).collect();
format!("{}…", head.trim_end())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hint_bar_contains_help_binding() {
let bar = hint_bar(&[MODE_BOARD]);
assert!(bar.contains("? help"), "got {bar:?}");
assert!(bar.contains("j/k nav"), "got {bar:?}");
}
#[test]
fn hint_bar_filters_by_active_mode() {
let board_bar = hint_bar(&[MODE_BOARD]);
assert!(board_bar.contains("n new"));
assert!(!board_bar.contains("c comment"), "got {board_bar:?}");
let detail_bar = hint_bar(&[MODE_DETAIL]);
assert!(detail_bar.contains("c comment"));
assert!(detail_bar.contains("a asset"));
assert!(!detail_bar.contains("n new"), "got {detail_bar:?}");
}
#[test]
fn hint_bar_truncates_overflow_with_ellipsis() {
let bar = hint_bar(&[MODE_BOARD]);
assert!(
bar.chars().count() <= MAX_HINT_CHARS,
"board hint ran {}-wide, max {MAX_HINT_CHARS}",
bar.chars().count()
);
assert!(
bar.ends_with('…'),
"a populated board hint must overflow and truncate: {bar:?}"
);
}
#[test]
fn hint_bar_unknown_mode_is_empty() {
assert_eq!(hint_bar(&["nope"]), "");
}
#[test]
fn overlay_lists_every_registry_entry_exactly_once() {
let rows = help_overlay_list();
assert_eq!(rows.len(), COMMANDS.len());
let mut pairs: Vec<(&str, &str)> = rows.iter().map(|(k, _, m)| (*k, *m)).collect();
pairs.sort_unstable();
pairs.dedup();
assert_eq!(
pairs.len(),
COMMANDS.len(),
"every (key, mode) pair must be unique"
);
for cmd in COMMANDS {
let found = rows.iter().any(|(k, _, m)| *k == cmd.key && *m == cmd.mode);
assert!(found, "overlay must list {} ({})", cmd.key, cmd.mode);
}
}
}