Krolyxon
Commit f2bd31a

Branches

Tags

Refs →

remagesrcinputmod.rs

173 lines · 6.0 KB · Rust

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
5pub mod keymap;
6pub mod keys;
7
8use winit::keyboard::{Key, ModifiersState, NamedKey};
9
10use crate::action::{Action, Zoom};
11use crate::state::view::{Axis, Dir, FitMode};
12
13#[derive(Debug, Default)]
14pub 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
21impl 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.
120pub 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)]
128mod 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}