use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GitKeyAction {
Down,
Up,
Left,
Right,
GotoTop,
GotoBottom,
HunkNext,
HunkPrev,
FileNext,
FilePrev,
ViewMode(u8),
ToggleSidebar,
CycleWhitespace,
ToggleWrap,
Stage,
Unstage,
Commit,
Refresh,
Close,
}
pub fn match_git_key(key: &KeyEvent) -> Option<GitKeyAction> {
let alt = key.modifiers.contains(KeyModifiers::ALT);
if let KeyCode::Char(c) = key.code
&& !alt
&& !key.modifiers.contains(KeyModifiers::CONTROL)
{
match c {
'1' => return Some(GitKeyAction::ViewMode(1)),
'2' => return Some(GitKeyAction::ViewMode(2)),
'3' => return Some(GitKeyAction::ViewMode(3)),
'4' => return Some(GitKeyAction::ViewMode(4)),
_ => {}
}
}
if alt {
return match key.code {
KeyCode::Down => Some(GitKeyAction::HunkNext),
KeyCode::Up => Some(GitKeyAction::HunkPrev),
_ => None,
};
}
if key.modifiers.contains(KeyModifiers::CONTROL) {
return None;
}
match key.code {
KeyCode::Char('j') => Some(GitKeyAction::Down),
KeyCode::Char('k') => Some(GitKeyAction::Up),
KeyCode::Char('h') => Some(GitKeyAction::Left),
KeyCode::Char('l') => Some(GitKeyAction::Right),
KeyCode::Char('g') => Some(GitKeyAction::GotoTop),
KeyCode::Char('G') => Some(GitKeyAction::GotoBottom),
KeyCode::Char(']') => Some(GitKeyAction::FileNext),
KeyCode::Char('[') => Some(GitKeyAction::FilePrev),
KeyCode::Char('v') => Some(GitKeyAction::ToggleSidebar),
KeyCode::Char('b') => Some(GitKeyAction::CycleWhitespace),
KeyCode::Char('w') => Some(GitKeyAction::ToggleWrap),
KeyCode::Char('s') => Some(GitKeyAction::Stage),
KeyCode::Char('u') => Some(GitKeyAction::Unstage),
KeyCode::Char('c') => Some(GitKeyAction::Commit),
KeyCode::Char('r') => Some(GitKeyAction::Refresh),
KeyCode::Char('q') => Some(GitKeyAction::Close),
KeyCode::Esc => Some(GitKeyAction::Close),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn k(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn alt(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::ALT)
}
#[test]
fn vim_motions_map() {
assert_eq!(
match_git_key(&k(KeyCode::Char('j'))),
Some(GitKeyAction::Down)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('k'))),
Some(GitKeyAction::Up)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('h'))),
Some(GitKeyAction::Left)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('l'))),
Some(GitKeyAction::Right)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('g'))),
Some(GitKeyAction::GotoTop)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('G'))),
Some(GitKeyAction::GotoBottom)
);
assert_eq!(
match_git_key(&alt(KeyCode::Down)),
Some(GitKeyAction::HunkNext)
);
assert_eq!(
match_git_key(&alt(KeyCode::Up)),
Some(GitKeyAction::HunkPrev)
);
assert_eq!(
match_git_key(&k(KeyCode::Char(']'))),
Some(GitKeyAction::FileNext)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('['))),
Some(GitKeyAction::FilePrev)
);
}
#[test]
fn number_keys_select_view() {
assert_eq!(
match_git_key(&k(KeyCode::Char('1'))),
Some(GitKeyAction::ViewMode(1))
);
assert_eq!(
match_git_key(&k(KeyCode::Char('2'))),
Some(GitKeyAction::ViewMode(2))
);
assert_eq!(
match_git_key(&k(KeyCode::Char('3'))),
Some(GitKeyAction::ViewMode(3))
);
assert_eq!(
match_git_key(&k(KeyCode::Char('4'))),
Some(GitKeyAction::ViewMode(4))
);
assert_eq!(match_git_key(&k(KeyCode::Char('5'))), None);
assert_eq!(match_git_key(&k(KeyCode::Char('0'))), None);
}
#[test]
fn whitespace_and_wrap_toggles() {
assert_eq!(
match_git_key(&k(KeyCode::Char('b'))),
Some(GitKeyAction::CycleWhitespace)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('w'))),
Some(GitKeyAction::ToggleWrap)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('s'))),
Some(GitKeyAction::Stage)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('u'))),
Some(GitKeyAction::Unstage)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('c'))),
Some(GitKeyAction::Commit)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('r'))),
Some(GitKeyAction::Refresh)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('v'))),
Some(GitKeyAction::ToggleSidebar)
);
}
#[test]
fn close_keys() {
assert_eq!(
match_git_key(&k(KeyCode::Char('q'))),
Some(GitKeyAction::Close)
);
assert_eq!(match_git_key(&k(KeyCode::Esc)), Some(GitKeyAction::Close));
}
fn ctrl(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::CONTROL)
}
#[test]
fn ctrl_c_does_not_commit() {
assert_eq!(match_git_key(&ctrl(KeyCode::Char('c'))), None);
assert_eq!(
match_git_key(&k(KeyCode::Char('c'))),
Some(GitKeyAction::Commit)
);
}
#[test]
fn ctrl_s_does_not_stage() {
assert_eq!(match_git_key(&ctrl(KeyCode::Char('s'))), None);
assert_eq!(
match_git_key(&k(KeyCode::Char('s'))),
Some(GitKeyAction::Stage)
);
}
#[test]
fn ctrl_r_and_ctrl_q_do_not_hijack() {
assert_eq!(match_git_key(&ctrl(KeyCode::Char('r'))), None);
assert_eq!(match_git_key(&ctrl(KeyCode::Char('q'))), None);
assert_eq!(
match_git_key(&k(KeyCode::Char('r'))),
Some(GitKeyAction::Refresh)
);
assert_eq!(
match_git_key(&k(KeyCode::Char('q'))),
Some(GitKeyAction::Close)
);
}
}