use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[cfg(feature = "keybindings")]
use crate::canvas::actions::{ActionResult, CanvasAction};
use crate::canvas::modes::AppMode;
use crate::editor::FormEditor;
use crate::DataProvider;
#[cfg(feature = "keybindings")]
use crate::integration::focus_handoff::{key_outcome_for_vertical_navigation, BoundaryExit};
#[cfg(feature = "keybindings")]
use crate::keybindings::{CanvasKeyAction, KeyEventOutcome, KeyStroke};
impl<D: DataProvider> FormEditor<D> {
#[cfg(feature = "keybindings")]
pub fn handle_key_event(&mut self, evt: KeyEvent) -> KeyEventOutcome {
let mode = self.ui_state.current_mode;
let stroke = KeyStroke {
code: evt.code,
modifiers: evt.modifiers,
};
self.seq_tracker.add_key(stroke);
let Some(keybindings) = self.keybindings.as_ref() else {
return KeyEventOutcome::NotMatched;
};
let (matched, is_prefix) = keybindings.lookup_action(mode, self.seq_tracker.sequence());
if let Some(action) = matched.cloned() {
let outcome = self.dispatch_canvas_action(&action);
self.seq_tracker.reset();
return outcome;
}
if is_prefix {
return KeyEventOutcome::Pending;
}
self.seq_tracker.reset();
if mode == AppMode::Ins {
if let KeyCode::Char(c) = evt.code {
let m = evt.modifiers;
let is_plain = m.is_empty() || m == KeyModifiers::SHIFT;
if is_plain {
if self.insert_char(c).is_ok() {
return KeyEventOutcome::Consumed(None);
}
}
}
}
KeyEventOutcome::NotMatched
}
#[cfg(feature = "keybindings")]
fn dispatch_canvas_action(&mut self, action: &CanvasKeyAction) -> KeyEventOutcome {
let Some(canvas_action) = Self::canvas_action_for_key_action(action) else {
return KeyEventOutcome::NotMatched;
};
let vertical_boundary = Self::vertical_boundary_for_key_action(action);
let before_field = self.current_field();
let result = self.execute(canvas_action);
if let Some(boundary) = vertical_boundary {
let moved = self.current_field() != before_field;
return key_outcome_for_vertical_navigation(moved, boundary);
}
Self::key_outcome_for_action_result(result)
}
#[cfg(feature = "keybindings")]
fn canvas_action_for_key_action(action: &CanvasKeyAction) -> Option<CanvasAction> {
match action {
CanvasKeyAction::MoveLeft => Some(CanvasAction::MoveLeft),
CanvasKeyAction::MoveRight => Some(CanvasAction::MoveRight),
CanvasKeyAction::MoveUp => Some(CanvasAction::MoveUp),
CanvasKeyAction::MoveDown => Some(CanvasAction::MoveDown),
CanvasKeyAction::NextField => Some(CanvasAction::NextField),
CanvasKeyAction::PrevField => Some(CanvasAction::PrevField),
CanvasKeyAction::MoveLineStart => Some(CanvasAction::MoveLineStart),
CanvasKeyAction::MoveLineEnd => Some(CanvasAction::MoveLineEnd),
CanvasKeyAction::MoveFirstLine => Some(CanvasAction::MoveFirstLine),
CanvasKeyAction::MoveLastLine => Some(CanvasAction::MoveLastLine),
CanvasKeyAction::MoveWordNext => Some(CanvasAction::MoveWordNext),
CanvasKeyAction::MoveWordPrev => Some(CanvasAction::MoveWordPrev),
CanvasKeyAction::MoveWordEnd => Some(CanvasAction::MoveWordEnd),
CanvasKeyAction::MoveWordEndPrev => Some(CanvasAction::MoveWordEndPrev),
CanvasKeyAction::MoveBigWordNext => Some(CanvasAction::MoveBigWordNext),
CanvasKeyAction::MoveBigWordPrev => Some(CanvasAction::MoveBigWordPrev),
CanvasKeyAction::MoveBigWordEnd => Some(CanvasAction::MoveBigWordEnd),
CanvasKeyAction::MoveBigWordEndPrev => Some(CanvasAction::MoveBigWordEndPrev),
CanvasKeyAction::DeleteCharBackward => Some(CanvasAction::DeleteBackward),
CanvasKeyAction::DeleteCharForward => Some(CanvasAction::DeleteForward),
CanvasKeyAction::Undo => Some(CanvasAction::Undo),
CanvasKeyAction::Redo => Some(CanvasAction::Redo),
CanvasKeyAction::OpenLineBelow => Some(CanvasAction::OpenLineBelow),
CanvasKeyAction::OpenLineAbove => Some(CanvasAction::OpenLineAbove),
CanvasKeyAction::EnterEditModeBefore => Some(CanvasAction::EnterEditMode),
CanvasKeyAction::EnterEditModeAfter => Some(CanvasAction::EnterEditModeAfter),
CanvasKeyAction::Exit | CanvasKeyAction::ExitEditMode => {
Some(CanvasAction::ExitEditMode)
}
CanvasKeyAction::EnterHighlightMode => Some(CanvasAction::EnterHighlightMode),
CanvasKeyAction::EnterHighlightModeLinewise => {
Some(CanvasAction::EnterHighlightModeLinewise)
}
CanvasKeyAction::ExitHighlightMode => Some(CanvasAction::ExitHighlightMode),
CanvasKeyAction::MoveHalfPageUp | CanvasKeyAction::MoveHalfPageDown => None,
CanvasKeyAction::EnterEditModeLineStart
| CanvasKeyAction::EnterEditModeLineEnd
| CanvasKeyAction::DeleteLine
| CanvasKeyAction::DeleteToLineEnd
| CanvasKeyAction::ChangeLine
| CanvasKeyAction::ChangeToLineEnd
| CanvasKeyAction::OperatorDelete
| CanvasKeyAction::OperatorChange
| CanvasKeyAction::OperatorYank
| CanvasKeyAction::JoinLineBelow
| CanvasKeyAction::YankLine
| CanvasKeyAction::PasteAfter
| CanvasKeyAction::PasteBefore
| CanvasKeyAction::DeleteSelection
| CanvasKeyAction::DeleteSelectionNoYank
| CanvasKeyAction::ChangeSelection
| CanvasKeyAction::ChangeSelectionNoYank
| CanvasKeyAction::YankSelection
| CanvasKeyAction::CollapseSelection
| CanvasKeyAction::ExtendLineBelow
| CanvasKeyAction::ExtendToLineBounds
| CanvasKeyAction::SearchNext
| CanvasKeyAction::SearchPrev
| CanvasKeyAction::SelectAll
| CanvasKeyAction::FlipSelections
| CanvasKeyAction::SwitchCase
| CanvasKeyAction::SwitchToLowercase
| CanvasKeyAction::SwitchToUppercase
| CanvasKeyAction::TrimSelections
| CanvasKeyAction::GotoFirstNonWhitespace
| CanvasKeyAction::MovePageUp
| CanvasKeyAction::MovePageDown
| CanvasKeyAction::SearchSelection
| CanvasKeyAction::EnsureSelectionForward
| CanvasKeyAction::MatchBrackets
| CanvasKeyAction::IndentSelection
| CanvasKeyAction::UnindentSelection
| CanvasKeyAction::IncrementNumber
| CanvasKeyAction::DecrementNumber
| CanvasKeyAction::FindNextChar
| CanvasKeyAction::FindPrevChar
| CanvasKeyAction::TillNextChar
| CanvasKeyAction::TillPrevChar
| CanvasKeyAction::ReplaceChar
| CanvasKeyAction::RepeatLastFind
| CanvasKeyAction::RepeatLastFindReverse
| CanvasKeyAction::SurroundAdd
| CanvasKeyAction::SurroundDelete
| CanvasKeyAction::SurroundReplace
| CanvasKeyAction::DeleteWordBackward
| CanvasKeyAction::DeleteToLineStart
| CanvasKeyAction::DeleteWordForward
| CanvasKeyAction::ClearSearch => None,
#[cfg(feature = "suggestions")]
CanvasKeyAction::OpenSuggestions => Some(CanvasAction::TriggerSuggestions),
#[cfg(feature = "suggestions")]
CanvasKeyAction::ApplySuggestion | CanvasKeyAction::EnterDecider => {
Some(CanvasAction::SelectSuggestion)
}
#[cfg(feature = "suggestions")]
CanvasKeyAction::SuggestionDown => Some(CanvasAction::SuggestionDown),
#[cfg(feature = "suggestions")]
CanvasKeyAction::SuggestionUp => Some(CanvasAction::SuggestionUp),
CanvasKeyAction::Unknown(_) => None,
#[cfg(not(feature = "suggestions"))]
CanvasKeyAction::OpenSuggestions
| CanvasKeyAction::ApplySuggestion
| CanvasKeyAction::EnterDecider
| CanvasKeyAction::SuggestionDown
| CanvasKeyAction::SuggestionUp => None,
}
}
#[cfg(feature = "keybindings")]
fn vertical_boundary_for_key_action(action: &CanvasKeyAction) -> Option<BoundaryExit> {
match action {
CanvasKeyAction::MoveUp | CanvasKeyAction::PrevField => Some(BoundaryExit::Top),
CanvasKeyAction::MoveDown | CanvasKeyAction::NextField => Some(BoundaryExit::Bottom),
_ => None,
}
}
#[cfg(feature = "keybindings")]
fn key_outcome_for_action_result(result: ActionResult) -> KeyEventOutcome {
match result {
ActionResult::Success => KeyEventOutcome::Consumed(None),
ActionResult::Message(msg) | ActionResult::Error(msg) => {
KeyEventOutcome::Consumed(Some(msg))
}
}
}
}