use super::action::{Action, Operator};
use super::pending::{Key, KeyCode};
use crate::text::motion::Motion;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verb {
Motion(Motion),
Goto(Motion),
Operator(Operator),
Object { around: bool },
Find { forward: bool, till: bool },
ReplaceChar,
Register,
Prefix(char),
Act(Action),
}
pub fn normal(key: Key) -> Option<Verb> {
if key.alt && !key.ctrl {
match key.code {
KeyCode::Char('j') => return Some(Verb::Act(Action::ListItemMove { down: true })),
KeyCode::Char('k') => return Some(Verb::Act(Action::ListItemMove { down: false })),
_ => {}
}
}
if let Some(m) = motion(key) {
return Some(Verb::Motion(m));
}
let ctrl = key.ctrl;
Some(match (key.code, ctrl) {
(KeyCode::Char('G'), false) => Verb::Goto(Motion::BufferEnd),
(KeyCode::Char('d'), false) => Verb::Operator(Operator::Delete),
(KeyCode::Char('c'), false) => Verb::Operator(Operator::Change),
(KeyCode::Char('y'), false) => Verb::Operator(Operator::Yank),
(KeyCode::Char('>'), false) => Verb::Operator(Operator::Indent),
(KeyCode::Char('<'), false) => Verb::Operator(Operator::Outdent),
(KeyCode::Char('"'), false) => Verb::Register,
(KeyCode::Char('r'), false) => Verb::ReplaceChar,
(KeyCode::Char('g'), false) => Verb::Prefix('g'),
(KeyCode::Char('z'), false) => Verb::Prefix('z'),
(KeyCode::Char('w'), true) => Verb::Prefix('w'),
(KeyCode::Char('f'), false) => Verb::Find { forward: true, till: false },
(KeyCode::Char('F'), false) => Verb::Find { forward: false, till: false },
(KeyCode::Char('t'), false) => Verb::Find { forward: true, till: true },
(KeyCode::Char('T'), false) => Verb::Find { forward: false, till: true },
(KeyCode::Char('i'), false) => Verb::Object { around: false },
(KeyCode::Char('a'), false) => Verb::Object { around: true },
(KeyCode::Char('I'), false) => Verb::Act(Action::InsertLineStart),
(KeyCode::Char('A'), false) => Verb::Act(Action::AppendLineEnd),
(KeyCode::Char('o'), false) => Verb::Act(Action::OpenBelow),
(KeyCode::Char('O'), false) => Verb::Act(Action::OpenAbove),
(KeyCode::Char('x'), false) => Verb::Act(Action::DeleteChar),
(KeyCode::Char('X'), false) => Verb::Act(Action::DeleteCharBack),
(KeyCode::Char('D'), false) => Verb::Act(Action::DeleteToEol),
(KeyCode::Char('C'), false) => Verb::Act(Action::ChangeToEol),
(KeyCode::Char('Y'), false) => Verb::Act(Action::YankLine),
(KeyCode::Char('J'), false) => Verb::Act(Action::JoinLines),
(KeyCode::Char('~'), false) => Verb::Act(Action::ToggleCase),
(KeyCode::Char('p'), false) => Verb::Act(Action::PasteAfter),
(KeyCode::Char('P'), false) => Verb::Act(Action::PasteBefore),
(KeyCode::Char('v'), false) => Verb::Act(Action::Visual),
(KeyCode::Char('V'), false) => Verb::Act(Action::VisualLine),
(KeyCode::Char('u'), false) => Verb::Act(Action::Undo),
(KeyCode::Char('r'), true) => Verb::Act(Action::Redo),
(KeyCode::Char('s'), true) => Verb::Act(Action::Save),
(KeyCode::Char('^'), true) => Verb::Act(Action::AlternateBuffer),
(KeyCode::Char('6'), true) => Verb::Act(Action::AlternateBuffer),
(KeyCode::Char('/'), false) => Verb::Act(Action::SearchForward),
(KeyCode::Char('?'), false) => Verb::Act(Action::SearchBackward),
(KeyCode::Char('n'), false) => Verb::Act(Action::SearchNext { reverse: false }),
(KeyCode::Char('N'), false) => Verb::Act(Action::SearchNext { reverse: true }),
(KeyCode::Char(';'), false) => Verb::Act(Action::RepeatFind { reverse: false }),
(KeyCode::Char(','), false) => Verb::Act(Action::RepeatFind { reverse: true }),
(KeyCode::Char('*'), false) => Verb::Act(Action::SearchWordUnderCursor),
(KeyCode::Char(':'), false) => Verb::Act(Action::Command),
(KeyCode::Char('.'), false) => Verb::Act(Action::Repeat),
(KeyCode::Esc, _) => Verb::Act(Action::NormalMode),
_ => return None,
})
}
pub fn visual(key: Key) -> Option<Verb> {
if key.alt {
return visual(Key { alt: false, ..key });
}
if key.ctrl {
return normal(key);
}
Some(match key.code {
KeyCode::Char('d') | KeyCode::Char('x') => Verb::Operator(Operator::Delete),
KeyCode::Char('c') | KeyCode::Char('s') => Verb::Operator(Operator::Change),
KeyCode::Char('y') => Verb::Operator(Operator::Yank),
KeyCode::Char('>') => Verb::Operator(Operator::Indent),
KeyCode::Char('<') => Verb::Operator(Operator::Outdent),
KeyCode::Char('u') => Verb::Operator(Operator::Lowercase),
KeyCode::Char('U') => Verb::Operator(Operator::Uppercase),
KeyCode::Char('i') => Verb::Object { around: false },
KeyCode::Char('a') => Verb::Object { around: true },
KeyCode::Char('o') => Verb::Act(Action::SwapSelectionEnds),
KeyCode::Char('v') => Verb::Act(Action::Visual),
KeyCode::Char('V') => Verb::Act(Action::VisualLine),
KeyCode::Esc => Verb::Act(Action::NormalMode),
_ => return normal(key),
})
}
pub fn after_prefix(prefix: char, key: Key, visual_mode: bool) -> Option<Verb> {
let c = match (prefix, key.code) {
('w', KeyCode::Left) => 'h',
('w', KeyCode::Down) => 'j',
('w', KeyCode::Up) => 'k',
('w', KeyCode::Right) => 'l',
(_, KeyCode::Char(c)) => c,
_ => return None,
};
Some(match (prefix, c) {
('g', 'g') => Verb::Goto(Motion::BufferStart),
('g', 'u') if !visual_mode => Verb::Operator(Operator::Lowercase),
('g', 'U') if !visual_mode => Verb::Operator(Operator::Uppercase),
('g', 'b') => Verb::Act(Action::ToggleBold),
('g', 'i') => Verb::Act(Action::ToggleItalic),
('g', 'h') => Verb::Act(Action::ToggleHighlight),
('g', 'k') => Verb::Act(Action::ToggleCode),
('g', 'l') => Verb::Act(Action::InsertLink),
('g', 't') => Verb::Act(Action::ToggleTask),
('g', 'a') => Verb::Act(Action::AlignTable),
('g', 'f') => Verb::Act(Action::FollowLink),
('g', 'x') => Verb::Act(Action::OpenExternal),
('g', 'p') => Verb::Act(Action::AppendParagraph),
('g', c @ '0'..='6') => Verb::Act(Action::SetHeading(c as u8 - b'0')),
('w', c) => Verb::Act(Action::Window(c)),
_ => return None,
})
}
fn motion(key: Key) -> Option<Motion> {
use KeyCode::*;
Some(match (key.code, key.ctrl) {
(Char('h'), false) | (Left, _) => Motion::Left,
(Char('l'), false) | (Right, _) => Motion::Right,
(Char('k'), false) | (Up, _) => Motion::Up,
(Char('j'), false) | (Down, _) => Motion::Down,
(Char('w'), false) => Motion::WordForward { big: false },
(Char('W'), false) => Motion::WordForward { big: true },
(Char('b'), false) => Motion::WordBack { big: false },
(Char('B'), false) => Motion::WordBack { big: true },
(Char('e'), false) => Motion::WordEnd { big: false },
(Char('E'), false) => Motion::WordEnd { big: true },
(Char('0'), false) => Motion::LineStart,
(Char('^'), false) => Motion::LineFirstNonBlank,
(Char('$'), false) => Motion::LineEnd,
(Char('{'), false) => Motion::ParagraphBack,
(Char('}'), false) => Motion::ParagraphForward,
(Char('H'), false) => Motion::ScreenTop,
(Char('M'), false) => Motion::ScreenMiddle,
(Char('L'), false) => Motion::ScreenBottom,
(Char('d'), true) => Motion::HalfPageDown,
(Char('u'), true) => Motion::HalfPageUp,
(Char('f'), true) => Motion::PageDown,
(Char('b'), true) => Motion::PageUp,
_ => return None,
})
}
impl Verb {
pub fn from_config_name(name: &str) -> Option<Verb> {
let verb = match name {
"delete" | "operator_delete" => Verb::Operator(Operator::Delete),
"change" | "operator_change" => Verb::Operator(Operator::Change),
"yank" | "operator_yank" => Verb::Operator(Operator::Yank),
"indent" | "operator_indent" => Verb::Operator(Operator::Indent),
"outdent" | "operator_outdent" => Verb::Operator(Operator::Outdent),
"lowercase" | "operator_lowercase" => Verb::Operator(Operator::Lowercase),
"uppercase" | "operator_uppercase" => Verb::Operator(Operator::Uppercase),
"inner_object" => Verb::Object { around: false },
"around_object" => Verb::Object { around: true },
"register" => Verb::Register,
"replace_char" => Verb::ReplaceChar,
"find_char" => Verb::Find { forward: true, till: false },
"find_char_back" => Verb::Find { forward: false, till: false },
"till_char" => Verb::Find { forward: true, till: true },
"till_char_back" => Verb::Find { forward: false, till: true },
"goto_line" => Verb::Goto(Motion::BufferEnd),
"goto_line_first" => Verb::Goto(Motion::BufferStart),
_ => {
if let Some(m) = motion_by_name(name) {
return Some(Verb::Motion(m));
}
return Action::from_config_name(name).map(Verb::Act);
}
};
Some(verb)
}
}
fn motion_by_name(name: &str) -> Option<Motion> {
Some(match name {
"move_left" => Motion::Left,
"move_right" => Motion::Right,
"move_up" => Motion::Up,
"move_down" => Motion::Down,
"word_forward" => Motion::WordForward { big: false },
"word_back" => Motion::WordBack { big: false },
"word_end" => Motion::WordEnd { big: false },
"line_start" => Motion::LineStart,
"line_first_non_blank" => Motion::LineFirstNonBlank,
"line_end" => Motion::LineEnd,
"paragraph_forward" => Motion::ParagraphForward,
"paragraph_back" => Motion::ParagraphBack,
"half_page_down" => Motion::HalfPageDown,
"half_page_up" => Motion::HalfPageUp,
"page_down" => Motion::PageDown,
"page_up" => Motion::PageUp,
"screen_top" => Motion::ScreenTop,
"screen_middle" => Motion::ScreenMiddle,
"screen_bottom" => Motion::ScreenBottom,
_ => return None,
})
}
#[derive(Clone, Debug, PartialEq)]
pub struct Command {
pub action: Action,
pub count: usize,
pub register: Option<char>,
}
impl Command {
pub fn new(action: Action, count: usize, register: Option<char>) -> Command {
Command { action, count, register }
}
pub fn bare(action: Action) -> Command {
Command { action, count: 1, register: None }
}
}