1use crate::input::InputRegistry;
11use crate::input::KeyChord;
12
13#[cfg(feature = "canvas")]
14use crate::canvas::CanvasAction;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum BindingSource {
20 Builtin,
22 Config,
24 CanvasBuiltin,
26 Runtime,
28 Unknown,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum BindingLayer {
37 Keymap,
38 Canvas,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct BindingInfo<A> {
45 pub layer: BindingLayer,
46 pub mode: String,
47 pub sequence: Vec<KeyChord>,
48 pub action: A,
49 pub source: BindingSource,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum CanvasRoutingPrecedence {
56 KeymapFirst,
58 CanvasFirst,
60 StickyOwner,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum BindingConflict<A> {
69 SameModeDuplicate {
71 mode: String,
72 sequence: Vec<KeyChord>,
73 first: A,
74 second: A,
75 },
76 ActiveModeShadow {
79 sequence: Vec<KeyChord>,
80 first_mode: String,
81 first: A,
82 shadowed_mode: String,
83 shadowed: A,
84 },
85 #[cfg(feature = "canvas")]
87 CanvasOverlap {
88 mode: String,
89 sequence: Vec<KeyChord>,
90 keymap_action: A,
91 canvas_action: CanvasAction,
92 routing: CanvasRoutingPrecedence,
93 },
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct BindingCatalog<A> {
100 pub bindings: Vec<BindingInfo<A>>,
101}
102
103impl<A> Default for BindingCatalog<A> {
104 fn default() -> Self {
105 Self {
106 bindings: Vec::new(),
107 }
108 }
109}
110
111impl<A: Clone> BindingCatalog<A> {
112 pub fn from_registry(registry: &InputRegistry<A>, source: BindingSource) -> Self {
115 let mut bindings = Vec::new();
116 for map in registry.maps.values() {
117 for (sequence, action) in &map.bindings {
118 bindings.push(BindingInfo {
119 layer: BindingLayer::Keymap,
120 mode: map.id.clone(),
121 sequence: sequence.clone(),
122 action: action.clone(),
123 source,
124 });
125 }
126 }
127 Self { bindings }
128 }
129
130 pub fn push(&mut self, info: BindingInfo<A>) {
133 self.bindings.push(info);
134 }
135
136 pub fn extend(&mut self, other: BindingCatalog<A>) {
139 self.bindings.extend(other.bindings);
140 }
141}
142
143impl<A> BindingCatalog<A> {
144 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn bindings_for_mode(&self, mode: &str) -> Vec<&BindingInfo<A>> {
149 self.bindings
150 .iter()
151 .filter(|info| info.mode == mode)
152 .collect()
153 }
154
155 pub fn bindings_for_sequence(&self, mode: &str, sequence: &[KeyChord]) -> Vec<&BindingInfo<A>> {
156 self.bindings
157 .iter()
158 .filter(|info| info.mode == mode && info.sequence == sequence)
159 .collect()
160 }
161}
162
163impl<A: PartialEq> BindingCatalog<A> {
164 pub fn bindings_for_action(&self, action: &A) -> Vec<&BindingInfo<A>> {
165 self.bindings
166 .iter()
167 .filter(|info| &info.action == action)
168 .collect()
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct BindableActionInfo<A> {
176 pub action: A,
177 pub name: &'static str,
178 pub description: &'static str,
179 pub modes: &'static [&'static str],
180}
181
182const NAV_FOCUS_MODES: &[&str] = &["general"];
183const NAV_GLOBAL_MODES: &[&str] = &["global"];
184
185impl<A> BindableActionInfo<A> {
186 pub fn new(action: A, name: &'static str, description: &'static str) -> Self {
195 Self {
196 action,
197 name,
198 description,
199 modes: NAV_GLOBAL_MODES,
200 }
201 }
202
203 pub fn modes(mut self, modes: &'static [&'static str]) -> Self {
205 self.modes = modes;
206 self
207 }
208}
209
210pub fn navigation_bindable_actions<A>() -> Vec<BindableActionInfo<A>>
213where
214 A: From<crate::keybindings::NavigationAction>,
215{
216 crate::keybindings::NavigationAction::infos()
217 .iter()
218 .map(|info| BindableActionInfo {
219 action: A::from(info.action),
220 name: info.name,
221 description: info.description,
222 modes: match info.category {
223 "Focus" => NAV_FOCUS_MODES,
224 _ => NAV_GLOBAL_MODES,
225 },
226 })
227 .collect()
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct BindingAnalysis<A> {
233 pub bindings: Vec<BindingInfo<A>>,
234 pub conflicts: Vec<BindingConflict<A>>,
235}
236
237pub fn analyze_keymap_bindings<A>(
247 catalog: &BindingCatalog<A>,
248 active_modes: &[impl AsRef<str>],
249) -> BindingAnalysis<A>
250where
251 A: Clone + PartialEq,
252{
253 let mut conflicts = Vec::new();
254
255 let mut seen: Vec<(&str, &[KeyChord], &A)> = Vec::new();
257 for info in &catalog.bindings {
258 if info.layer != BindingLayer::Keymap {
259 continue;
260 }
261 if let Some((_, _, existing)) = seen
262 .iter()
263 .find(|(mode, seq, _)| *mode == info.mode.as_str() && *seq == info.sequence.as_slice())
264 {
265 if **existing != info.action {
266 conflicts.push(BindingConflict::SameModeDuplicate {
267 mode: info.mode.clone(),
268 sequence: info.sequence.clone(),
269 first: (*existing).clone(),
270 second: info.action.clone(),
271 });
272 }
273 } else {
274 seen.push((info.mode.as_str(), info.sequence.as_slice(), &info.action));
275 }
276 }
277
278 let active: Vec<&str> = active_modes.iter().map(|m| m.as_ref()).collect();
283 let mut handled: Vec<&[KeyChord]> = Vec::new();
284 for info in &catalog.bindings {
285 if info.layer != BindingLayer::Keymap || !active.contains(&info.mode.as_str()) {
286 continue;
287 }
288 if handled.iter().any(|seq| *seq == info.sequence.as_slice()) {
289 continue;
290 }
291 handled.push(info.sequence.as_slice());
292
293 let mut hits: Vec<&BindingInfo<A>> = active
296 .iter()
297 .filter_map(|mode| {
298 catalog.bindings.iter().find(|other| {
299 other.layer == BindingLayer::Keymap
300 && other.mode == *mode
301 && other.sequence == info.sequence
302 })
303 })
304 .collect();
305 hits.dedup_by(|a, b| a.mode == b.mode);
307
308 if hits.len() < 2 {
309 continue;
310 }
311 let winner = hits[0];
312 for shadowed in &hits[1..] {
313 conflicts.push(BindingConflict::ActiveModeShadow {
314 sequence: info.sequence.clone(),
315 first_mode: winner.mode.clone(),
316 first: winner.action.clone(),
317 shadowed_mode: shadowed.mode.clone(),
318 shadowed: shadowed.action.clone(),
319 });
320 }
321 }
322
323 BindingAnalysis {
324 bindings: catalog.bindings.clone(),
325 conflicts,
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::input::{InputRegistry, KeyMap, try_parse_binding};
333
334 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
335 enum TestAction {
336 A,
337 B,
338 }
339
340 fn seq(binding: &str) -> Vec<KeyChord> {
341 try_parse_binding(binding).unwrap()
342 }
343
344 fn registry() -> InputRegistry<TestAction> {
345 let mut reg = InputRegistry::empty();
346 let mut general = KeyMap::new("general");
347 general.bind(seq("ctrl+a"), TestAction::A);
348 reg.add_map(general);
349 let mut global = KeyMap::new("global");
350 global.bind(seq("ctrl+a"), TestAction::B);
351 reg.add_map(global);
352 reg
353 }
354
355 #[test]
356 fn catalog_records_layer_and_source() {
357 let catalog = BindingCatalog::from_registry(®istry(), BindingSource::Config);
358 assert_eq!(catalog.bindings.len(), 2);
359 assert!(
360 catalog
361 .bindings
362 .iter()
363 .all(|b| b.layer == BindingLayer::Keymap && b.source == BindingSource::Config)
364 );
365 assert_eq!(catalog.bindings_for_mode("general").len(), 1);
366 assert_eq!(catalog.bindings_for_action(&TestAction::A).len(), 1);
367 assert_eq!(
368 catalog
369 .bindings_for_sequence("global", &seq("ctrl+a"))
370 .len(),
371 1
372 );
373 }
374
375 #[test]
376 fn active_mode_shadow_ordered_by_priority() {
377 let catalog = BindingCatalog::from_registry(®istry(), BindingSource::Config);
378 let analysis = analyze_keymap_bindings(&catalog, &["general", "global"]);
379 let shadows: Vec<_> = analysis
380 .conflicts
381 .iter()
382 .filter(|c| matches!(c, BindingConflict::ActiveModeShadow { .. }))
383 .collect();
384 assert_eq!(shadows.len(), 1);
385 match shadows[0] {
386 BindingConflict::ActiveModeShadow {
387 first_mode,
388 shadowed_mode,
389 ..
390 } => {
391 assert_eq!(first_mode, "general");
392 assert_eq!(shadowed_mode, "global");
393 }
394 _ => unreachable!(),
395 }
396 }
397
398 #[test]
399 fn same_action_in_two_modes_is_not_a_duplicate() {
400 let mut reg = InputRegistry::empty();
401 let mut general = KeyMap::new("general");
402 general.bind(seq("ctrl+a"), TestAction::A);
403 reg.add_map(general);
404 let mut global = KeyMap::new("global");
405 global.bind(seq("ctrl+a"), TestAction::A);
406 reg.add_map(global);
407
408 let catalog = BindingCatalog::from_registry(®, BindingSource::Builtin);
409 let analysis = analyze_keymap_bindings(&catalog, &["general", "global"]);
410 assert!(
411 !analysis
412 .conflicts
413 .iter()
414 .any(|c| matches!(c, BindingConflict::SameModeDuplicate { .. }))
415 );
416 }
417
418 #[test]
419 fn same_mode_duplicate_detected_when_merged() {
420 let mut catalog = BindingCatalog::new();
422 catalog.push(BindingInfo {
423 layer: BindingLayer::Keymap,
424 mode: "nor".to_string(),
425 sequence: seq("d"),
426 action: TestAction::A,
427 source: BindingSource::Builtin,
428 });
429 catalog.push(BindingInfo {
430 layer: BindingLayer::Keymap,
431 mode: "nor".to_string(),
432 sequence: seq("d"),
433 action: TestAction::B,
434 source: BindingSource::Config,
435 });
436 let analysis = analyze_keymap_bindings(&catalog, &["nor"]);
437 assert!(
438 analysis
439 .conflicts
440 .iter()
441 .any(|c| matches!(c, BindingConflict::SameModeDuplicate { .. }))
442 );
443 }
444}