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