#![allow(dead_code)]
mod actions;
mod binding;
mod presets;
pub use actions::Action;
pub use binding::KeyBinding;
pub use presets::KeymapPreset;
use crossterm::event::{KeyCode, KeyModifiers};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keymap {
#[serde(default)]
pub preset: KeymapPreset,
#[serde(default)]
pub overrides: Vec<KeyBinding>,
}
impl Default for Keymap {
fn default() -> Self {
Self {
preset: KeymapPreset::Standard,
overrides: Vec::new(),
}
}
}
impl Keymap {
#[must_use]
pub fn get_action(&self, code: KeyCode, modifiers: KeyModifiers) -> Option<Action> {
for binding in self.all_bindings() {
if binding.matches(code, modifiers) {
return Some(binding.action);
}
}
None
}
#[must_use]
pub fn all_bindings(&self) -> Vec<KeyBinding> {
let mut bindings = self.overrides.clone();
let preset_bindings = self.preset.bindings();
for preset_binding in preset_bindings {
let is_overridden = self
.overrides
.iter()
.any(|o| o.action == preset_binding.action);
if !is_overridden {
bindings.push(preset_binding);
}
}
bindings
}
#[must_use]
pub fn navigation_display(&self) -> String {
let up_key = self.get_key_display_for_action(Action::MoveUp);
let down_key = self.get_key_display_for_action(Action::MoveDown);
format!("{up_key}/{down_key}")
}
#[must_use]
pub fn quit_display(&self) -> String {
let quit_key = self.get_key_display_for_action(Action::Quit);
let cancel_key = self.get_key_display_for_action(Action::Cancel);
if quit_key == cancel_key {
quit_key
} else {
format!("{quit_key}/{cancel_key}")
}
}
#[must_use]
pub fn confirm_display(&self) -> String {
self.get_key_display_for_action(Action::Confirm)
}
#[must_use]
pub fn get_key_display_for_action(&self, action: Action) -> String {
if let Some(binding) = self.overrides.iter().find(|b| b.action == action) {
return binding.display();
}
if let Some(binding) = self
.preset
.bindings()
.into_iter()
.find(|b| b.action == action)
{
return binding.display();
}
format!("{action:?}")
}
#[must_use]
pub fn theme_key_hint(theme_type_name: &str) -> String {
format!("Theme: {theme_type_name} (t)")
}
#[must_use]
pub fn footer_navigation(&self, theme_type_name: &str) -> String {
format!(
"{}: Navigate | {}: Select | {}: Back | ?: Help | {}",
self.navigation_display(),
self.confirm_display(),
self.quit_display(),
Self::theme_key_hint(theme_type_name)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_keymap() {
let keymap = Keymap::default();
assert_eq!(keymap.preset, KeymapPreset::Standard);
assert!(keymap.overrides.is_empty());
}
#[test]
fn test_get_action_from_preset() {
let keymap = Keymap::default();
let action = keymap.get_action(KeyCode::Char('q'), KeyModifiers::NONE);
assert_eq!(action, Some(Action::Quit));
}
#[test]
fn test_override_takes_precedence() {
let keymap = Keymap {
preset: KeymapPreset::Standard,
overrides: vec![KeyBinding::new("q", Action::Help)],
};
let action = keymap.get_action(KeyCode::Char('q'), KeyModifiers::NONE);
assert_eq!(action, Some(Action::Help));
}
#[test]
fn test_vim_preset() {
let keymap = Keymap {
preset: KeymapPreset::Vim,
overrides: Vec::new(),
};
let action = keymap.get_action(KeyCode::Char('j'), KeyModifiers::NONE);
assert_eq!(action, Some(Action::MoveDown));
}
}