use crate::key::{KeyCode, KeyInput};
use crate::editor::Editor;
use crate::input::command::{CaseOp, Command, Motion};
use crate::input::mode::Mode;
pub fn map_key(editor: &mut Editor, key: KeyInput) -> Option<Command> {
if editor.showing_file_finder {
return map_file_finder(key);
}
if editor.showing_workspace_symbols {
return map_workspace_symbols(key);
}
match editor.mode {
Mode::Normal => map_normal(editor, key),
Mode::Insert => map_insert(editor, key),
Mode::Visual | Mode::VisualLine => map_visual(key),
Mode::Command => map_command(key),
Mode::Search => map_search(key),
}
}
fn map_normal(editor: &mut Editor, key: KeyInput) -> Option<Command> {
if editor.showing_hover {
editor.showing_hover = false;
editor.hover_text = None;
return None;
}
if editor.showing_references {
match key.code {
KeyCode::Char('j') | KeyCode::Down => return Some(Command::ReferenceNext),
KeyCode::Char('k') | KeyCode::Up => return Some(Command::ReferencePrev),
KeyCode::Enter => return Some(Command::ReferenceJump),
KeyCode::Esc | KeyCode::Char('q') => return Some(Command::DismissPopup),
_ => return Some(Command::DismissPopup),
}
}
if editor.showing_code_actions {
match key.code {
KeyCode::Char('j') | KeyCode::Down => return Some(Command::CodeActionNext),
KeyCode::Char('k') | KeyCode::Up => return Some(Command::CodeActionPrev),
KeyCode::Enter => return Some(Command::CodeActionAccept),
KeyCode::Esc | KeyCode::Char('q') => return Some(Command::CodeActionDismiss),
_ => return Some(Command::CodeActionDismiss),
}
}
if editor.showing_diagnostics {
match key.code {
KeyCode::Char('j') | KeyCode::Down => return Some(Command::DiagnosticNext),
KeyCode::Char('k') | KeyCode::Up => return Some(Command::DiagnosticPrev),
KeyCode::Enter => return Some(Command::DiagnosticJump),
KeyCode::Esc | KeyCode::Char('q') => return Some(Command::DismissPopup),
_ => return None,
}
}
if !editor.pending_keys.is_empty() {
return handle_pending(editor, key);
}
if key.ctrl {
match key.code {
KeyCode::Char('d') => return Some(Command::HalfPageDown),
KeyCode::Char('u') => return Some(Command::HalfPageUp),
KeyCode::Char('f') => return Some(Command::FullPageDown),
KeyCode::Char('b') => return Some(Command::FullPageUp),
KeyCode::Char('r') => return Some(Command::Redo),
KeyCode::Char('p') => return Some(Command::OpenFileFinder),
KeyCode::Char('t') => return Some(Command::WorkspaceSymbol),
KeyCode::Char('o') => return Some(Command::JumpBack),
KeyCode::Char('i') => return Some(Command::JumpForward),
KeyCode::Char('a') => return Some(Command::IncrementNumber),
KeyCode::Char('x') => return Some(Command::DecrementNumber),
KeyCode::Char('w') => {
editor.pending_keys.push('W'); return None;
}
KeyCode::Char('c') => {
editor.should_quit = true;
return None;
}
_ => return None,
}
}
match key.code {
KeyCode::Char('h') | KeyCode::Left => Some(Command::MoveLeft),
KeyCode::Char('j') | KeyCode::Down => Some(Command::MoveDown),
KeyCode::Char('k') | KeyCode::Up => Some(Command::MoveUp),
KeyCode::Char('l') | KeyCode::Right => Some(Command::MoveRight),
KeyCode::Char('w') => Some(Command::MoveWordForward),
KeyCode::Char('b') => Some(Command::MoveWordBackward),
KeyCode::Char('e') => Some(Command::MoveWordEnd),
KeyCode::Char('0') => Some(Command::MoveLineStart),
KeyCode::Char('$') => Some(Command::MoveLineEnd),
KeyCode::Char('^') => Some(Command::MoveFirstNonBlank),
KeyCode::Char('W') => Some(Command::MoveWORDForward),
KeyCode::Char('B') => Some(Command::MoveWORDBackward),
KeyCode::Char('E') => Some(Command::MoveWORDEnd),
KeyCode::Char('{') => Some(Command::MoveParagraphBackward),
KeyCode::Char('}') => Some(Command::MoveParagraphForward),
KeyCode::Char('G') => Some(Command::GotoBottom),
KeyCode::Char('i') => Some(Command::EnterInsertMode),
KeyCode::Char('a') => Some(Command::EnterInsertModeAfter),
KeyCode::Char('A') => Some(Command::EnterInsertModeLineEnd),
KeyCode::Char('I') => Some(Command::EnterInsertModeFirstNonBlank),
KeyCode::Char('o') => Some(Command::InsertNewlineBelow),
KeyCode::Char('O') => Some(Command::InsertNewlineAbove),
KeyCode::Char('x') => Some(Command::DeleteCharForward),
KeyCode::Char('J') => Some(Command::JoinLines),
KeyCode::Char('D') => Some(Command::DeleteMotion(Motion::LineEnd)),
KeyCode::Char('C') => Some(Command::ChangeMotion(Motion::LineEnd)),
KeyCode::Char('.') => Some(Command::RepeatLastChange),
KeyCode::Char('~') => Some(Command::ToggleCaseChar),
KeyCode::Char('*') => Some(Command::SearchWordForward),
KeyCode::Char('#') => Some(Command::SearchWordBackward),
KeyCode::Char('%') => Some(Command::MatchBracket),
KeyCode::Char('H') => Some(Command::ViewportHigh),
KeyCode::Char('M') => Some(Command::ViewportMiddle),
KeyCode::Char('L') => Some(Command::ViewportLow),
KeyCode::Char('d') => {
editor.pending_keys.push('d');
None
}
KeyCode::Char('c') => {
editor.pending_keys.push('c');
None
}
KeyCode::Char('y') => {
editor.pending_keys.push('y');
None
}
KeyCode::Char('g') => {
editor.pending_keys.push('g');
None
}
KeyCode::Char('>') => {
editor.pending_keys.push('>');
None
}
KeyCode::Char('<') => {
editor.pending_keys.push('<');
None
}
KeyCode::Char('f') => {
editor.pending_keys.push('f');
None
}
KeyCode::Char('F') => {
editor.pending_keys.push('F');
None
}
KeyCode::Char('t') => {
editor.pending_keys.push('t');
None
}
KeyCode::Char('T') => {
editor.pending_keys.push('T');
None
}
KeyCode::Char('r') => {
editor.pending_keys.push('r');
None
}
KeyCode::Char('z') => {
editor.pending_keys.push('z');
None
}
KeyCode::Char(']') => {
editor.pending_keys.push(']');
None
}
KeyCode::Char('[') => {
editor.pending_keys.push('[');
None
}
KeyCode::Char('"') => {
editor.pending_keys.push('"');
None
}
KeyCode::Char('q') => {
if editor.recording_macro.is_some() {
Some(Command::StopMacro)
} else {
editor.pending_keys.push('q');
None
}
}
KeyCode::Char('@') => {
editor.pending_keys.push('@');
None
}
KeyCode::Char('K') => Some(Command::Hover),
KeyCode::Char('p') => Some(Command::PasteAfter),
KeyCode::Char('P') => Some(Command::PasteBefore),
KeyCode::Char('u') => Some(Command::Undo),
KeyCode::Char('v') => Some(Command::EnterVisualMode),
KeyCode::Char('V') => Some(Command::EnterVisualLineMode),
KeyCode::Char('/') => Some(Command::EnterSearchMode),
KeyCode::Char('n') => Some(Command::SearchNext),
KeyCode::Char('N') => Some(Command::SearchPrev),
KeyCode::Char(':') => Some(Command::EnterCommandMode),
_ => None,
}
}
fn handle_pending(editor: &mut Editor, key: KeyInput) -> Option<Command> {
if key.code == KeyCode::Esc {
editor.pending_keys.clear();
return None;
}
let ch = match key.code {
KeyCode::Char(ch) => ch,
_ => {
editor.pending_keys.clear();
return None;
}
};
let pending = editor.pending_keys.clone();
editor.pending_keys.clear();
match *pending.as_slice() {
['"'] => {
editor.selected_register = Some(ch);
None
}
['q'] => Some(Command::StartMacro(ch)),
['@'] => {
if ch == '@' {
Some(Command::PlayLastMacro)
} else {
Some(Command::PlayMacro(ch))
}
}
['d'] => match ch {
'd' => Some(Command::DeleteLine),
'w' => Some(Command::DeleteMotion(Motion::WordForward)),
'e' => Some(Command::DeleteMotion(Motion::WordEnd)),
'b' => Some(Command::DeleteMotion(Motion::WordBackward)),
'W' => Some(Command::DeleteMotion(Motion::WORDForward)),
'E' => Some(Command::DeleteMotion(Motion::WORDEnd)),
'B' => Some(Command::DeleteMotion(Motion::WORDBackward)),
'$' => Some(Command::DeleteMotion(Motion::LineEnd)),
'0' => Some(Command::DeleteMotion(Motion::LineStart)),
'^' => Some(Command::DeleteMotion(Motion::FirstNonBlank)),
'}' => Some(Command::DeleteMotion(Motion::ParagraphForward)),
'{' => Some(Command::DeleteMotion(Motion::ParagraphBackward)),
'i' | 'a' | 'f' | 'F' | 't' | 'T' => {
editor.pending_keys.push('d');
editor.pending_keys.push(ch);
None
}
_ => None,
},
['d', 'i'] => Some(Command::DeleteMotion(Motion::Inner(ch))),
['d', 'a'] => Some(Command::DeleteMotion(Motion::Around(ch))),
['d', 'f'] => Some(Command::DeleteMotion(Motion::FindForward(ch))),
['d', 'F'] => Some(Command::DeleteMotion(Motion::FindBackward(ch))),
['d', 't'] => Some(Command::DeleteMotion(Motion::TillForward(ch))),
['d', 'T'] => Some(Command::DeleteMotion(Motion::TillBackward(ch))),
['c'] => match ch {
'c' => Some(Command::ChangeMotion(Motion::Line)),
'w' => Some(Command::ChangeMotion(Motion::WordForward)),
'e' => Some(Command::ChangeMotion(Motion::WordEnd)),
'b' => Some(Command::ChangeMotion(Motion::WordBackward)),
'W' => Some(Command::ChangeMotion(Motion::WORDForward)),
'E' => Some(Command::ChangeMotion(Motion::WORDEnd)),
'B' => Some(Command::ChangeMotion(Motion::WORDBackward)),
'$' => Some(Command::ChangeMotion(Motion::LineEnd)),
'0' => Some(Command::ChangeMotion(Motion::LineStart)),
'^' => Some(Command::ChangeMotion(Motion::FirstNonBlank)),
'}' => Some(Command::ChangeMotion(Motion::ParagraphForward)),
'{' => Some(Command::ChangeMotion(Motion::ParagraphBackward)),
'i' | 'a' | 'f' | 'F' | 't' | 'T' => {
editor.pending_keys.push('c');
editor.pending_keys.push(ch);
None
}
_ => None,
},
['c', 'i'] => Some(Command::ChangeMotion(Motion::Inner(ch))),
['c', 'a'] => Some(Command::ChangeMotion(Motion::Around(ch))),
['c', 'f'] => Some(Command::ChangeMotion(Motion::FindForward(ch))),
['c', 'F'] => Some(Command::ChangeMotion(Motion::FindBackward(ch))),
['c', 't'] => Some(Command::ChangeMotion(Motion::TillForward(ch))),
['c', 'T'] => Some(Command::ChangeMotion(Motion::TillBackward(ch))),
['y'] => match ch {
'y' => Some(Command::YankLine),
'w' => Some(Command::YankMotion(Motion::WordForward)),
'e' => Some(Command::YankMotion(Motion::WordEnd)),
'b' => Some(Command::YankMotion(Motion::WordBackward)),
'W' => Some(Command::YankMotion(Motion::WORDForward)),
'E' => Some(Command::YankMotion(Motion::WORDEnd)),
'B' => Some(Command::YankMotion(Motion::WORDBackward)),
'$' => Some(Command::YankMotion(Motion::LineEnd)),
'0' => Some(Command::YankMotion(Motion::LineStart)),
'^' => Some(Command::YankMotion(Motion::FirstNonBlank)),
'}' => Some(Command::YankMotion(Motion::ParagraphForward)),
'{' => Some(Command::YankMotion(Motion::ParagraphBackward)),
'i' | 'a' => {
editor.pending_keys.push('y');
editor.pending_keys.push(ch);
None
}
_ => None,
},
['y', 'i'] => Some(Command::YankMotion(Motion::Inner(ch))),
['y', 'a'] => Some(Command::YankMotion(Motion::Around(ch))),
['g'] => match ch {
'd' => Some(Command::GotoDefinition),
'r' => Some(Command::FindReferences),
'g' => Some(Command::GotoTop),
'a' => Some(Command::CodeAction),
'E' => Some(Command::DiagnosticList),
't' => Some(Command::NextBuffer),
'T' => Some(Command::PrevBuffer),
'j' => Some(Command::MoveDocumentLineDown),
'k' => Some(Command::MoveDocumentLineUp),
'u' | 'U' | '~' => {
editor.pending_keys.push('g');
editor.pending_keys.push(ch);
None
}
_ => None,
},
['g', op @ ('u' | 'U' | '~')] => {
map_case_motion(editor, op, ch)
}
['g', 'u', 'i'] => Some(Command::CaseChange(CaseOp::Lower, Motion::Inner(ch))),
['g', 'u', 'a'] => Some(Command::CaseChange(CaseOp::Lower, Motion::Around(ch))),
['g', 'U', 'i'] => Some(Command::CaseChange(CaseOp::Upper, Motion::Inner(ch))),
['g', 'U', 'a'] => Some(Command::CaseChange(CaseOp::Upper, Motion::Around(ch))),
['g', '~', 'i'] => Some(Command::CaseChange(CaseOp::Toggle, Motion::Inner(ch))),
['g', '~', 'a'] => Some(Command::CaseChange(CaseOp::Toggle, Motion::Around(ch))),
['>'] => match ch {
'>' => Some(Command::IndentLine),
_ => None,
},
['<'] => match ch {
'<' => Some(Command::DedentLine),
_ => None,
},
['f'] => Some(Command::FindCharForward(ch)),
['F'] => Some(Command::FindCharBackward(ch)),
['t'] => Some(Command::TillCharForward(ch)),
['T'] => Some(Command::TillCharBackward(ch)),
['r'] => Some(Command::ReplaceChar(ch)),
[']'] => match ch {
'd' => Some(Command::DiagnosticNext),
_ => None,
},
['['] => match ch {
'd' => Some(Command::DiagnosticPrev),
_ => None,
},
['W'] => match ch {
'v' => Some(Command::SplitVertical),
's' => Some(Command::SplitHorizontal),
'h' => Some(Command::PaneLeft),
'j' => Some(Command::PaneDown),
'k' => Some(Command::PaneUp),
'l' => Some(Command::PaneRight),
'w' => Some(Command::PaneNext),
'q' => Some(Command::PaneClose),
_ => None,
},
['z'] => match ch {
'z' => Some(Command::ScrollCenter),
't' => Some(Command::ScrollTop),
'b' => Some(Command::ScrollBottom),
_ => None,
},
_ => None,
}
}
fn map_case_motion(editor: &mut Editor, op: char, ch: char) -> Option<Command> {
let case_op = match op {
'u' => CaseOp::Lower,
'U' => CaseOp::Upper,
'~' => CaseOp::Toggle,
_ => return None,
};
match ch {
c if c == op => Some(Command::CaseChangeLine(case_op)),
'w' => Some(Command::CaseChange(case_op, Motion::WordForward)),
'e' => Some(Command::CaseChange(case_op, Motion::WordEnd)),
'b' => Some(Command::CaseChange(case_op, Motion::WordBackward)),
'W' => Some(Command::CaseChange(case_op, Motion::WORDForward)),
'E' => Some(Command::CaseChange(case_op, Motion::WORDEnd)),
'B' => Some(Command::CaseChange(case_op, Motion::WORDBackward)),
'$' => Some(Command::CaseChange(case_op, Motion::LineEnd)),
'0' => Some(Command::CaseChange(case_op, Motion::LineStart)),
'^' => Some(Command::CaseChange(case_op, Motion::FirstNonBlank)),
'i' | 'a' => {
editor.pending_keys.push('g');
editor.pending_keys.push(op);
editor.pending_keys.push(ch);
None
}
_ => None,
}
}
fn map_visual(key: KeyInput) -> Option<Command> {
if key.ctrl {
match key.code {
KeyCode::Char('d') => return Some(Command::HalfPageDown),
KeyCode::Char('u') => return Some(Command::HalfPageUp),
KeyCode::Char('f') => return Some(Command::FullPageDown),
KeyCode::Char('b') => return Some(Command::FullPageUp),
_ => return None,
}
}
match key.code {
KeyCode::Char('h') | KeyCode::Left => Some(Command::MoveLeft),
KeyCode::Char('j') | KeyCode::Down => Some(Command::MoveDown),
KeyCode::Char('k') | KeyCode::Up => Some(Command::MoveUp),
KeyCode::Char('l') | KeyCode::Right => Some(Command::MoveRight),
KeyCode::Char('w') => Some(Command::MoveWordForward),
KeyCode::Char('b') => Some(Command::MoveWordBackward),
KeyCode::Char('e') => Some(Command::MoveWordEnd),
KeyCode::Char('W') => Some(Command::MoveWORDForward),
KeyCode::Char('B') => Some(Command::MoveWORDBackward),
KeyCode::Char('E') => Some(Command::MoveWORDEnd),
KeyCode::Char('0') => Some(Command::MoveLineStart),
KeyCode::Char('$') => Some(Command::MoveLineEnd),
KeyCode::Char('^') => Some(Command::MoveFirstNonBlank),
KeyCode::Char('{') => Some(Command::MoveParagraphBackward),
KeyCode::Char('}') => Some(Command::MoveParagraphForward),
KeyCode::Char('G') => Some(Command::GotoBottom),
KeyCode::Char('o') => Some(Command::VisualSwapAnchor),
KeyCode::Char('~') => Some(Command::ToggleCaseChar),
KeyCode::Char('u') => Some(Command::CaseChangeLine(CaseOp::Lower)),
KeyCode::Char('U') => Some(Command::CaseChangeLine(CaseOp::Upper)),
KeyCode::Char('d') | KeyCode::Char('x') => Some(Command::VisualDelete),
KeyCode::Char('y') => Some(Command::VisualYank),
KeyCode::Char('c') => Some(Command::VisualChange),
KeyCode::Char('>') => Some(Command::VisualIndent),
KeyCode::Char('<') => Some(Command::VisualDedent),
KeyCode::Esc | KeyCode::Char('v') | KeyCode::Char('V') => {
Some(Command::ExitToNormalMode)
}
_ => None,
}
}
fn map_insert(editor: &Editor, key: KeyInput) -> Option<Command> {
if editor.showing_completion {
match key.code {
KeyCode::Down | KeyCode::Tab => return Some(Command::CompletionNext),
KeyCode::Up | KeyCode::BackTab => return Some(Command::CompletionPrev),
KeyCode::Enter => return Some(Command::AcceptCompletion),
KeyCode::Esc => return Some(Command::CancelCompletion),
_ => {
}
}
}
match key.code {
KeyCode::Esc => Some(Command::ExitToNormalMode),
KeyCode::Backspace => Some(Command::DeleteCharBackward),
KeyCode::Enter => Some(Command::InsertNewline),
KeyCode::Left => Some(Command::MoveLeft),
KeyCode::Right => Some(Command::MoveRight),
KeyCode::Up => Some(Command::MoveUp),
KeyCode::Down => Some(Command::MoveDown),
KeyCode::Char(' ') if key.ctrl => {
Some(Command::TriggerCompletion)
}
KeyCode::Char(ch) => Some(Command::InsertChar(ch)),
KeyCode::Tab => Some(Command::InsertTab),
_ => None,
}
}
fn map_command(key: KeyInput) -> Option<Command> {
match key.code {
KeyCode::Esc => Some(Command::ExitToNormalMode),
KeyCode::Enter => Some(Command::CmdExecute),
KeyCode::Backspace => Some(Command::CmdBackspace),
KeyCode::Up => Some(Command::CmdHistoryPrev),
KeyCode::Down => Some(Command::CmdHistoryNext),
KeyCode::Char(ch) => Some(Command::CmdInput(ch)),
_ => None,
}
}
fn map_search(key: KeyInput) -> Option<Command> {
match key.code {
KeyCode::Esc => Some(Command::SearchCancel),
KeyCode::Enter => Some(Command::SearchConfirm),
KeyCode::Backspace => Some(Command::SearchBackspace),
KeyCode::Char(ch) => Some(Command::SearchInput(ch)),
_ => None,
}
}
fn map_workspace_symbols(key: KeyInput) -> Option<Command> {
match key.code {
KeyCode::Esc => Some(Command::WorkspaceSymbolCancel),
KeyCode::Enter => Some(Command::WorkspaceSymbolConfirm),
KeyCode::Backspace => Some(Command::WorkspaceSymbolBackspace),
KeyCode::Down | KeyCode::Tab => Some(Command::WorkspaceSymbolNext),
KeyCode::Up | KeyCode::BackTab => Some(Command::WorkspaceSymbolPrev),
KeyCode::Char('n') if key.ctrl => Some(Command::WorkspaceSymbolNext),
KeyCode::Char('p') if key.ctrl => Some(Command::WorkspaceSymbolPrev),
KeyCode::Char(ch) => Some(Command::WorkspaceSymbolInput(ch)),
_ => None,
}
}
fn map_file_finder(key: KeyInput) -> Option<Command> {
match key.code {
KeyCode::Esc => Some(Command::FileFinderCancel),
KeyCode::Enter => Some(Command::FileFinderConfirm),
KeyCode::Backspace => Some(Command::FileFinderBackspace),
KeyCode::Down | KeyCode::Tab => Some(Command::FileFinderNext),
KeyCode::Up | KeyCode::BackTab => Some(Command::FileFinderPrev),
KeyCode::Char('n') if key.ctrl => {
Some(Command::FileFinderNext)
}
KeyCode::Char('p') if key.ctrl => {
Some(Command::FileFinderPrev)
}
KeyCode::Char(ch) => Some(Command::FileFinderInput(ch)),
_ => None,
}
}