tui_pages/input/
registry.rs1use crate::input::{InputHint, KeyChord};
2use std::collections::{HashMap, HashSet};
3
4#[derive(Debug, Clone)]
5pub struct KeyMap<A> {
6 pub id: String,
7 pub bindings: HashMap<Vec<KeyChord>, A>,
8}
9
10impl<A> KeyMap<A> {
11 pub fn new(id: impl Into<String>) -> Self {
12 Self {
13 id: id.into(),
14 bindings: HashMap::new(),
15 }
16 }
17
18 pub fn bind(&mut self, sequence: impl Into<Vec<KeyChord>>, action: A) {
19 self.bindings.insert(sequence.into(), action);
20 }
21
22 pub fn unbind(&mut self, sequence: &[KeyChord]) -> Option<A> {
26 self.bindings.remove(sequence)
27 }
28}
29
30impl<A: PartialEq> KeyMap<A> {
31 pub fn bindings_for(&self, action: &A) -> Vec<&[KeyChord]> {
35 self.bindings
36 .iter()
37 .filter(|(_, bound)| *bound == action)
38 .map(|(sequence, _)| sequence.as_slice())
39 .collect()
40 }
41
42 pub fn unbind_action(&mut self, action: &A) -> usize {
46 let before = self.bindings.len();
47 self.bindings.retain(|_, bound| bound != action);
48 before - self.bindings.len()
49 }
50}
51
52#[derive(Debug, Clone)]
53pub struct InputRegistry<A> {
54 pub maps: HashMap<String, KeyMap<A>>,
55}
56
57impl<A> Default for InputRegistry<A> {
58 fn default() -> Self {
59 Self::empty()
60 }
61}
62
63impl<A> InputRegistry<A> {
64 pub fn empty() -> Self {
65 Self {
66 maps: HashMap::new(),
67 }
68 }
69
70 pub fn add_map(&mut self, map: KeyMap<A>) {
71 self.maps.insert(map.id.clone(), map);
72 }
73
74 pub fn map_mut(&mut self, id: impl Into<String>) -> &mut KeyMap<A> {
75 let id = id.into();
76 self.maps
77 .entry(id.clone())
78 .or_insert_with(|| KeyMap::new(id))
79 }
80
81 pub fn total_bindings(&self) -> usize {
82 self.maps.values().map(|map| map.bindings.len()).sum()
83 }
84}
85
86impl<A: Clone> InputRegistry<A> {
87 pub fn match_action(&self, sequence: &[KeyChord], modes: &[&str]) -> Option<A> {
88 for mode in modes {
89 if let Some(map) = self.maps.get(*mode) {
90 if let Some(action) = map.bindings.get(sequence) {
91 return Some(action.clone());
92 }
93 }
94 }
95 None
96 }
97
98 pub fn get_hints(&self, prefix: &[KeyChord], modes: &[&str]) -> Vec<InputHint<A>> {
99 let mut hints = Vec::new();
100 let mut seen_keys = HashSet::new();
101
102 for mode in modes {
103 if let Some(map) = self.maps.get(*mode) {
104 for (sequence, action) in &map.bindings {
105 if sequence.len() > prefix.len()
106 && sequence.starts_with(prefix)
107 && seen_keys.insert(sequence[prefix.len()])
108 {
109 hints.push(InputHint {
110 key: sequence[prefix.len()],
111 action: action.clone(),
112 });
113 }
114 }
115 }
116 }
117
118 hints
119 }
120
121 pub fn starts_sequence(&self, chord: &KeyChord, modes: &[&str]) -> bool {
122 let prefix = [*chord];
123 modes.iter().any(|mode| {
124 self.maps.get(*mode).is_some_and(|map| {
125 map.bindings
126 .keys()
127 .any(|sequence| sequence.starts_with(&prefix))
128 })
129 })
130 }
131
132 pub fn is_prefix(&self, sequence: &[KeyChord], modes: &[&str]) -> bool {
133 if sequence.is_empty() {
134 return false;
135 }
136
137 modes.iter().any(|mode| {
138 self.maps.get(*mode).is_some_and(|map| {
139 map.bindings.keys().any(|candidate| {
140 candidate.len() > sequence.len() && candidate.starts_with(sequence)
141 })
142 })
143 })
144 }
145}