#[derive(Debug, Clone)]
pub struct ViewportKeyMap {
pub up: Vec<KeyBinding>,
pub down: Vec<KeyBinding>,
pub page_up: Vec<KeyBinding>,
pub page_down: Vec<KeyBinding>,
pub half_page_up: Vec<KeyBinding>,
pub half_page_down: Vec<KeyBinding>,
pub goto_top: Vec<KeyBinding>,
pub goto_bottom: Vec<KeyBinding>,
pub left: Vec<KeyBinding>,
pub right: Vec<KeyBinding>,
pub goto_left: Vec<KeyBinding>,
pub goto_right: Vec<KeyBinding>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyBinding {
pub key: KeyType,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyType {
Char(char),
Up,
Down,
Left,
Right,
PageUp,
PageDown,
Home,
End,
Space,
Enter,
Escape,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Modifiers {
pub const NONE: Self = Self {
ctrl: false,
alt: false,
shift: false,
};
pub const CTRL: Self = Self {
ctrl: true,
alt: false,
shift: false,
};
pub const ALT: Self = Self {
ctrl: false,
alt: true,
shift: false,
};
pub const SHIFT: Self = Self {
ctrl: false,
alt: false,
shift: true,
};
}
impl KeyBinding {
pub fn new(key: KeyType, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
pub fn char(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::NONE)
}
pub fn ctrl(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::CTRL)
}
pub fn special(key: KeyType) -> Self {
Self::new(key, Modifiers::NONE)
}
pub fn matches(&self, input: &str, key: &crate::hooks::Key) -> bool {
if self.modifiers.ctrl != key.ctrl
|| self.modifiers.alt != key.alt
|| self.modifiers.shift != key.shift
{
return false;
}
match &self.key {
KeyType::Char(c) => {
if input.len() == 1 {
input.starts_with(*c)
} else {
false
}
}
KeyType::Up => key.up_arrow,
KeyType::Down => key.down_arrow,
KeyType::Left => key.left_arrow,
KeyType::Right => key.right_arrow,
KeyType::PageUp => key.page_up,
KeyType::PageDown => key.page_down,
KeyType::Home => key.home,
KeyType::End => key.end,
KeyType::Space => key.space,
KeyType::Enter => key.return_key,
KeyType::Escape => key.escape,
}
}
}
impl Default for ViewportKeyMap {
fn default() -> Self {
Self {
up: vec![KeyBinding::special(KeyType::Up), KeyBinding::char('k')],
down: vec![KeyBinding::special(KeyType::Down), KeyBinding::char('j')],
page_up: vec![KeyBinding::special(KeyType::PageUp), KeyBinding::ctrl('b')],
page_down: vec![
KeyBinding::special(KeyType::PageDown),
KeyBinding::ctrl('f'),
KeyBinding::special(KeyType::Space),
],
half_page_up: vec![KeyBinding::ctrl('u')],
half_page_down: vec![KeyBinding::ctrl('d')],
goto_top: vec![KeyBinding::special(KeyType::Home), KeyBinding::char('g')],
goto_bottom: vec![KeyBinding::special(KeyType::End), KeyBinding::char('G')],
left: vec![KeyBinding::special(KeyType::Left), KeyBinding::char('h')],
right: vec![KeyBinding::special(KeyType::Right), KeyBinding::char('l')],
goto_left: vec![KeyBinding::char('0')],
goto_right: vec![KeyBinding::char('$')],
}
}
}
impl ViewportKeyMap {
pub fn new() -> Self {
Self::default()
}
pub fn arrows_only() -> Self {
Self {
up: vec![KeyBinding::special(KeyType::Up)],
down: vec![KeyBinding::special(KeyType::Down)],
page_up: vec![KeyBinding::special(KeyType::PageUp)],
page_down: vec![KeyBinding::special(KeyType::PageDown)],
half_page_up: vec![],
half_page_down: vec![],
goto_top: vec![KeyBinding::special(KeyType::Home)],
goto_bottom: vec![KeyBinding::special(KeyType::End)],
left: vec![KeyBinding::special(KeyType::Left)],
right: vec![KeyBinding::special(KeyType::Right)],
goto_left: vec![],
goto_right: vec![],
}
}
pub fn vim() -> Self {
Self {
up: vec![KeyBinding::char('k')],
down: vec![KeyBinding::char('j')],
page_up: vec![KeyBinding::ctrl('b')],
page_down: vec![KeyBinding::ctrl('f')],
half_page_up: vec![KeyBinding::ctrl('u')],
half_page_down: vec![KeyBinding::ctrl('d')],
goto_top: vec![KeyBinding::char('g')],
goto_bottom: vec![KeyBinding::char('G')],
left: vec![KeyBinding::char('h')],
right: vec![KeyBinding::char('l')],
goto_left: vec![KeyBinding::char('0')],
goto_right: vec![KeyBinding::char('$')],
}
}
pub fn disabled() -> Self {
Self {
up: vec![],
down: vec![],
page_up: vec![],
page_down: vec![],
half_page_up: vec![],
half_page_down: vec![],
goto_top: vec![],
goto_bottom: vec![],
left: vec![],
right: vec![],
goto_left: vec![],
goto_right: vec![],
}
}
pub fn match_action(&self, input: &str, key: &crate::hooks::Key) -> Option<ViewportAction> {
for binding in &self.up {
if binding.matches(input, key) {
return Some(ViewportAction::ScrollUp);
}
}
for binding in &self.down {
if binding.matches(input, key) {
return Some(ViewportAction::ScrollDown);
}
}
for binding in &self.page_up {
if binding.matches(input, key) {
return Some(ViewportAction::PageUp);
}
}
for binding in &self.page_down {
if binding.matches(input, key) {
return Some(ViewportAction::PageDown);
}
}
for binding in &self.half_page_up {
if binding.matches(input, key) {
return Some(ViewportAction::HalfPageUp);
}
}
for binding in &self.half_page_down {
if binding.matches(input, key) {
return Some(ViewportAction::HalfPageDown);
}
}
for binding in &self.goto_top {
if binding.matches(input, key) {
return Some(ViewportAction::GotoTop);
}
}
for binding in &self.goto_bottom {
if binding.matches(input, key) {
return Some(ViewportAction::GotoBottom);
}
}
for binding in &self.left {
if binding.matches(input, key) {
return Some(ViewportAction::ScrollLeft);
}
}
for binding in &self.right {
if binding.matches(input, key) {
return Some(ViewportAction::ScrollRight);
}
}
for binding in &self.goto_left {
if binding.matches(input, key) {
return Some(ViewportAction::GotoLeft);
}
}
for binding in &self.goto_right {
if binding.matches(input, key) {
return Some(ViewportAction::GotoRight);
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewportAction {
ScrollUp,
ScrollDown,
PageUp,
PageDown,
HalfPageUp,
HalfPageDown,
GotoTop,
GotoBottom,
ScrollLeft,
ScrollRight,
GotoLeft,
GotoRight,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_keymap() {
let keymap = ViewportKeyMap::default();
assert!(!keymap.up.is_empty());
assert!(!keymap.down.is_empty());
}
#[test]
fn test_vim_keymap() {
let keymap = ViewportKeyMap::vim();
assert!(keymap.up.iter().any(|b| b.key == KeyType::Char('k')));
assert!(keymap.down.iter().any(|b| b.key == KeyType::Char('j')));
}
#[test]
fn test_disabled_keymap() {
let keymap = ViewportKeyMap::disabled();
assert!(keymap.up.is_empty());
assert!(keymap.down.is_empty());
}
}