use crate::input::KeyInput;
use crate::keymap::{Keymap, resolve_layered};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawInput<'a>(&'a [u8]);
impl<'a> RawInput<'a> {
#[must_use]
pub fn from_bytes(bytes: &'a [u8]) -> Self {
RawInput(bytes)
}
#[must_use]
pub fn as_bytes(&self) -> &'a [u8] {
self.0
}
}
#[derive(Debug, PartialEq, Eq)]
#[must_use]
pub enum Resolution<'a, A> {
Action(&'a A),
Passthrough(RawInput<'a>),
Consume,
}
pub fn resolve_passthrough<'a, A, L>(
layers: L,
input: &KeyInput,
raw: RawInput<'a>,
) -> Resolution<'a, A>
where
L: IntoIterator<Item = &'a Keymap<A>>,
A: 'a,
{
match resolve_layered(layers, input) {
Some(action) => Resolution::Action(action),
None => Resolution::Passthrough(raw),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::{Key, Modifiers};
#[derive(Debug, Clone, PartialEq)]
enum Action {
Quit,
Save,
Split,
}
fn ctrl(c: char) -> KeyInput {
KeyInput::new(Key::Char(c), Modifiers::CTRL)
}
#[test]
fn raw_input_round_trips_its_bytes() {
let raw = RawInput::from_bytes(&[0x1b, 0x5b, b'A']);
assert_eq!(raw.as_bytes(), &[0x1b, 0x5b, b'A']);
}
#[test]
fn hit_resolves_to_action() {
let mut base = Keymap::new();
base.bind(ctrl('q'), Action::Quit);
let raw = RawInput::from_bytes(&[0x11]);
assert_eq!(
resolve_passthrough([&base], &ctrl('q'), raw),
Resolution::Action(&Action::Quit),
);
}
#[test]
fn miss_carries_the_raw_bytes_for_passthrough() {
let base: Keymap<Action> = Keymap::new();
let raw = RawInput::from_bytes(b"a");
match resolve_passthrough(
[&base],
&KeyInput::new(Key::Char('a'), Modifiers::NONE),
raw,
) {
Resolution::Passthrough(bytes) => assert_eq!(bytes.as_bytes(), b"a"),
other => panic!("expected passthrough, got {other:?}"),
}
}
#[test]
fn empty_raw_passes_through_as_empty() {
let base: Keymap<Action> = Keymap::new();
let raw = RawInput::from_bytes(&[]);
match resolve_passthrough(
[&base],
&KeyInput::new(Key::Char('a'), Modifiers::NONE),
raw,
) {
Resolution::Passthrough(bytes) => assert_eq!(bytes.as_bytes(), b""),
other => panic!("expected passthrough, got {other:?}"),
}
}
#[test]
fn miss_preserves_multibyte_sequence_verbatim() {
let base: Keymap<Action> = Keymap::new();
let csi_up: &[u8] = &[0x1b, 0x5b, b'A'];
let raw = RawInput::from_bytes(csi_up);
match resolve_passthrough([&base], &KeyInput::new(Key::Up, Modifiers::NONE), raw) {
Resolution::Passthrough(bytes) => assert_eq!(bytes.as_bytes(), csi_up),
other => panic!("expected passthrough, got {other:?}"),
}
}
#[test]
fn first_hit_wins_across_layers_just_like_resolve_layered() {
let mut base = Keymap::new();
base.bind(ctrl('s'), Action::Save);
let mut overlay = Keymap::new();
overlay.bind(ctrl('s'), Action::Split);
let raw = RawInput::from_bytes(&[0x13]);
assert_eq!(
resolve_passthrough([&overlay, &base], &ctrl('s'), raw),
Resolution::Action(&Action::Split),
);
}
#[test]
fn earlier_miss_falls_through_to_a_later_layer_hit() {
let overlay: Keymap<Action> = Keymap::new(); let mut base = Keymap::new();
base.bind(ctrl('q'), Action::Quit);
let raw = RawInput::from_bytes(&[0x11]);
assert_eq!(
resolve_passthrough([&overlay, &base], &ctrl('q'), raw),
Resolution::Action(&Action::Quit),
);
}
#[test]
fn hit_ignores_raw_bytes_entirely() {
let mut base = Keymap::new();
base.bind(ctrl('q'), Action::Quit);
let raw = RawInput::from_bytes(b"\xff\xff\xff");
assert_eq!(
resolve_passthrough([&base], &ctrl('q'), raw),
Resolution::Action(&Action::Quit),
);
}
#[test]
fn no_layers_is_a_passthrough_not_a_panic() {
let raw = RawInput::from_bytes(b"z");
match resolve_passthrough(
std::iter::empty::<&Keymap<Action>>(),
&KeyInput::new(Key::Char('z'), Modifiers::NONE),
raw,
) {
Resolution::Passthrough(bytes) => assert_eq!(bytes.as_bytes(), b"z"),
other => panic!("expected passthrough, got {other:?}"),
}
}
#[test]
fn resolver_never_returns_consume() {
let base: Keymap<Action> = Keymap::new();
let raw = RawInput::from_bytes(b"a");
let grabbing = true;
let disposition = match resolve_passthrough(
[&base],
&KeyInput::new(Key::Char('a'), Modifiers::NONE),
raw,
) {
Resolution::Action(a) => Resolution::Action(a),
Resolution::Passthrough(_) if grabbing => Resolution::Consume,
Resolution::Passthrough(bytes) => Resolution::Passthrough(bytes),
Resolution::Consume => Resolution::Consume,
};
assert_eq!(disposition, Resolution::<Action>::Consume);
}
}