use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action {
Quit,
Dismiss,
MoveUp,
MoveDown,
MoveTop,
MoveBottom,
PageUp,
PageDown,
OpenDetail,
NewVar,
EditVar,
DeleteVar,
LinkVar,
CopyValue,
ViewValue,
ToggleSecretVisibility,
RunInProject,
StartFuzzy,
SwitchProfile,
NextView,
ToggleHelp,
Refresh,
Noop,
}
impl Action {
#[must_use]
pub fn from_key(key: KeyEvent) -> Self {
if key.kind != KeyEventKind::Press {
return Self::Noop;
}
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
match key.code {
KeyCode::Char('c') if ctrl => Self::Quit,
KeyCode::Char('q') => Self::Quit,
KeyCode::Esc => Self::Dismiss,
KeyCode::Char('j') | KeyCode::Down => Self::MoveDown,
KeyCode::Char('k') | KeyCode::Up => Self::MoveUp,
KeyCode::Char('g') => Self::MoveTop,
KeyCode::Char('G') => Self::MoveBottom,
KeyCode::PageDown => Self::PageDown,
KeyCode::PageUp => Self::PageUp,
KeyCode::Enter => Self::OpenDetail,
KeyCode::Char('n') => Self::NewVar,
KeyCode::Char('e') => Self::EditVar,
KeyCode::Char('d') => Self::DeleteVar,
KeyCode::Char('l') => Self::LinkVar,
KeyCode::Char('y') => Self::CopyValue,
KeyCode::Char('v') => Self::ViewValue,
KeyCode::Char('s') => Self::ToggleSecretVisibility,
KeyCode::Char('R') => Self::RunInProject,
KeyCode::Char('f') if ctrl => Self::StartFuzzy,
KeyCode::Char('p') => Self::SwitchProfile,
KeyCode::Tab => Self::NextView,
KeyCode::Char('?') => Self::ToggleHelp,
KeyCode::Char('r') => Self::Refresh,
_ => Self::Noop,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn press(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn release(code: KeyCode) -> KeyEvent {
let mut e = KeyEvent::new(code, KeyModifiers::NONE);
e.kind = KeyEventKind::Release;
e
}
#[test]
fn release_events_are_dropped() {
assert_eq!(Action::from_key(release(KeyCode::Char('q'))), Action::Noop);
assert_eq!(Action::from_key(release(KeyCode::Down)), Action::Noop);
}
#[test]
fn basic_navigation_keys() {
assert_eq!(
Action::from_key(press(KeyCode::Char('j'))),
Action::MoveDown
);
assert_eq!(Action::from_key(press(KeyCode::Down)), Action::MoveDown);
assert_eq!(Action::from_key(press(KeyCode::Char('k'))), Action::MoveUp);
assert_eq!(Action::from_key(press(KeyCode::Up)), Action::MoveUp);
assert_eq!(Action::from_key(press(KeyCode::Char('g'))), Action::MoveTop);
assert_eq!(
Action::from_key(press(KeyCode::Char('G'))),
Action::MoveBottom
);
}
#[test]
fn quit_and_dismiss_are_distinct() {
assert_eq!(Action::from_key(press(KeyCode::Char('q'))), Action::Quit);
assert_eq!(Action::from_key(press(KeyCode::Esc)), Action::Dismiss);
}
#[test]
fn ctrl_f_starts_fuzzy_search() {
let ctrl_f = KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL);
assert_eq!(Action::from_key(ctrl_f), Action::StartFuzzy);
assert_eq!(Action::from_key(press(KeyCode::Char('f'))), Action::Noop);
}
#[test]
fn ctrl_c_quits() {
let ctrl_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
assert_eq!(Action::from_key(ctrl_c), Action::Quit);
assert_eq!(Action::from_key(press(KeyCode::Char('c'))), Action::Noop);
}
#[test]
fn shift_r_is_run_in_project_lowercase_r_is_refresh() {
assert_eq!(Action::from_key(press(KeyCode::Char('r'))), Action::Refresh);
assert_eq!(
Action::from_key(press(KeyCode::Char('R'))),
Action::RunInProject
);
}
#[test]
fn unknown_key_is_noop() {
assert_eq!(Action::from_key(press(KeyCode::Char('z'))), Action::Noop);
assert_eq!(Action::from_key(press(KeyCode::F(7))), Action::Noop);
}
}