use crate::ui::input::actions::{Action, NavigateAction};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub struct NavigationHandler;
impl Default for NavigationHandler {
fn default() -> Self {
Self::new()
}
}
impl NavigationHandler {
#[must_use]
pub fn new() -> Self {
NavigationHandler
}
#[must_use]
pub fn handle_key(&self, key: KeyEvent, mode: &crate::buffer::AppMode) -> Option<Action> {
use crate::buffer::AppMode;
if !matches!(mode, AppMode::Results) {
return None;
}
match key.code {
KeyCode::Char('h') if !key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::Navigate(NavigateAction::Left(1)))
}
KeyCode::Char('j') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::Navigate(NavigateAction::Down(1)))
}
KeyCode::Char('k') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::Navigate(NavigateAction::Up(1)))
}
KeyCode::Char('l') => Some(Action::Navigate(NavigateAction::Right(1))),
KeyCode::Up if !key.modifiers.contains(KeyModifiers::ALT) => {
Some(Action::Navigate(NavigateAction::Up(1)))
}
KeyCode::Down if !key.modifiers.contains(KeyModifiers::ALT) => {
Some(Action::Navigate(NavigateAction::Down(1)))
}
KeyCode::Left
if !key.modifiers.contains(KeyModifiers::SHIFT)
&& !key.modifiers.contains(KeyModifiers::CONTROL) =>
{
Some(Action::Navigate(NavigateAction::Left(1)))
}
KeyCode::Right
if !key.modifiers.contains(KeyModifiers::SHIFT)
&& !key.modifiers.contains(KeyModifiers::CONTROL) =>
{
Some(Action::Navigate(NavigateAction::Right(1)))
}
KeyCode::PageUp => Some(Action::Navigate(NavigateAction::PageUp)),
KeyCode::PageDown => Some(Action::Navigate(NavigateAction::PageDown)),
KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::Navigate(NavigateAction::PageDown))
}
KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::Navigate(NavigateAction::PageUp))
}
KeyCode::Home if !key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::Navigate(NavigateAction::Home))
}
KeyCode::End if !key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::Navigate(NavigateAction::End))
}
KeyCode::Char('g') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::Navigate(NavigateAction::Home))
}
KeyCode::Char('G') => Some(Action::Navigate(NavigateAction::End)),
KeyCode::Char('H') if key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::NavigateToViewportTop)
}
KeyCode::Char('M') => Some(Action::NavigateToViewportMiddle),
KeyCode::Char('L') if key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::NavigateToViewportBottom)
}
KeyCode::Char('^') => Some(Action::Navigate(NavigateAction::FirstColumn)),
KeyCode::Char('$') => Some(Action::Navigate(NavigateAction::LastColumn)),
KeyCode::Tab if !key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::NextColumn)
}
KeyCode::BackTab | KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => {
Some(Action::PreviousColumn)
}
KeyCode::Char('x') => Some(Action::ToggleCursorLock),
KeyCode::Char(' ') if key.modifiers.contains(KeyModifiers::CONTROL) => {
Some(Action::ToggleViewportLock)
}
_ => None,
}
}
#[must_use]
pub fn is_navigation_key(&self, key: &KeyEvent, mode: &crate::buffer::AppMode) -> bool {
self.handle_key(*key, mode).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::buffer::AppMode;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[test]
fn test_vim_navigation() {
let handler = NavigationHandler::new();
let mode = AppMode::Results;
let h_key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
assert_eq!(
handler.handle_key(h_key, &mode),
Some(Action::Navigate(NavigateAction::Left(1)))
);
let j_key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty());
assert_eq!(
handler.handle_key(j_key, &mode),
Some(Action::Navigate(NavigateAction::Down(1)))
);
let k_key = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty());
assert_eq!(
handler.handle_key(k_key, &mode),
Some(Action::Navigate(NavigateAction::Up(1)))
);
let l_key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
assert_eq!(
handler.handle_key(l_key, &mode),
Some(Action::Navigate(NavigateAction::Right(1)))
);
}
#[test]
fn test_arrow_keys() {
let handler = NavigationHandler::new();
let mode = AppMode::Results;
let up_key = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
assert_eq!(
handler.handle_key(up_key, &mode),
Some(Action::Navigate(NavigateAction::Up(1)))
);
let down_key = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
assert_eq!(
handler.handle_key(down_key, &mode),
Some(Action::Navigate(NavigateAction::Down(1)))
);
}
#[test]
fn test_page_navigation() {
let handler = NavigationHandler::new();
let mode = AppMode::Results;
let pageup_key = KeyEvent::new(KeyCode::PageUp, KeyModifiers::empty());
assert_eq!(
handler.handle_key(pageup_key, &mode),
Some(Action::Navigate(NavigateAction::PageUp))
);
let pagedown_key = KeyEvent::new(KeyCode::PageDown, KeyModifiers::empty());
assert_eq!(
handler.handle_key(pagedown_key, &mode),
Some(Action::Navigate(NavigateAction::PageDown))
);
}
#[test]
fn test_not_in_results_mode() {
let handler = NavigationHandler::new();
let mode = AppMode::Command;
let h_key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
assert_eq!(handler.handle_key(h_key, &mode), None);
}
}