use crossterm::event::{KeyCode, KeyModifiers};
use crate::keybindings::{get_keybindings, KeyCombo, KeybindingId};
use crate::theme::{theme, Color};
pub fn format_combo(combo: KeyCombo, capitalize: bool) -> String {
let _ = capitalize; let mut parts: Vec<String> = Vec::new();
let m = combo.modifiers;
let ctrl = m.contains(KeyModifiers::CONTROL);
let alt = m.contains(KeyModifiers::ALT);
let sup = m.contains(KeyModifiers::SUPER);
if ctrl {
parts.push(modifier_name("ctrl"));
}
if alt {
parts.push(modifier_name("alt"));
}
if sup {
parts.push(modifier_name("super"));
}
let key_name = key_code_name(&combo.code);
let shift_implicit = matches!(combo.code, KeyCode::Char(_)) && !ctrl && !alt;
if m.contains(KeyModifiers::SHIFT) && !shift_implicit {
parts.push(modifier_name("shift"));
}
parts.push(key_name);
parts.join("+")
}
fn modifier_name(name: &str) -> String {
let mut c = name.chars();
match c.next() {
Some(first) => first.to_uppercase().chain(c).collect(),
None => String::new(),
}
}
pub fn key_code_name(code: &KeyCode) -> String {
match code {
KeyCode::Char(c) => {
if c.is_ascii_alphabetic() {
c.to_ascii_uppercase().to_string()
} else {
c.to_string()
}
}
KeyCode::Enter => "Enter".to_string(),
KeyCode::Tab => "Tab".to_string(),
KeyCode::BackTab => "Shift+Tab".to_string(),
KeyCode::Esc => "Esc".to_string(),
KeyCode::Backspace => "Backspace".to_string(),
KeyCode::Delete => "Delete".to_string(),
KeyCode::Insert => "Insert".to_string(),
KeyCode::Home => "Home".to_string(),
KeyCode::End => "End".to_string(),
KeyCode::PageUp => "PageUp".to_string(),
KeyCode::PageDown => "PageDown".to_string(),
KeyCode::Left => "Left".to_string(),
KeyCode::Right => "Right".to_string(),
KeyCode::Up => "Up".to_string(),
KeyCode::Down => "Down".to_string(),
KeyCode::CapsLock => "CapsLock".to_string(),
KeyCode::ScrollLock => "ScrollLock".to_string(),
KeyCode::NumLock => "NumLock".to_string(),
KeyCode::PrintScreen => "PrintScreen".to_string(),
KeyCode::Pause => "Pause".to_string(),
KeyCode::Menu => "Menu".to_string(),
KeyCode::F(n) => format!("F{}", n),
KeyCode::Null => String::new(),
KeyCode::Media(_) => "Media".to_string(),
KeyCode::Modifier(_) => "Modifier".to_string(),
KeyCode::KeypadBegin => "KeypadBegin".to_string(),
}
}
pub fn key_text(id: KeybindingId) -> String {
let combos = get_keybindings().get_keys(id);
if combos.is_empty() {
return String::new();
}
combos
.iter()
.map(|c| format_combo(*c, false))
.collect::<Vec<_>>()
.join("/")
}
pub fn key_display_text(id: KeybindingId) -> String {
let combos = get_keybindings().get_keys(id);
if combos.is_empty() {
return String::new();
}
combos
.iter()
.map(|c| format_combo(*c, true))
.collect::<Vec<_>>()
.join("/")
}
pub fn key_hint(id: KeybindingId, description: &str) -> String {
let colors = theme().colors;
let keys = key_text(id);
hint_line(&keys, description, colors.muted, colors.muted)
}
pub fn raw_key_hint(key: &str, description: &str) -> String {
let colors = theme().colors;
hint_line(key, description, colors.muted, colors.muted)
}
fn hint_line(keys: &str, description: &str, key_color: Color, desc_color: Color) -> String {
format!("{} {}", key_color.fg(keys), desc_color.fg(description))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_text_submit_contains_enter() {
let text = key_text("tui.input.submit");
assert!(text.contains("Enter"), "got: {text}");
}
#[test]
fn test_key_text_newline_has_shift_and_ctrl() {
let text = key_text("tui.input.newLine");
assert!(text.contains("Enter") || text.contains("J"));
}
#[test]
fn test_key_text_unknown_id_empty() {
assert_eq!(key_text("tui.does.not.exist"), "");
}
#[test]
fn test_format_combo_ctrl_c() {
let combo = KeyCombo::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
assert_eq!(format_combo(combo, false), "Ctrl+C");
assert_eq!(format_combo(combo, true), "Ctrl+C");
}
#[test]
fn test_format_combo_shift_enter() {
let combo = KeyCombo::new(KeyCode::Enter, KeyModifiers::SHIFT);
assert_eq!(format_combo(combo, false), "Shift+Enter");
}
#[test]
fn test_key_code_name_special() {
assert_eq!(key_code_name(&KeyCode::Esc), "Esc");
assert_eq!(key_code_name(&KeyCode::F(5)), "F5");
assert_eq!(key_code_name(&KeyCode::Char('a')), "A");
assert_eq!(key_code_name(&KeyCode::Char('$')), "$");
}
}