use crate::input::handler::{DeferredAction, InputContext, InputHandler, InputResult};
use crate::input::keybindings::{Action, KeybindingResolver};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub struct TerminalModeInputHandler<'a> {
keyboard_capture: bool,
keybindings: &'a KeybindingResolver,
}
impl<'a> TerminalModeInputHandler<'a> {
pub fn new(keyboard_capture: bool, keybindings: &'a KeybindingResolver) -> Self {
Self {
keyboard_capture,
keybindings,
}
}
}
impl InputHandler for TerminalModeInputHandler<'_> {
fn handle_key_event(&mut self, event: &KeyEvent, ctx: &mut InputContext) -> InputResult {
let code = event.code;
let modifiers = event.modifiers;
if code == KeyCode::F(9) {
ctx.defer(DeferredAction::ToggleKeyboardCapture);
return InputResult::Consumed;
}
if self.keyboard_capture {
ctx.defer(DeferredAction::SendTerminalKey(code, modifiers));
return InputResult::Consumed;
}
let ui_action = self.keybindings.resolve_terminal_ui_action(event);
if !matches!(ui_action, Action::None) {
if matches!(ui_action, Action::TerminalEscape) {
ctx.defer(DeferredAction::ExitTerminalMode { explicit: true });
return InputResult::Consumed;
}
if matches!(
ui_action,
Action::NextSplit | Action::PrevSplit | Action::CloseSplit
) {
ctx.defer(DeferredAction::ExitTerminalMode { explicit: false });
}
ctx.defer(DeferredAction::ExecuteAction(ui_action));
return InputResult::Consumed;
}
if modifiers.contains(KeyModifiers::SHIFT) && code == KeyCode::PageUp {
ctx.defer(DeferredAction::EnterScrollbackMode);
return InputResult::Consumed;
}
ctx.defer(DeferredAction::SendTerminalKey(code, modifiers));
InputResult::Consumed
}
fn is_modal(&self) -> bool {
true
}
}
pub fn should_enter_terminal_mode(event: &KeyEvent) -> bool {
let code = event.code;
let modifiers = event.modifiers;
if modifiers.contains(KeyModifiers::CONTROL) {
matches!(
code,
KeyCode::Char(' ') | KeyCode::Char(']') | KeyCode::Char('`')
)
} else {
code == KeyCode::Char('q')
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
fn key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn key_with_mods(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent::new(code, modifiers)
}
fn make_resolver() -> KeybindingResolver {
let config = Config::default();
KeybindingResolver::new(&config)
}
#[test]
fn test_f9_toggles_capture() {
let resolver = make_resolver();
let mut handler = TerminalModeInputHandler::new(false, &resolver);
let mut ctx = InputContext::new();
let result = handler.handle_key_event(&key(KeyCode::F(9)), &mut ctx);
assert!(matches!(result, InputResult::Consumed));
assert_eq!(ctx.deferred_actions.len(), 1);
assert!(matches!(
ctx.deferred_actions[0],
DeferredAction::ToggleKeyboardCapture
));
}
#[test]
fn test_keyboard_capture_forwards_all_keys() {
let resolver = make_resolver();
let mut handler = TerminalModeInputHandler::new(true, &resolver);
let mut ctx = InputContext::new();
let result = handler.handle_key_event(&key(KeyCode::Char('a')), &mut ctx);
assert!(matches!(result, InputResult::Consumed));
assert_eq!(ctx.deferred_actions.len(), 1);
assert!(matches!(
ctx.deferred_actions[0],
DeferredAction::SendTerminalKey(KeyCode::Char('a'), KeyModifiers::NONE)
));
}
#[test]
fn test_shift_pageup_enters_scrollback() {
let resolver = make_resolver();
let mut handler = TerminalModeInputHandler::new(false, &resolver);
let mut ctx = InputContext::new();
let result = handler.handle_key_event(
&key_with_mods(KeyCode::PageUp, KeyModifiers::SHIFT),
&mut ctx,
);
assert!(matches!(result, InputResult::Consumed));
assert_eq!(ctx.deferred_actions.len(), 1);
assert!(matches!(
ctx.deferred_actions[0],
DeferredAction::EnterScrollbackMode
));
}
#[test]
fn test_regular_keys_forwarded() {
let resolver = make_resolver();
let mut handler = TerminalModeInputHandler::new(false, &resolver);
let mut ctx = InputContext::new();
let result = handler.handle_key_event(&key(KeyCode::Char('x')), &mut ctx);
assert!(matches!(result, InputResult::Consumed));
assert_eq!(ctx.deferred_actions.len(), 1);
assert!(matches!(
ctx.deferred_actions[0],
DeferredAction::SendTerminalKey(KeyCode::Char('x'), KeyModifiers::NONE)
));
}
#[test]
fn test_should_enter_terminal_mode() {
assert!(should_enter_terminal_mode(&key_with_mods(
KeyCode::Char(' '),
KeyModifiers::CONTROL
)));
assert!(should_enter_terminal_mode(&key_with_mods(
KeyCode::Char(']'),
KeyModifiers::CONTROL
)));
assert!(should_enter_terminal_mode(&key_with_mods(
KeyCode::Char('`'),
KeyModifiers::CONTROL
)));
assert!(should_enter_terminal_mode(&key(KeyCode::Char('q'))));
assert!(!should_enter_terminal_mode(&key(KeyCode::Char('a'))));
assert!(!should_enter_terminal_mode(&key(KeyCode::Enter)));
}
#[test]
fn test_is_modal() {
let resolver = make_resolver();
let handler = TerminalModeInputHandler::new(false, &resolver);
assert!(handler.is_modal());
}
}