| 1 | //! Keyboard and mouse input: vim key notation and per-mode keymaps. |
| 2 | //! |
| 3 | //! Until the configurable keymap lands (M2), [`Input`] holds the default bindings in code. |
| 4 | |
| 5 | pub mod keymap; |
| 6 | pub mod keys; |
| 7 | |
| 8 | use winit::keyboard::{Key, ModifiersState, NamedKey}; |
| 9 | |
| 10 | use crate::action::{Action, Zoom}; |
| 11 | use crate::state::view::{Axis, Dir, FitMode}; |
| 12 | |
| 13 | #[derive(Debug, Default)] |
| 14 | pub struct Input { |
| 15 | /// A `g` is waiting for its second key. |
| 16 | pending_g: bool, |
| 17 | /// A count prefix (`5n`, `12G`). |
| 18 | count: Option<usize>, |
| 19 | } |
| 20 | |
| 21 | impl Input { |
| 22 | /// The incomplete sequence typed so far, for the status bar. |
| 23 | pub fn pending(&self) -> String { |
| 24 | let mut s = self.count.map(|c| c.to_string()).unwrap_or_default(); |
| 25 | if self.pending_g { |
| 26 | s.push('g'); |
| 27 | } |
| 28 | s |
| 29 | } |
| 30 | |
| 31 | pub fn cancel(&mut self) { |
| 32 | self.pending_g = false; |
| 33 | self.count = None; |
| 34 | } |
| 35 | |
| 36 | /// Map a key press to an action using the default bindings. |
| 37 | pub fn key(&mut self, key: &Key, mods: ModifiersState) -> Option<Action> { |
| 38 | if mods.control_key() || mods.alt_key() || mods.super_key() { |
| 39 | return None; |
| 40 | } |
| 41 | if let Key::Character(s) = key { |
| 42 | // `0` is a motion unless a count is already pending. |
| 43 | if let Some(d) = s.chars().next().and_then(|c| c.to_digit(10)) |
| 44 | && s.len() == 1 |
| 45 | && !self.pending_g |
| 46 | && (d != 0 || self.count.is_some()) |
| 47 | { |
| 48 | self.count = Some( |
| 49 | self.count |
| 50 | .unwrap_or(0) |
| 51 | .saturating_mul(10) |
| 52 | .saturating_add(d as usize) |
| 53 | .min(9_999_999), |
| 54 | ); |
| 55 | return None; |
| 56 | } |
| 57 | } |
| 58 | let was_g = std::mem::take(&mut self.pending_g); |
| 59 | if !was_g && matches!(key, Key::Character(s) if s == "g") { |
| 60 | self.pending_g = true; |
| 61 | return None; |
| 62 | } |
| 63 | let count = self.count.take(); |
| 64 | let n = count.unwrap_or(1); |
| 65 | let action = match key { |
| 66 | Key::Character(s) => match s.as_str() { |
| 67 | "g" => count.map_or(Action::First, Action::Goto), |
| 68 | "q" => Action::Quit, |
| 69 | "n" => Action::Next(n), |
| 70 | "p" => Action::Prev(n), |
| 71 | "]" => Action::Next(10 * n), |
| 72 | "[" => Action::Prev(10 * n), |
| 73 | "G" => count.map_or(Action::Last, Action::Goto), |
| 74 | "h" => Action::Pan(Dir::Left), |
| 75 | "j" => Action::Pan(Dir::Down), |
| 76 | "k" => Action::Pan(Dir::Up), |
| 77 | "l" => Action::Pan(Dir::Right), |
| 78 | "H" => Action::PanEdge(Dir::Left), |
| 79 | "J" => Action::PanEdge(Dir::Down), |
| 80 | "K" => Action::PanEdge(Dir::Up), |
| 81 | "L" => Action::PanEdge(Dir::Right), |
| 82 | "+" => Action::Zoom(Zoom::In), |
| 83 | "-" => Action::Zoom(Zoom::Out), |
| 84 | "=" => Action::Zoom(Zoom::Percent(100.0)), |
| 85 | "w" => Action::Fit(FitMode::Fit), |
| 86 | "W" => Action::Fit(FitMode::Fill), |
| 87 | "e" => Action::Fit(FitMode::Width), |
| 88 | "E" => Action::Fit(FitMode::Height), |
| 89 | "z" => Action::Center, |
| 90 | "<" => Action::Rotate(-1), |
| 91 | ">" => Action::Rotate(1), |
| 92 | "|" => Action::Flip(Axis::Horizontal), |
| 93 | "_" => Action::Flip(Axis::Vertical), |
| 94 | "a" => Action::FilterToggle, |
| 95 | "A" => Action::AlphaBgCycle, |
| 96 | "f" => Action::Fullscreen, |
| 97 | "b" => Action::BarToggle, |
| 98 | "r" => Action::Reload, |
| 99 | "D" => Action::Remove, |
| 100 | _ => return None, |
| 101 | }, |
| 102 | Key::Named(named) => match named { |
| 103 | NamedKey::Backspace => Action::Prev(n), |
| 104 | NamedKey::Escape => Action::Cancel, |
| 105 | NamedKey::ArrowLeft => Action::Pan(Dir::Left), |
| 106 | NamedKey::ArrowDown => Action::Pan(Dir::Down), |
| 107 | NamedKey::ArrowUp => Action::Pan(Dir::Up), |
| 108 | NamedKey::ArrowRight => Action::Pan(Dir::Right), |
| 109 | NamedKey::Home => Action::First, |
| 110 | NamedKey::End => Action::Last, |
| 111 | _ => return None, |
| 112 | }, |
| 113 | _ => return None, |
| 114 | }; |
| 115 | Some(action) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /// Whether holding the key should repeat the action. |
| 120 | pub fn repeats(action: &Action) -> bool { |
| 121 | matches!( |
| 122 | action, |
| 123 | Action::Next(_) | Action::Prev(_) | Action::Zoom(Zoom::In | Zoom::Out) |
| 124 | ) |
| 125 | } |
| 126 | |
| 127 | #[cfg(test)] |
| 128 | mod tests { |
| 129 | use super::*; |
| 130 | |
| 131 | fn ch(s: &str) -> Key { |
| 132 | Key::Character(s.into()) |
| 133 | } |
| 134 | |
| 135 | #[test] |
| 136 | fn gg_is_a_sequence() { |
| 137 | let mut input = Input::default(); |
| 138 | let none = ModifiersState::empty(); |
| 139 | assert_eq!(input.key(&ch("g"), none), None); |
| 140 | assert_eq!(input.pending(), "g"); |
| 141 | assert_eq!(input.key(&ch("g"), none), Some(Action::First)); |
| 142 | assert_eq!(input.key(&ch("g"), none), None); |
| 143 | assert_eq!(input.key(&ch("n"), none), Some(Action::Next(1))); |
| 144 | assert_eq!(input.pending(), ""); |
| 145 | } |
| 146 | |
| 147 | #[test] |
| 148 | fn counts() { |
| 149 | let mut input = Input::default(); |
| 150 | let none = ModifiersState::empty(); |
| 151 | for k in ["1", "2"] { |
| 152 | assert_eq!(input.key(&ch(k), none), None); |
| 153 | } |
| 154 | assert_eq!(input.pending(), "12"); |
| 155 | assert_eq!(input.key(&ch("G"), none), Some(Action::Goto(12))); |
| 156 | assert_eq!(input.key(&ch("3"), none), None); |
| 157 | assert_eq!(input.key(&ch("n"), none), Some(Action::Next(3))); |
| 158 | assert_eq!(input.key(&ch("G"), none), Some(Action::Last)); |
| 159 | // `0` alone is not a count. |
| 160 | assert_eq!(input.key(&ch("0"), none), None); |
| 161 | assert_eq!(input.pending(), ""); |
| 162 | input.key(&ch("5"), none); |
| 163 | input.key(&ch("g"), none); |
| 164 | assert_eq!(input.pending(), "5g"); |
| 165 | assert_eq!(input.key(&ch("g"), none), Some(Action::Goto(5))); |
| 166 | } |
| 167 | |
| 168 | #[test] |
| 169 | fn modifiers_block_plain_bindings() { |
| 170 | let mut input = Input::default(); |
| 171 | assert_eq!(input.key(&ch("n"), ModifiersState::CONTROL), None); |
| 172 | } |
| 173 | } |