use ahash::AHashMap;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::config::KeybindingsFromToml;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UserAction {
Left,
Right,
Up,
Down,
BigUp,
BigDown,
PageUp,
PageDown,
GoTop,
GoBot,
AddFeed,
Sync,
SyncAll,
Play,
MarkPlayed,
MarkAllPlayed,
Download,
DownloadAll,
Delete,
DeleteAll,
Remove,
RemoveAll,
FilterPlayed,
FilterDownloaded,
Help,
Quit,
}
#[derive(Debug, Clone)]
pub struct Keybindings(AHashMap<String, UserAction>);
impl Keybindings {
pub fn new() -> Self {
return Self(AHashMap::new());
}
pub fn default() -> Self {
let defaults = Self::_defaults();
let mut keymap = Self::new();
for (action, defaults) in defaults.into_iter() {
keymap.insert_from_vec(defaults, action);
}
return keymap;
}
pub fn from_config(config: KeybindingsFromToml) -> Self {
let config_actions: Vec<(Option<Vec<String>>, UserAction)> = vec![
(config.left, UserAction::Left),
(config.right, UserAction::Right),
(config.up, UserAction::Up),
(config.down, UserAction::Down),
(config.big_up, UserAction::BigUp),
(config.big_down, UserAction::BigDown),
(config.page_up, UserAction::PageUp),
(config.page_down, UserAction::PageDown),
(config.go_top, UserAction::GoTop),
(config.go_bot, UserAction::GoBot),
(config.add_feed, UserAction::AddFeed),
(config.sync, UserAction::Sync),
(config.sync_all, UserAction::SyncAll),
(config.play, UserAction::Play),
(config.mark_played, UserAction::MarkPlayed),
(config.mark_all_played, UserAction::MarkAllPlayed),
(config.download, UserAction::Download),
(config.download_all, UserAction::DownloadAll),
(config.delete, UserAction::Delete),
(config.delete_all, UserAction::DeleteAll),
(config.remove, UserAction::Remove),
(config.remove_all, UserAction::RemoveAll),
(config.filter_played, UserAction::FilterPlayed),
(config.filter_downloaded, UserAction::FilterDownloaded),
(config.help, UserAction::Help),
(config.quit, UserAction::Quit),
];
let mut keymap = Self::default();
for (config, action) in config_actions.into_iter() {
if let Some(config) = config {
keymap.insert_from_vec(config, action);
}
}
return keymap;
}
pub fn get_from_input(&self, input: KeyEvent) -> Option<&UserAction> {
match input_to_str(input) {
Some(code) => self.0.get(&code),
None => None,
}
}
pub fn insert(&mut self, code: String, action: UserAction) {
self.0.insert(code, action);
}
pub fn insert_from_vec(&mut self, vec: Vec<String>, action: UserAction) {
for key in vec.into_iter() {
self.insert(key, action);
}
}
pub fn keys_for_action(&self, action: UserAction) -> Vec<String> {
return self
.0
.iter()
.filter_map(|(key, &val)| {
if val == action {
Some(key.clone())
} else {
None
}
})
.collect();
}
fn _defaults() -> Vec<(UserAction, Vec<String>)> {
return vec![
(UserAction::Left, vec!["Left".to_string(), "h".to_string()]),
(UserAction::Right, vec![
"Right".to_string(),
"l".to_string(),
]),
(UserAction::Up, vec!["Up".to_string(), "k".to_string()]),
(UserAction::Down, vec!["Down".to_string(), "j".to_string()]),
(UserAction::BigUp, vec!["K".to_string()]),
(UserAction::BigDown, vec!["J".to_string()]),
(UserAction::PageUp, vec!["PgUp".to_string()]),
(UserAction::PageDown, vec!["PgDn".to_string()]),
(UserAction::GoTop, vec!["g".to_string()]),
(UserAction::GoBot, vec!["G".to_string()]),
(UserAction::AddFeed, vec!["a".to_string()]),
(UserAction::Sync, vec!["s".to_string()]),
(UserAction::SyncAll, vec!["S".to_string()]),
(UserAction::Play, vec!["Enter".to_string(), "p".to_string()]),
(UserAction::MarkPlayed, vec!["m".to_string()]),
(UserAction::MarkAllPlayed, vec!["M".to_string()]),
(UserAction::Download, vec!["d".to_string()]),
(UserAction::DownloadAll, vec!["D".to_string()]),
(UserAction::Delete, vec!["x".to_string()]),
(UserAction::DeleteAll, vec!["X".to_string()]),
(UserAction::Remove, vec!["r".to_string()]),
(UserAction::RemoveAll, vec!["R".to_string()]),
(UserAction::FilterPlayed, vec!["1".to_string()]),
(UserAction::FilterDownloaded, vec!["2".to_string()]),
(UserAction::Help, vec!["?".to_string()]),
(UserAction::Quit, vec!["q".to_string()]),
];
}
}
pub fn input_to_str(input: KeyEvent) -> Option<String> {
let ctrl = if input.modifiers.intersects(KeyModifiers::CONTROL) {
"Ctrl+"
} else {
""
};
let alt = if input.modifiers.intersects(KeyModifiers::ALT) {
"Alt+"
} else {
""
};
let shift = if input.modifiers.intersects(KeyModifiers::SHIFT) {
"Shift+"
} else {
""
};
let mut tmp = [0; 4];
return match input.code {
KeyCode::Backspace => Some(format!("{ctrl}{alt}{shift}Backspace")),
KeyCode::Enter => Some(format!("{ctrl}{alt}{shift}Enter")),
KeyCode::Left => Some(format!("{ctrl}{alt}{shift}Left")),
KeyCode::Right => Some(format!("{ctrl}{alt}{shift}Right")),
KeyCode::Up => Some(format!("{ctrl}{alt}{shift}Up")),
KeyCode::Down => Some(format!("{ctrl}{alt}{shift}Down")),
KeyCode::Home => Some(format!("{ctrl}{alt}{shift}Home")),
KeyCode::End => Some(format!("{ctrl}{alt}{shift}End")),
KeyCode::PageUp => Some(format!("{ctrl}{alt}{shift}PgUp")),
KeyCode::PageDown => Some(format!("{ctrl}{alt}{shift}PgDn")),
KeyCode::Tab => Some(format!("{ctrl}{alt}{shift}Tab")),
KeyCode::BackTab => Some(format!("{ctrl}{alt}{shift}Tab")),
KeyCode::Delete => Some(format!("{ctrl}{alt}{shift}Del")),
KeyCode::Insert => Some(format!("{ctrl}{alt}{shift}Ins")),
KeyCode::Esc => Some(format!("{ctrl}{alt}{shift}Esc")),
KeyCode::F(num) => Some(format!("{ctrl}{alt}{shift}F{num}")), KeyCode::Char(c) => {
if c == '\u{7f}' {
Some(format!("{ctrl}{alt}{shift}Backspace"))
} else if c == '\u{1b}' {
Some(format!("{ctrl}{alt}{shift}Esc"))
} else if c == '\n' {
Some(format!("{ctrl}{alt}{shift}Enter"))
} else if c == '\t' {
Some(format!("{ctrl}{alt}{shift}Tab"))
} else {
Some(format!("{}{}{}", ctrl, alt, c.encode_utf8(&mut tmp)))
}
}
_ => None,
};
}