1use std::collections::HashMap;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use std::sync::Arc;
6
7use indexmap::IndexMap;
8use thiserror::Error;
9
10use crate::action::Action;
11use crate::file::FileIndex;
12
13pub use termwiz::input::KeyCode;
16
17pub use termwiz::input::Modifiers;
20
21#[derive(Debug, Error)]
23pub enum BindingError {
24 #[error("invalid keybinding: {0}")]
26 Invalid(String),
27
28 #[error("{0} missing parameter {1}")]
30 MissingParameter(String, usize),
31
32 #[error("invalid integer")]
34 InvalidInt(#[from] std::num::ParseIntError),
35
36 #[error("invalid {binding} parameter {index}")]
38 ForParameter {
39 #[source]
41 error: Box<BindingError>,
42
43 binding: String,
45
46 index: usize,
48 },
49}
50
51impl BindingError {
52 fn for_parameter(self, binding: String, index: usize) -> Self {
53 Self::ForParameter {
54 error: Box::new(self),
55 binding,
56 index,
57 }
58 }
59}
60
61type Result<T> = std::result::Result<T, BindingError>;
62
63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
67pub enum Category {
68 None,
70
71 General,
73
74 Navigation,
76
77 Presentation,
79
80 Searching,
82
83 Hidden,
85}
86
87impl Category {
88 pub(crate) fn categories() -> impl Iterator<Item = Category> {
90 [
91 Category::General,
92 Category::Navigation,
93 Category::Presentation,
94 Category::Searching,
95 Category::None,
96 ]
97 .iter()
98 .cloned()
99 }
100}
101
102impl std::fmt::Display for Category {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match *self {
105 Category::None => f.write_str("Other"),
106 Category::General => f.write_str("General"),
107 Category::Navigation => f.write_str("Navigation"),
108 Category::Presentation => f.write_str("Presentation"),
109 Category::Searching => f.write_str("Searching"),
110 Category::Hidden => f.write_str("Hidden"),
111 }
112 }
113}
114
115#[derive(Clone, Debug, Hash, PartialEq, Eq)]
117pub enum Binding {
118 Action(Action),
120
121 Custom(CustomBinding),
123
124 Unrecognized(String),
126}
127
128impl Binding {
129 pub fn custom(
136 category: Category,
137 description: impl Into<String>,
138 callback: impl Fn(FileIndex) + Send + Sync + 'static,
139 ) -> Self {
140 Binding::Custom(CustomBinding::new(category, description, callback))
141 }
142
143 pub(crate) fn category(&self) -> Category {
144 match self {
145 Binding::Action(action) => {
146 use Action::*;
147 match action {
148 Quit | Refresh | Help | Cancel => Category::General,
149 PreviousFile
150 | NextFile
151 | ScrollUpLines(_)
152 | ScrollDownLines(_)
153 | ScrollUpScreenFraction(_)
154 | ScrollDownScreenFraction(_)
155 | ScrollToTop
156 | ScrollToBottom
157 | ScrollLeftColumns(_)
158 | ScrollRightColumns(_)
159 | ScrollLeftScreenFraction(_)
160 | ScrollRightScreenFraction(_)
161 | PromptGoToLine => Category::Navigation,
162 ToggleRuler | ToggleLineNumbers | ToggleLineWrapping => Category::Presentation,
163 PromptSearchFromStart
164 | PromptSearchForwards
165 | PromptSearchBackwards
166 | NextMatch
167 | PreviousMatch
168 | NextMatchLine
169 | PreviousMatchLine
170 | PreviousMatchScreen
171 | NextMatchScreen
172 | FirstMatch
173 | LastMatch => Category::Searching,
174 AppendDigitToRepeatCount(_) => Category::Hidden,
175 }
176 }
177 Binding::Custom(binding) => binding.category,
178 Binding::Unrecognized(_) => Category::None,
179 }
180 }
181
182 pub fn parse(ident: String, params: Vec<String>) -> Result<Self> {
184 use Action::*;
185
186 let param_usize = |index| -> Result<usize> {
187 let value: &String = params
188 .get(index)
189 .ok_or_else(|| BindingError::MissingParameter(ident.clone(), index))?;
190 let value = value
191 .parse::<usize>()
192 .map_err(|err| BindingError::from(err).for_parameter(ident.clone(), index))?;
193 Ok(value)
194 };
195
196 let action = match ident.as_str() {
197 "Quit" => Quit,
198 "Refresh" => Refresh,
199 "Help" => Help,
200 "Cancel" => Cancel,
201 "PreviousFile" => PreviousFile,
202 "NextFile" => NextFile,
203 "ToggleRuler" => ToggleRuler,
204 "ScrollUpLines" => ScrollUpLines(param_usize(0)?),
205 "ScrollDownLines" => ScrollDownLines(param_usize(0)?),
206 "ScrollUpScreenFraction" => ScrollUpScreenFraction(param_usize(0)?),
207 "ScrollDownScreenFraction" => ScrollDownScreenFraction(param_usize(0)?),
208 "ScrollToTop" => ScrollToTop,
209 "ScrollToBottom" => ScrollToBottom,
210 "ScrollLeftColumns" => ScrollLeftColumns(param_usize(0)?),
211 "ScrollRightColumns" => ScrollRightColumns(param_usize(0)?),
212 "ScrollLeftScreenFraction" => ScrollLeftScreenFraction(param_usize(0)?),
213 "ScrollRightScreenFraction" => ScrollRightScreenFraction(param_usize(0)?),
214 "ToggleLineNumbers" => ToggleLineNumbers,
215 "ToggleLineWrapping" => ToggleLineWrapping,
216 "PromptGoToLine" => PromptGoToLine,
217 "PromptSearchFromStart" => PromptSearchFromStart,
218 "PromptSearchForwards" => PromptSearchForwards,
219 "PromptSearchBackwards" => PromptSearchBackwards,
220 "PreviousMatch" => PreviousMatch,
221 "NextMatch" => NextMatch,
222 "PreviousMatchLine" => PreviousMatchLine,
223 "NextMatchLine" => NextMatchLine,
224 "FirstMatch" => FirstMatch,
225 "LastMatch" => LastMatch,
226 _ => return Ok(Binding::Unrecognized(ident)),
227 };
228
229 Ok(Binding::Action(action))
230 }
231}
232
233impl From<Action> for Binding {
234 fn from(action: Action) -> Binding {
235 Binding::Action(action)
236 }
237}
238
239impl From<Action> for Option<Binding> {
240 fn from(action: Action) -> Option<Binding> {
241 Some(Binding::Action(action))
242 }
243}
244
245impl std::fmt::Display for Binding {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 match *self {
248 Binding::Action(ref a) => write!(f, "{}", a),
249 Binding::Custom(ref b) => write!(f, "{}", b.description),
250 Binding::Unrecognized(ref s) => write!(f, "Unrecognized binding ({})", s),
251 }
252 }
253}
254
255static CUSTOM_BINDING_ID: AtomicUsize = AtomicUsize::new(0);
256
257#[derive(Clone)]
260pub struct CustomBinding {
261 id: usize,
263
264 category: Category,
266
267 description: String,
269
270 callback: Arc<dyn Fn(FileIndex) + Sync + Send>,
272}
273
274impl CustomBinding {
275 pub fn new(
280 category: Category,
281 description: impl Into<String>,
282 callback: impl Fn(FileIndex) + Sync + Send + 'static,
283 ) -> CustomBinding {
284 CustomBinding {
285 id: CUSTOM_BINDING_ID.fetch_add(1, Ordering::SeqCst),
286 category,
287 description: description.into(),
288 callback: Arc::new(callback),
289 }
290 }
291
292 pub fn run(&self, file_index: FileIndex) {
294 (self.callback)(file_index)
295 }
296}
297
298impl PartialEq for CustomBinding {
299 fn eq(&self, other: &Self) -> bool {
300 self.id == other.id
301 }
302}
303
304impl Eq for CustomBinding {}
305
306impl std::hash::Hash for CustomBinding {
307 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
308 self.id.hash(state);
309 }
310}
311
312impl std::fmt::Debug for CustomBinding {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 f.debug_tuple("CustomBinding")
315 .field(&self.id)
316 .field(&self.description)
317 .finish()
318 }
319}
320
321#[derive(Clone, Debug)]
324#[doc(hidden)]
325pub struct BindingConfig {
326 pub binding: Binding,
328
329 pub visible: bool,
331}
332
333#[derive(PartialEq, Eq)]
335pub struct Keymap {
336 bindings: HashMap<(Modifiers, KeyCode), Binding>,
338
339 keys: IndexMap<Binding, Vec<(Modifiers, KeyCode)>>,
341}
342
343impl<'a, I: IntoIterator<Item = &'a ((Modifiers, KeyCode), BindingConfig)>> From<I> for Keymap {
344 fn from(iter: I) -> Keymap {
345 let iter = iter.into_iter();
346 let size_hint = iter.size_hint();
347 let mut bindings = HashMap::with_capacity(size_hint.0);
348 let mut keys = IndexMap::with_capacity(size_hint.0);
349 for &((modifiers, keycode), ref binding_config) in iter {
350 bindings.insert((modifiers, keycode), binding_config.binding.clone());
351 if binding_config.visible {
352 keys.entry(binding_config.binding.clone())
353 .or_insert_with(Vec::new)
354 .push((modifiers, keycode));
355 }
356 }
357 Keymap { bindings, keys }
358 }
359}
360
361impl Keymap {
362 pub fn new() -> Self {
364 Keymap {
365 bindings: HashMap::new(),
366 keys: IndexMap::new(),
367 }
368 }
369
370 pub fn get(&self, modifiers: Modifiers, keycode: KeyCode) -> Option<&Binding> {
372 self.bindings.get(&(modifiers, keycode))
373 }
374
375 pub fn bind(
377 &mut self,
378 modifiers: Modifiers,
379 keycode: KeyCode,
380 binding: impl Into<Option<Binding>>,
381 ) -> &mut Self {
382 self.bind_impl(modifiers, keycode, binding.into(), true)
383 }
384
385 pub fn bind_hidden(
387 &mut self,
388 modifiers: Modifiers,
389 keycode: KeyCode,
390 binding: impl Into<Option<Binding>>,
391 ) -> &mut Self {
392 self.bind_impl(modifiers, keycode, binding.into(), false)
393 }
394
395 fn bind_impl(
396 &mut self,
397 modifiers: Modifiers,
398 keycode: KeyCode,
399 binding: Option<Binding>,
400 visible: bool,
401 ) -> &mut Self {
402 if let Some(old_binding) = self.bindings.remove(&(modifiers, keycode)) {
403 if let Some(keys) = self.keys.get_mut(&old_binding) {
404 keys.retain(|&item| item != (modifiers, keycode));
405 }
406 }
407 if let Some(binding) = binding {
408 self.bindings.insert((modifiers, keycode), binding.clone());
409 if visible {
410 self.keys
411 .entry(binding)
412 .or_insert_with(Vec::new)
413 .push((modifiers, keycode));
414 }
415 }
416 self
417 }
418
419 pub(crate) fn iter_keys(&self) -> impl Iterator<Item = (&Binding, &Vec<(Modifiers, KeyCode)>)> {
420 self.keys.iter()
421 }
422}
423
424impl Default for Keymap {
425 fn default() -> Self {
426 Keymap::from(crate::keymaps::default::KEYMAP.iter())
427 }
428}
429
430impl std::fmt::Debug for Keymap {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 f.debug_tuple("Keymap")
433 .field(&format!("<{} keys bound>", self.bindings.len()))
434 .finish()
435 }
436}