use crate::Buffer;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use crossterm::event::KeyEvent as Event;
pub use crossterm::event::KeyCode;
pub type KeyBindings = std::collections::HashMap<Event, Action>;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Action {
Write(char),
Delete(Scope),
Move(Range, Direction),
Suggest(Direction),
Complete(Range),
Accept,
Cancel,
Noop,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Scope {
WholeLine,
WholeWord,
Relative(Range, Direction),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Range {
Line,
Word,
Single,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Direction {
Forward,
Backward,
}
pub trait Overrider {
fn override_for(&self, event: Event, buffer: &Buffer) -> Option<Action>;
}
impl Overrider for KeyBindings {
fn override_for(&self, event: Event, _: &Buffer) -> Option<Action> {
self.get(&event).copied()
}
}
impl<F> Overrider for F
where
F: Fn(Event, &Buffer) -> Option<Action>,
{
fn override_for(&self, event: Event, buffer: &Buffer) -> Option<Action> {
self(event, buffer)
}
}
pub(super) fn action_for<O: Overrider + ?Sized>(
overrider: Option<&O>,
event: Event,
buffer: &Buffer,
) -> Action {
overrider
.as_ref()
.and_then(|b| b.override_for(event, buffer))
.unwrap_or_else(|| default_action(event, buffer))
}
#[inline]
fn control_pressed(event: &Event) -> bool {
event.modifiers == crossterm::event::KeyModifiers::CONTROL
}
#[inline]
fn alt_pressed(event: &Event) -> bool {
event.modifiers == crossterm::event::KeyModifiers::ALT
}
#[inline]
fn complete_if_at_end_else_move(buffer: &Buffer, range: Range) -> Action {
if buffer.cursor() == buffer.len() {
if range == Range::Word {
Action::Complete(Range::Word)
} else {
Action::Complete(Range::Line)
}
} else {
Action::Move(range, Direction::Forward)
}
}
fn default_action(event: Event, buffer: &Buffer) -> Action {
use Action::{Accept, Cancel, Delete, Move, Noop, Suggest, Write};
use Direction::{Backward, Forward};
use Range::{Line, Single, Word};
use Scope::{Relative, WholeLine, WholeWord};
match event.code {
KeyCode::Enter => Accept,
KeyCode::Esc => Cancel,
KeyCode::Tab => Suggest(Forward),
KeyCode::BackTab => Suggest(Backward),
KeyCode::Backspace => Delete(Relative(Single, Backward)),
KeyCode::Delete => Delete(Relative(Single, Forward)),
KeyCode::Right => complete_if_at_end_else_move(buffer, Single),
KeyCode::Left => Move(Single, Backward),
KeyCode::Home => Move(Line, Backward),
KeyCode::End => complete_if_at_end_else_move(buffer, Line),
KeyCode::Char(c) => {
if control_pressed(&event) {
match c {
'm' | 'd' => Accept,
'c' => Cancel,
'b' => Move(Single, Backward),
'f' => complete_if_at_end_else_move(buffer, Single),
'a' => Move(Line, Backward),
'e' => complete_if_at_end_else_move(buffer, Line),
'j' => Delete(Relative(Word, Backward)),
'k' => Delete(Relative(Word, Forward)),
'h' => Delete(Relative(Line, Backward)),
'l' => Delete(Relative(Line, Forward)),
'w' => Delete(WholeWord),
'u' => Delete(WholeLine),
_ => Noop,
}
} else if alt_pressed(&event) {
match c {
'b' => Move(Word, Backward),
'f' => complete_if_at_end_else_move(buffer, Word),
_ => Noop,
}
} else {
Write(c)
}
}
_ => Noop,
}
}
#[cfg(test)]
mod test {
use super::{action_for, default_action, Action, Buffer, Direction, Event, KeyCode, Range};
#[test]
fn should_complete_if_at_end() {
use crossterm::event::KeyModifiers;
use Action::{Complete, Move};
use Direction::Forward;
use KeyCode::{Char, End, Right};
use Range::{Line, Single, Word};
let mut c = "a".into();
assert_eq!(default_action(Event::from(Right), &c), Complete(Line));
assert_eq!(default_action(Event::from(End), &c), Complete(Line));
assert_eq!(
default_action(Event::new(Char('f'), KeyModifiers::CONTROL), &c),
Complete(Line)
);
assert_eq!(
default_action(Event::new(Char('f'), KeyModifiers::ALT), &c),
Complete(Word)
);
c.set_cursor(0).unwrap();
assert_eq!(
default_action(Event::from(Right), &c),
Move(Single, Forward)
);
assert_eq!(default_action(Event::from(End), &c), Move(Line, Forward));
assert_eq!(
default_action(Event::new(Char('f'), KeyModifiers::CONTROL), &c),
Move(Single, Forward)
);
assert_eq!(
default_action(Event::new(Char('f'), KeyModifiers::ALT), &c),
Move(Word, Forward)
);
}
#[test]
fn should_default_if_no_mapping() {
use super::KeyBindings;
use KeyCode::Tab;
let action = action_for::<KeyBindings>(None, Event::from(Tab), &Buffer::new());
assert_eq!(action, Action::Suggest(Direction::Forward));
}
mod basic {
use super::super::{
action_for, Action, Buffer, Direction, Event, KeyBindings, KeyCode::Tab,
};
#[test]
fn should_default_if_event_missing_form_mapping() {
let overrider = KeyBindings::new();
let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
assert_eq!(action, Action::Suggest(Direction::Forward));
}
#[test]
fn should_override_if_defined() {
let mut bindings = KeyBindings::new();
bindings.insert(Event::from(Tab), Action::Write('\t'));
let action = action_for(Some(&bindings), Event::from(Tab), &Buffer::new());
assert_eq!(action, Action::Write('\t'));
}
}
mod closure {
use super::super::{action_for, Action, Buffer, Direction, Event, KeyCode::Tab};
#[test]
fn should_default_if_event_missing_form_mapping() {
let overrider = |_, _: &Buffer| None;
let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
assert_eq!(action, Action::Suggest(Direction::Forward));
}
#[test]
fn should_override_if_defined() {
let overrider = |e, _: &Buffer| {
if e == Event::from(Tab) {
Some(Action::Write('\t'))
} else {
None
}
};
let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
assert_eq!(action, Action::Write('\t'));
}
}
}