use std::fmt;
use std::time::Duration;
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::View;
use crate::audio::Mode;
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
Quit,
Help,
CommandHelp,
ShowView(View),
NextView,
Cursor(i64),
CursorFirst,
CursorLast,
StartSearch,
Search(String),
ClearSearch,
StartCommand,
Activate,
Add,
Remove,
MoveTrack(i64),
ClearSelection,
StartSave,
SaveAs(String),
DeletePlaylist,
StartRename,
RenameTo(String),
PlayPlaylist(String),
TogglePause,
Next,
Prev,
Stop,
SeekBy(i64),
SeekTo(Duration),
VolumeBy(f32),
SetVolume(f32),
SpeedBy(i32),
SetSpeed(i32),
CycleMode(bool),
SetMode(Mode),
Mark,
MarkAt(Duration),
UndoMark,
ClearMarks,
NextMark,
PrevMark,
Map {
view: Option<View>,
key: Key,
action: Option<Box<Action>>,
},
Unmap {
view: Option<View>,
key: Key,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Key {
code: KeyCode,
mods: KeyModifiers,
}
const KEY_NAMES: &[(&str, KeyCode)] = &[
("space", KeyCode::Char(' ')),
("enter", KeyCode::Enter),
("esc", KeyCode::Esc),
("tab", KeyCode::Tab),
("backtab", KeyCode::BackTab),
("backspace", KeyCode::Backspace),
("delete", KeyCode::Delete),
("insert", KeyCode::Insert),
("up", KeyCode::Up),
("down", KeyCode::Down),
("left", KeyCode::Left),
("right", KeyCode::Right),
("home", KeyCode::Home),
("end", KeyCode::End),
("pageup", KeyCode::PageUp),
("pagedown", KeyCode::PageDown),
];
const MODIFIERS: &[(&str, KeyModifiers)] = &[
("ctrl-", KeyModifiers::CONTROL),
("alt-", KeyModifiers::ALT),
("shift-", KeyModifiers::SHIFT),
];
impl Key {
fn new(code: KeyCode, mods: KeyModifiers) -> Self {
let mut mods = mods & (KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT);
let code = match code {
KeyCode::Char(c) => {
mods.remove(KeyModifiers::SHIFT);
if mods.contains(KeyModifiers::CONTROL) {
KeyCode::Char(c.to_ascii_lowercase())
} else {
code
}
}
KeyCode::BackTab => {
mods.remove(KeyModifiers::SHIFT);
code
}
_ => code,
};
Key { code, mods }
}
pub fn parse(text: &str) -> Result<Key, String> {
let mut rest = text;
let mut mods = KeyModifiers::NONE;
'prefixes: loop {
for (prefix, m) in MODIFIERS {
if let Some(r) = rest.strip_prefix(prefix).filter(|r| !r.is_empty()) {
mods |= *m;
rest = r;
continue 'prefixes;
}
}
break;
}
let mut chars = rest.chars();
let code = match (chars.next(), chars.next()) {
(Some(c), None) => KeyCode::Char(c),
_ => {
let lower = rest.to_ascii_lowercase();
let f = lower
.strip_prefix('f')
.and_then(|n| n.parse::<u8>().ok())
.filter(|n| (1..=12).contains(n));
match (KEY_NAMES.iter().find(|(n, _)| *n == lower), f) {
(Some((_, code)), _) => *code,
(None, Some(n)) => KeyCode::F(n),
(None, None) => return Err(format!("not a key: {text}")),
}
}
};
Ok(Key::new(code, mods))
}
}
impl From<&KeyEvent> for Key {
fn from(event: &KeyEvent) -> Self {
Key::new(event.code, event.modifiers)
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (prefix, m) in MODIFIERS {
if self.mods.contains(*m) {
f.write_str(prefix)?;
}
}
if let Some((name, _)) = KEY_NAMES.iter().find(|(_, c)| *c == self.code) {
return f.write_str(name);
}
match self.code {
KeyCode::Char(c) => write!(f, "{c}"),
KeyCode::F(n) => write!(f, "f{n}"),
other => write!(f, "{other:?}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Binding {
pub view: Option<View>,
pub key: Key,
pub action: Option<Action>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Keymap {
bindings: Vec<Binding>,
}
impl Default for Keymap {
fn default() -> Self {
super::config::Config::default().keys
}
}
impl Keymap {
pub fn empty() -> Self {
Keymap {
bindings: Vec::new(),
}
}
pub fn lookup(&self, key: Key, view: View) -> Option<&Action> {
let find = |v| self.bindings.iter().find(|b| b.key == key && b.view == v);
find(Some(view)).or_else(|| find(None))?.action.as_ref()
}
pub fn bind(&mut self, view: Option<View>, key: Key, action: Option<Action>) {
let binding = Binding { view, key, action };
match self
.bindings
.iter_mut()
.find(|b| b.key == key && b.view == view)
{
Some(old) => *old = binding,
None => self.bindings.push(binding),
}
}
pub fn unbind(&mut self, view: Option<View>, key: Key) -> bool {
let before = self.bindings.len();
self.bindings.retain(|b| !(b.key == key && b.view == view));
self.bindings.len() < before
}
pub fn bindings(&self) -> &[Binding] {
&self.bindings
}
}