git-file-history 0.1.0

TUI for browsing the Git history of a single file
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use crate::app::Action;

pub(super) fn action_for_key(key: KeyEvent) -> Option<Action> {
    if key.modifiers.contains(KeyModifiers::CONTROL) && matches!(key.code, KeyCode::Char('c')) {
        return Some(Action::Quit);
    }

    match key.code {
        KeyCode::Char('?') | KeyCode::F(1) => Some(Action::Help),
        KeyCode::Char('q') | KeyCode::Esc | KeyCode::Left => Some(Action::Cancel),
        KeyCode::Down if key.modifiers.contains(KeyModifiers::SHIFT) => Some(Action::NextCommit),
        KeyCode::Up if key.modifiers.contains(KeyModifiers::SHIFT) => Some(Action::PreviousCommit),
        KeyCode::Char('j') | KeyCode::Down => Some(Action::Down),
        KeyCode::Char('k') | KeyCode::Up => Some(Action::Up),
        KeyCode::Enter | KeyCode::Right => Some(Action::Open),
        KeyCode::Char('J') => Some(Action::NextCommit),
        KeyCode::Char('K') => Some(Action::PreviousCommit),
        KeyCode::Char('g') | KeyCode::Home => Some(Action::Top),
        KeyCode::Char('G') | KeyCode::End => Some(Action::Bottom),
        KeyCode::PageDown => Some(Action::PageDown),
        KeyCode::PageUp => Some(Action::PageUp),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn key(code: KeyCode) -> KeyEvent {
        KeyEvent::new(code, KeyModifiers::NONE)
    }

    fn shifted_key(code: KeyCode) -> KeyEvent {
        KeyEvent::new(code, KeyModifiers::SHIFT)
    }

    fn control_key(code: KeyCode) -> KeyEvent {
        KeyEvent::new(code, KeyModifiers::CONTROL)
    }

    #[test]
    fn arrow_keys_map_to_navigation_actions() {
        assert_eq!(action_for_key(key(KeyCode::Left)), Some(Action::Cancel));
        assert_eq!(action_for_key(key(KeyCode::Right)), Some(Action::Open));
        assert_eq!(action_for_key(key(KeyCode::Down)), Some(Action::Down));
        assert_eq!(action_for_key(key(KeyCode::Up)), Some(Action::Up));
    }

    #[test]
    fn documented_keys_map_to_expected_actions() {
        assert_eq!(action_for_key(key(KeyCode::Enter)), Some(Action::Open));
        assert_eq!(action_for_key(key(KeyCode::Right)), Some(Action::Open));
        assert_eq!(action_for_key(key(KeyCode::Left)), Some(Action::Cancel));
        assert_eq!(action_for_key(key(KeyCode::Esc)), Some(Action::Cancel));
        assert_eq!(
            action_for_key(key(KeyCode::Char('q'))),
            Some(Action::Cancel)
        );
        assert_eq!(action_for_key(key(KeyCode::Char('j'))), Some(Action::Down));
        assert_eq!(action_for_key(key(KeyCode::Char('k'))), Some(Action::Up));
        assert_eq!(action_for_key(key(KeyCode::Down)), Some(Action::Down));
        assert_eq!(action_for_key(key(KeyCode::Up)), Some(Action::Up));
        assert_eq!(
            action_for_key(key(KeyCode::Char('J'))),
            Some(Action::NextCommit)
        );
        assert_eq!(
            action_for_key(shifted_key(KeyCode::Down)),
            Some(Action::NextCommit)
        );
        assert_eq!(
            action_for_key(key(KeyCode::Char('K'))),
            Some(Action::PreviousCommit)
        );
        assert_eq!(
            action_for_key(shifted_key(KeyCode::Up)),
            Some(Action::PreviousCommit)
        );
        assert_eq!(action_for_key(key(KeyCode::Char('g'))), Some(Action::Top));
        assert_eq!(action_for_key(key(KeyCode::Home)), Some(Action::Top));
        assert_eq!(
            action_for_key(key(KeyCode::Char('G'))),
            Some(Action::Bottom)
        );
        assert_eq!(action_for_key(key(KeyCode::End)), Some(Action::Bottom));
        assert_eq!(
            action_for_key(key(KeyCode::PageDown)),
            Some(Action::PageDown)
        );
        assert_eq!(action_for_key(key(KeyCode::PageUp)), Some(Action::PageUp));
        assert_eq!(action_for_key(key(KeyCode::Char('?'))), Some(Action::Help));
        assert_eq!(action_for_key(key(KeyCode::F(1))), Some(Action::Help));
        assert_eq!(
            action_for_key(control_key(KeyCode::Char('c'))),
            Some(Action::Quit)
        );
    }

    #[test]
    fn shifted_arrows_map_to_commit_switching() {
        assert_eq!(
            action_for_key(shifted_key(KeyCode::Down)),
            Some(Action::NextCommit)
        );
        assert_eq!(
            action_for_key(shifted_key(KeyCode::Up)),
            Some(Action::PreviousCommit)
        );
    }

    #[test]
    fn help_keys_map_to_help_action() {
        assert_eq!(action_for_key(key(KeyCode::Char('?'))), Some(Action::Help));
        assert_eq!(action_for_key(key(KeyCode::F(1))), Some(Action::Help));
        assert_eq!(action_for_key(key(KeyCode::Char('h'))), None);
    }
}