1use std::{
2 collections::{HashMap, HashSet},
3 hash::Hash,
4};
5
6use crossterm::event::{KeyCode, KeyModifiers};
7
8use crate::{
9 app::{Action, InputMode, ListMode},
10 entry::{EntryKind, EntryRenderData},
11};
12
13#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
14pub struct KeyCombo {
15 pub key_code: KeyCode,
16 pub modifiers: KeyModifiers,
17}
18
19impl From<KeyCode> for KeyCombo {
20 fn from(key_code: KeyCode) -> Self {
21 KeyCombo {
22 key_code,
23 modifiers: KeyModifiers::NONE,
24 }
25 }
26}
27
28impl From<char> for KeyCombo {
29 fn from(c: char) -> Self {
30 KeyCombo {
31 key_code: KeyCode::Char(c),
32 modifiers: KeyModifiers::NONE,
33 }
34 }
35}
36
37impl From<(char, KeyModifiers)> for KeyCombo {
38 fn from((c, modifiers): (char, KeyModifiers)) -> Self {
39 KeyCombo {
40 key_code: KeyCode::Char(c),
41 modifiers,
42 }
43 }
44}
45
46impl From<(KeyCode, KeyModifiers)> for KeyCombo {
47 fn from((key_code, modifiers): (KeyCode, KeyModifiers)) -> Self {
48 KeyCombo {
49 key_code,
50 modifiers,
51 }
52 }
53}
54
55#[derive(Debug)]
56pub struct HotkeysTrieNode<T> {
57 pub children: HashMap<KeyCombo, HotkeysTrieNode<T>>,
58 pub value: Option<T>,
59}
60
61#[derive(Debug)]
62struct HotkeysTrie<T> {
63 root: HotkeysTrieNode<T>,
64}
65
66impl<T> HotkeysTrie<T> {
67 pub fn new() -> Self {
68 HotkeysTrie {
69 root: HotkeysTrieNode {
70 children: HashMap::new(),
71 value: None,
72 },
73 }
74 }
75
76 pub fn insert(&mut self, key_combos: &[KeyCombo], value: T) {
77 let mut current_node = &mut self.root;
79
80 for &key_combo in key_combos {
81 current_node = current_node
83 .children
84 .entry(key_combo)
85 .or_insert(HotkeysTrieNode {
86 children: HashMap::new(),
87 value: None,
88 });
89 }
90
91 current_node.value = Some(value);
93 }
94
95 pub fn get_value(&self, key_combos: &[KeyCombo]) -> Option<&T> {
96 let node = self.get_node(key_combos)?;
98 node.value.as_ref()
99 }
100
101 pub fn get_node(&self, key_combos: &[KeyCombo]) -> Option<&HotkeysTrieNode<T>> {
102 let mut current_node = &self.root;
104
105 for &key_combo in key_combos {
106 if let Some(node) = current_node.children.get(&key_combo) {
107 current_node = node;
108 } else {
109 return None;
110 }
111 }
112
113 Some(current_node)
114 }
115
116 pub fn clear(&mut self) {
117 self.root.children.clear();
118 self.root.value = None;
119 }
120}
121
122impl<T> Default for HotkeysTrie<T> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128#[derive(Debug)]
129pub struct HotkeysRegistry<C, T>
130where
131 C: Eq + Hash,
132 T: std::fmt::Debug,
133{
134 system_hotkeys: HashMap<C, HotkeysTrie<T>>,
137 system_hotkeys_count: usize,
138
139 entry_hotkeys: HotkeysTrie<T>,
142 entry_hotkeys_count: usize,
143}
144
145impl<C, T> HotkeysRegistry<C, T>
146where
147 C: Eq + Hash,
148 T: std::fmt::Debug,
149{
150 pub fn new() -> Self {
151 HotkeysRegistry {
152 system_hotkeys: HashMap::new(),
153 system_hotkeys_count: 0,
154 entry_hotkeys: HotkeysTrie::new(),
155 entry_hotkeys_count: 0,
156 }
157 }
158
159 pub fn register_system_hotkey(&mut self, context: C, key_combos: &[KeyCombo], value: T) {
160 self.system_hotkeys_count += 1;
161 let trie = self.system_hotkeys.entry(context).or_default();
162 trie.insert(key_combos, value);
163 }
164
165 pub fn register_entry_hotkey(&mut self, key_combos: &[KeyCombo], value: T) {
166 self.entry_hotkeys_count += 1;
167 self.entry_hotkeys.insert(key_combos, value);
168 }
169
170 pub fn clear_entry_hotkeys(&mut self) {
171 self.entry_hotkeys.clear();
172 self.entry_hotkeys_count = 0;
173 }
174
175 pub fn get_hotkey_value(&self, context: C, key_combos: &[KeyCombo]) -> Option<&T> {
176 if self.system_hotkeys_count == 0 && self.entry_hotkeys_count == 0 {
177 return None;
178 }
179
180 self.system_hotkeys
182 .get(&context)
183 .and_then(|trie| trie.get_value(key_combos))
184 .or_else(|| self.entry_hotkeys.get_value(key_combos))
185 }
186
187 pub fn get_hotkey_node(
188 &self,
189 context: C,
190 key_combos: &[KeyCombo],
191 ) -> Option<&HotkeysTrieNode<T>> {
192 if self.system_hotkeys_count == 0 && self.entry_hotkeys_count == 0 {
193 return None;
194 }
195
196 self.system_hotkeys
198 .get(&context)
199 .and_then(|trie| trie.get_node(key_combos))
200 .or_else(|| self.entry_hotkeys.get_node(key_combos))
201 }
202}
203
204impl<C, T> Default for HotkeysRegistry<C, T>
205where
206 C: Eq + Hash,
207 T: std::fmt::Debug,
208{
209 fn default() -> Self {
210 Self::new()
211 }
212}
213
214const fn key_combo_from_char(c: char) -> KeyCombo {
215 KeyCombo {
216 key_code: KeyCode::Char(c),
217 modifiers: KeyModifiers::NONE,
218 }
219}
220
221pub const PREFERRED_KEY_COMBOS_IN_ORDER: [KeyCombo; 31] = [
226 key_combo_from_char('a'),
227 key_combo_from_char('s'),
228 key_combo_from_char('w'),
229 key_combo_from_char('e'),
230 key_combo_from_char('r'),
231 key_combo_from_char('t'),
232 key_combo_from_char('z'),
233 key_combo_from_char('x'),
234 key_combo_from_char('c'),
235 key_combo_from_char('v'),
236 key_combo_from_char('b'),
237 key_combo_from_char('y'),
238 key_combo_from_char('u'),
239 key_combo_from_char('i'),
240 key_combo_from_char('o'),
241 key_combo_from_char('p'),
242 key_combo_from_char('n'),
243 key_combo_from_char('m'),
244 key_combo_from_char(','),
245 key_combo_from_char('1'),
246 key_combo_from_char('2'),
247 key_combo_from_char('3'),
248 key_combo_from_char('4'),
249 key_combo_from_char('5'),
250 key_combo_from_char('6'),
251 key_combo_from_char('7'),
252 key_combo_from_char('8'),
253 key_combo_from_char('9'),
254 key_combo_from_char('0'),
255 key_combo_from_char('-'),
256 key_combo_from_char('='),
257];
258
259impl HotkeysRegistry<InputMode, Action> {
260 pub fn new_with_default_system_hotkeys() -> Self {
261 let mut registry = HotkeysRegistry::new();
262
263 registry.register_system_hotkey(
264 InputMode::Normal,
265 &[KeyCombo::from('g'), KeyCombo::from('g')],
266 Action::SelectFirst,
267 );
268
269 registry.register_system_hotkey(
270 InputMode::Normal,
271 &[KeyCombo::from(KeyCode::Home)],
272 Action::SelectFirst,
273 );
274
275 registry.register_system_hotkey(
276 InputMode::Normal,
277 &[KeyCombo::from(('G', KeyModifiers::SHIFT))],
278 Action::SelectLast,
279 );
280
281 registry.register_system_hotkey(
282 InputMode::Normal,
283 &[KeyCombo::from(KeyCode::End)],
284 Action::SelectLast,
285 );
286
287 registry.register_system_hotkey(
288 InputMode::Normal,
289 &[KeyCombo::from('j')],
290 Action::SelectNext,
291 );
292
293 registry.register_system_hotkey(
294 InputMode::Normal,
295 &[KeyCombo::from(KeyCode::Down)],
296 Action::SelectNext,
297 );
298
299 registry.register_system_hotkey(
300 InputMode::Normal,
301 &[KeyCombo::from('k')],
302 Action::SelectPrevious,
303 );
304
305 registry.register_system_hotkey(
306 InputMode::Normal,
307 &[KeyCombo::from(KeyCode::Up)],
308 Action::SelectPrevious,
309 );
310
311 registry.register_system_hotkey(
312 InputMode::Normal,
313 &[KeyCombo::from(('d', KeyModifiers::CONTROL))],
314 Action::SwitchToListMode(ListMode::Directory),
315 );
316
317 registry.register_system_hotkey(
318 InputMode::Normal,
319 &[KeyCombo::from('l')],
320 Action::ChangeDirectoryToSelectedEntry,
321 );
322
323 registry.register_system_hotkey(
324 InputMode::Normal,
325 &[KeyCombo::from(KeyCode::Right)],
326 Action::ChangeDirectoryToSelectedEntry,
327 );
328
329 registry.register_system_hotkey(
330 InputMode::Normal,
331 &[KeyCombo::from('h')],
332 Action::ChangeDirectoryToParent,
333 );
334
335 registry.register_system_hotkey(
336 InputMode::Normal,
337 &[KeyCombo::from(KeyCode::Left)],
338 Action::ChangeDirectoryToParent,
339 );
340
341 registry.register_system_hotkey(
342 InputMode::Normal,
343 &[KeyCombo::from(KeyCode::Backspace)],
344 Action::ChangeDirectoryToParent,
345 );
346
347 registry.register_system_hotkey(
348 InputMode::Normal,
349 &[KeyCombo::from(('f', KeyModifiers::CONTROL))],
350 Action::SwitchToListMode(ListMode::Frecent),
351 );
352
353 registry.register_system_hotkey(
354 InputMode::Normal,
355 &[KeyCombo::from('?')],
356 Action::ToggleHelp,
357 );
358
359 registry.register_system_hotkey(
360 InputMode::Normal,
361 &[KeyCombo::from('/')],
362 Action::SwitchToInputMode(InputMode::Search),
363 );
364
365 registry.register_system_hotkey(
366 InputMode::Normal,
367 &[KeyCombo::from(KeyCode::Esc)],
368 Action::Exit,
369 );
370
371 registry.register_system_hotkey(InputMode::Normal, &[KeyCombo::from('q')], Action::Exit);
372
373 registry.register_system_hotkey(
374 InputMode::Normal,
375 &[KeyCombo::from(KeyCode::Enter)],
376 Action::ChangeDirectoryToSelectedEntry,
377 );
378
379 registry.register_system_hotkey(
380 InputMode::Normal,
381 &[KeyCombo::from('_')],
382 Action::ResetSearchInput,
383 );
384
385 registry.register_system_hotkey(
386 InputMode::Search,
387 &[KeyCombo::from(KeyCode::Enter)],
388 Action::ChangeDirectoryToSelectedEntry,
389 );
390
391 registry.register_system_hotkey(
392 InputMode::Search,
393 &[KeyCombo::from(KeyCode::Right)],
394 Action::ChangeDirectoryToSelectedEntry,
395 );
396
397 registry.register_system_hotkey(
398 InputMode::Search,
399 &[KeyCombo::from(KeyCode::Down)],
400 Action::SelectNext,
401 );
402
403 registry.register_system_hotkey(
404 InputMode::Search,
405 &[KeyCombo::from(KeyCode::Up)],
406 Action::SelectPrevious,
407 );
408
409 registry.register_system_hotkey(
410 InputMode::Search,
411 &[KeyCombo::from(KeyCode::Esc)],
412 Action::ExitSearchInput,
413 );
414
415 registry.register_system_hotkey(
416 InputMode::Search,
417 &[KeyCombo::from(KeyCode::Backspace)],
418 Action::SearchInputBackspace,
419 );
420
421 registry
422 }
423
424 fn generate_sequence_permutations(
425 key_combos: &[KeyCombo],
426 length: usize,
427 ) -> Vec<Vec<KeyCombo>> {
428 let mut result = Vec::new();
429 let mut current = vec![key_combos[0]; length];
430
431 fn generate(
432 key_combos: &[KeyCombo],
433 current: &mut Vec<KeyCombo>,
434 result: &mut Vec<Vec<KeyCombo>>,
435 pos: usize,
436 ) {
437 if pos == current.len() {
438 result.push(current.clone());
439 return;
440 }
441
442 for &key_combo in key_combos {
443 current[pos] = key_combo;
444 generate(key_combos, current, result, pos + 1);
445 }
446 }
447
448 generate(key_combos, &mut current, &mut result, 0);
449 result
450 }
451
452 pub fn assign_hotkeys(
453 &mut self,
454 entry_render_data: &mut [EntryRenderData],
455 preferred_key_combos_in_order: &[KeyCombo],
456 ) {
457 self.clear_entry_hotkeys();
458
459 let mut directory_indexes: Vec<usize> = Vec::new();
460
461 for (i, entry_render_datum) in entry_render_data.iter().enumerate() {
462 if *entry_render_datum.kind == EntryKind::Directory {
463 directory_indexes.push(i);
464 }
465 }
466
467 let directory_indexes_count = directory_indexes.len();
468
469 if directory_indexes_count == 0 {
470 return;
472 }
473
474 let illegal_key_codes = entry_render_data
477 .iter()
478 .filter_map(|x| x.illegal_char_for_hotkey)
479 .map(KeyCode::Char)
480 .collect::<HashSet<_>>();
481
482 let mut available_key_combos: Vec<KeyCombo> = Vec::new();
483
484 for &key_combo in preferred_key_combos_in_order.iter() {
485 if !illegal_key_codes.contains(&key_combo.key_code) {
486 available_key_combos.push(key_combo);
487 }
488 }
489
490 let available_key_codes_count = available_key_combos.len();
491 if available_key_codes_count < 2 && directory_indexes_count > 1 {
492 return;
495 }
496
497 let mut sequence_length = 1;
498
499 while available_key_codes_count.pow(sequence_length) < directory_indexes_count {
500 sequence_length += 1;
501 }
502
503 let permutations = Self::generate_sequence_permutations(
504 available_key_combos.as_slice(),
505 sequence_length as usize,
506 );
507
508 assert!(permutations.len() >= directory_indexes_count);
509
510 let mut i = 0;
511 while i < directory_indexes_count {
512 let directory_index = directory_indexes[i];
514 entry_render_data[directory_index].key_combo_sequence = Some(permutations[i].clone());
515 self.register_entry_hotkey(
516 permutations[i].as_slice(),
517 Action::ChangeDirectoryToEntryWithIndex(directory_index),
518 );
519 i += 1;
520 }
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use std::path::PathBuf;
527
528 use crate::entry::Entry;
529
530 use super::*;
531
532 #[test]
533 fn hotkeys_trie_works_correctly() {
534 let mut trie = HotkeysTrie::new();
535 trie.insert(&[KeyCombo::from('a'), KeyCombo::from('b')], 1);
536
537 trie.insert(&[KeyCombo::from('a'), KeyCombo::from('c')], 2);
538
539 trie.insert(&[KeyCombo::from('c'), KeyCombo::from('d')], 3);
540
541 trie.insert(&[KeyCombo::from('a'), KeyCombo::from('z')], 1);
542
543 assert_eq!(
544 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('b'),]),
545 Some(&1)
546 );
547 assert_eq!(
548 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('c'),]),
549 Some(&2)
550 );
551 assert_eq!(
552 trie.get_value(&[KeyCombo::from('c'), KeyCombo::from('d'),]),
553 Some(&3)
554 );
555
556 assert_eq!(
557 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('d'),]),
558 None
559 );
560
561 assert_eq!(
562 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('z'),]),
563 Some(&1)
564 );
565 }
566
567 #[test]
568 fn hotkeys_trie_clear_works_correctly() {
569 let mut trie = HotkeysTrie::new();
570 trie.insert(&[KeyCombo::from('a'), KeyCombo::from('b')], 1);
571
572 assert_eq!(
573 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('b'),]),
574 Some(&1)
575 );
576
577 trie.clear();
578
579 assert_eq!(
580 trie.get_value(&[KeyCombo::from('a'), KeyCombo::from('b'),]),
581 None
582 );
583 }
584
585 #[test]
586 fn generate_sequence_permutations_works_correctly() {
587 let available_key_combos = &[
588 KeyCombo::from('a'),
589 KeyCombo::from('b'),
590 KeyCombo::from('c'),
591 ];
592
593 let result: Vec<Vec<KeyCombo>> =
594 HotkeysRegistry::generate_sequence_permutations(available_key_combos, 1);
595
596 assert_eq!(result.len(), 3);
597 assert_eq!(
598 result[0],
599 vec![KeyCombo {
600 key_code: KeyCode::Char('a'),
601 modifiers: KeyModifiers::NONE
602 }]
603 );
604 assert_eq!(
605 result[1],
606 vec![KeyCombo {
607 key_code: KeyCode::Char('b'),
608 modifiers: KeyModifiers::NONE
609 }]
610 );
611 assert_eq!(
612 result[2],
613 vec![KeyCombo {
614 key_code: KeyCode::Char('c'),
615 modifiers: KeyModifiers::NONE
616 }]
617 );
618
619 let result: Vec<Vec<KeyCombo>> =
620 HotkeysRegistry::generate_sequence_permutations(available_key_combos, 2);
621
622 assert_eq!(result.len(), 9);
623
624 let expected_characters = [
625 ['a', 'a'],
626 ['a', 'b'],
627 ['a', 'c'],
628 ['b', 'a'],
629 ['b', 'b'],
630 ['b', 'c'],
631 ['c', 'a'],
632 ['c', 'b'],
633 ['c', 'c'],
634 ];
635
636 for (i, key_combos) in result.iter().enumerate() {
637 assert_eq!(key_combos.len(), 2);
638 assert_eq!(
639 key_combos[0].key_code,
640 KeyCode::Char(expected_characters[i][0])
641 );
642 assert_eq!(
643 key_combos[1].key_code,
644 KeyCode::Char(expected_characters[i][1])
645 );
646 }
647
648 let result: Vec<Vec<KeyCombo>> =
649 HotkeysRegistry::generate_sequence_permutations(available_key_combos, 3);
650
651 assert_eq!(result.len(), 27);
652
653 let expected_characters = [
654 ['a', 'a', 'a'],
655 ['a', 'a', 'b'],
656 ['a', 'a', 'c'],
657 ['a', 'b', 'a'],
658 ['a', 'b', 'b'],
659 ['a', 'b', 'c'],
660 ['a', 'c', 'a'],
661 ['a', 'c', 'b'],
662 ['a', 'c', 'c'],
663 ['b', 'a', 'a'],
664 ['b', 'a', 'b'],
665 ['b', 'a', 'c'],
666 ['b', 'b', 'a'],
667 ['b', 'b', 'b'],
668 ['b', 'b', 'c'],
669 ['b', 'c', 'a'],
670 ['b', 'c', 'b'],
671 ['b', 'c', 'c'],
672 ['c', 'a', 'a'],
673 ['c', 'a', 'b'],
674 ['c', 'a', 'c'],
675 ['c', 'b', 'a'],
676 ['c', 'b', 'b'],
677 ['c', 'b', 'c'],
678 ['c', 'c', 'a'],
679 ['c', 'c', 'b'],
680 ['c', 'c', 'c'],
681 ];
682
683 for (i, key_combos) in result.iter().enumerate() {
684 assert_eq!(key_combos.len(), 3);
685 assert_eq!(
686 key_combos[0].key_code,
687 KeyCode::Char(expected_characters[i][0])
688 );
689 assert_eq!(
690 key_combos[1].key_code,
691 KeyCode::Char(expected_characters[i][1])
692 );
693 assert_eq!(
694 key_combos[2].key_code,
695 KeyCode::Char(expected_characters[i][2])
696 );
697 }
698
699 let result: Vec<Vec<KeyCombo>> =
700 HotkeysRegistry::generate_sequence_permutations(available_key_combos, 4);
701
702 assert_eq!(result.len(), 81);
703 }
704
705 #[test]
706 fn assign_hotkeys_works_correctly() {
707 let entries = [
708 Entry {
709 name: "s-dir1".into(),
710 kind: EntryKind::Directory,
711 path: PathBuf::from("/home/user/s-dir/"),
712 },
713 Entry {
714 name: "d-dir2".into(),
715 kind: EntryKind::Directory,
716 path: PathBuf::from("/home/user/d-dir/"),
717 },
718 Entry {
719 name: "w-dir3".into(),
720 kind: EntryKind::Directory,
721 path: PathBuf::from("/home/user/w-dir/"),
722 },
723 Entry {
724 name: "e-dir4".into(),
725 kind: EntryKind::Directory,
726 path: PathBuf::from("/home/user/e-dir/"),
727 },
728 Entry {
729 name: "r-dir5".into(),
730 kind: EntryKind::Directory,
731 path: PathBuf::from("/home/user/Cargo.toml"),
732 },
733 Entry {
734 name: "Cargo.toml".into(),
735 kind: EntryKind::File {
736 extension: Some("toml".into()),
737 },
738 path: PathBuf::from("/home/user/Cargo.toml"),
739 },
740 ];
741
742 let mut entry_render_data: Vec<EntryRenderData> = entries
743 .iter()
744 .map(|entry| EntryRenderData::from_entry(entry, ""))
745 .collect();
746
747 let mut hotkeys_registry = HotkeysRegistry::new();
748
749 hotkeys_registry.assign_hotkeys(
750 &mut entry_render_data,
751 &[
752 KeyCombo::from('b'),
753 KeyCombo::from('a'),
754 KeyCombo::from('c'),
755 KeyCombo::from('y'),
756 ],
757 );
758
759 assert_eq!(hotkeys_registry.entry_hotkeys_count, 5);
760
761 assert_eq!(
762 entry_render_data[0].key_combo_sequence,
763 Some(vec![KeyCombo::from('b'), KeyCombo::from('b')])
764 );
765
766 assert_eq!(
767 entry_render_data[1].key_combo_sequence,
768 Some(vec![KeyCombo::from('b'), KeyCombo::from('a')])
769 );
770
771 assert_eq!(
772 entry_render_data[2].key_combo_sequence,
773 Some(vec![KeyCombo::from('b'), KeyCombo::from('y')])
774 );
775
776 assert_eq!(
777 entry_render_data[3].key_combo_sequence,
778 Some(vec![KeyCombo::from('a'), KeyCombo::from('b')])
779 );
780
781 assert_eq!(
782 entry_render_data[4].key_combo_sequence,
783 Some(vec![KeyCombo::from('a'), KeyCombo::from('a')])
784 );
785
786 assert_eq!(entry_render_data[5].key_combo_sequence, None);
787 }
788}