1use std::fmt;
2
3use toml::{Value, map::Map};
4
5use crate::input::{
6 BindingCatalog, BindingConflict, BindingInfo, BindingLayer, BindingSource, InputRegistry,
7 KeyChord, ParseKeyError, analyze_keymap_bindings, try_parse_binding,
8};
9#[cfg(feature = "canvas")]
10use crate::runtime::InputLayerContext;
11use crate::runtime::ModeId;
12
13use super::preset::parse_string_list;
14use super::{ActionRegistry, NavigationPresetError, NavigationPresetIssue};
15
16#[cfg(feature = "canvas")]
17use crate::canvas::{
18 BuiltinCanvasKeybindingPreset, CanvasAction, CanvasKeybindingPresetError,
19 CanvasKeybindingProfile, analyze_canvas_overlaps, canvas_default_binding_catalog,
20};
21
22#[derive(Debug)]
23pub enum KeybindingConfigError {
24 Toml(toml::de::Error),
25 Serialize(toml::ser::Error),
26 Navigation(NavigationPresetError),
27 KeyBinding(ParseKeyError),
28 CanvasPreset {
29 preset: String,
30 },
31 CanvasAction {
32 action: String,
33 },
34 #[cfg(feature = "canvas")]
35 Canvas(CanvasKeybindingPresetError),
36}
37
38impl fmt::Display for KeybindingConfigError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::Toml(err) => write!(f, "invalid keybinding TOML: {err}"),
42 Self::Serialize(err) => write!(f, "failed to serialize keybinding TOML section: {err}"),
43 Self::Navigation(err) => write!(f, "invalid navigation keybindings: {err}"),
44 Self::KeyBinding(err) => write!(f, "invalid keybinding: {err}"),
45 Self::CanvasPreset { preset } => {
46 write!(f, "unknown canvas keybinding preset {preset:?}")
47 }
48 Self::CanvasAction { action } => {
49 write!(f, "unknown canvas keybinding action {action:?}")
50 }
51 #[cfg(feature = "canvas")]
52 Self::Canvas(err) => write!(f, "invalid canvas keybindings: {err}"),
53 }
54 }
55}
56
57impl std::error::Error for KeybindingConfigError {
58 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59 match self {
60 Self::Toml(err) => Some(err),
61 Self::Serialize(err) => Some(err),
62 Self::Navigation(err) => Some(err),
63 Self::KeyBinding(err) => Some(err),
64 #[cfg(feature = "canvas")]
65 Self::Canvas(err) => Some(err),
66 Self::CanvasPreset { .. } => None,
67 Self::CanvasAction { .. } => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Default)]
77pub struct RawKeymap {
78 pub sections: Vec<RawKeymapSection>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct RawKeymapSection {
83 pub name: String,
85 pub mode: String,
87 pub bindings: Vec<RawKeymapBinding>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct RawKeymapBinding {
92 pub name: String,
94 pub keys: Vec<String>,
96}
97
98impl RawKeymap {
99 fn from_root(root: &Map<String, Value>) -> (Self, Vec<NavigationPresetIssue>) {
100 let (mut keymap, mut issues) = Self::from_value(root.get("keymap"));
101 for (section_name, section_value) in root {
102 if matches!(section_name.as_str(), "keymap" | "canvas") {
103 continue;
104 }
105 Self::push_section(
106 &mut keymap.sections,
107 section_name,
108 section_value,
109 &mut issues,
110 );
111 }
112 (keymap, issues)
113 }
114
115 fn from_value(value: Option<&Value>) -> (Self, Vec<NavigationPresetIssue>) {
119 let mut issues = Vec::new();
120 let Some(value) = value else {
121 return (Self::default(), issues);
122 };
123 let Some(table) = value.as_table() else {
124 issues.push(NavigationPresetIssue::RootNotTable);
125 return (Self::default(), issues);
126 };
127
128 let mut sections = Vec::with_capacity(table.len());
129 for (section_name, section_value) in table {
130 Self::push_section(&mut sections, section_name, section_value, &mut issues);
131 }
132
133 (Self { sections }, issues)
134 }
135
136 fn push_section(
137 sections: &mut Vec<RawKeymapSection>,
138 section_name: &str,
139 section_value: &Value,
140 issues: &mut Vec<NavigationPresetIssue>,
141 ) {
142 let Some(section) = section_value.as_table() else {
143 issues.push(NavigationPresetIssue::SectionNotTable {
144 section: section_name.to_string(),
145 });
146 return;
147 };
148 let mode = match section.get("mode") {
149 Some(value) => value.as_str().map(ToString::to_string).unwrap_or_else(|| {
150 issues.push(NavigationPresetIssue::ModeNotString {
151 section: section_name.to_string(),
152 });
153 section_name.to_string()
154 }),
155 None => section_name.to_string(),
156 };
157
158 let mut bindings = Vec::new();
159 for (action_name, bindings_value) in section {
160 if action_name == "mode" {
161 continue;
162 }
163 let Some(keys) = parse_string_list(section_name, action_name, bindings_value, issues)
164 else {
165 continue;
166 };
167 if keys.is_empty() {
168 issues.push(NavigationPresetIssue::EmptyBindings {
169 section: section_name.to_string(),
170 action: action_name.clone(),
171 });
172 continue;
173 }
174 bindings.push(RawKeymapBinding {
175 name: action_name.clone(),
176 keys,
177 });
178 }
179
180 sections.push(RawKeymapSection {
181 name: section_name.to_string(),
182 mode,
183 bindings,
184 });
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct KeybindingConfig {
190 pub keymap: RawKeymap,
191 pub keymap_issues: Vec<NavigationPresetIssue>,
192 #[cfg(feature = "canvas")]
193 pub canvas_preset: BuiltinCanvasKeybindingPreset,
194 #[cfg(feature = "canvas")]
195 pub canvas_overrides_toml: String,
196}
197
198impl KeybindingConfig {
199 pub fn from_toml(source: &str) -> Result<Self, KeybindingConfigError> {
200 let value = if source.trim().is_empty() {
201 Value::Table(Map::new())
202 } else {
203 toml::from_str::<Value>(source).map_err(KeybindingConfigError::Toml)?
204 };
205 let root = value.as_table().cloned().unwrap_or_else(Map::new);
206
207 #[cfg_attr(not(feature = "canvas"), allow(unused_mut))]
208 let (mut keymap, keymap_issues) = RawKeymap::from_root(&root);
209
210 #[cfg(feature = "canvas")]
211 {
212 let canvas = root.get("canvas").and_then(Value::as_table);
213 let canvas_preset = canvas
214 .and_then(|table| table.get("preset"))
215 .and_then(Value::as_str)
216 .map(|preset| {
217 preset
218 .parse::<BuiltinCanvasKeybindingPreset>()
219 .map_err(|err| KeybindingConfigError::CanvasPreset {
220 preset: err.name().to_string(),
221 })
222 })
223 .transpose()?
224 .unwrap_or(BuiltinCanvasKeybindingPreset::Vim);
225
226 let mut canvas_overrides: Map<String, Value> = canvas
234 .and_then(|table| table.get("bindings"))
235 .and_then(Value::as_table)
236 .cloned()
237 .unwrap_or_default();
238 fold_keymap_canvas_overrides(&mut keymap, &mut canvas_overrides);
239
240 let canvas_overrides_toml = if canvas_overrides.is_empty() {
241 String::new()
242 } else {
243 toml::to_string(&Value::Table(canvas_overrides))
244 .map_err(KeybindingConfigError::Serialize)?
245 };
246
247 Ok(Self {
248 keymap,
249 keymap_issues,
250 canvas_preset,
251 canvas_overrides_toml,
252 })
253 }
254
255 #[cfg(not(feature = "canvas"))]
256 {
257 Ok(Self {
258 keymap,
259 keymap_issues,
260 })
261 }
262 }
263
264 #[cfg(feature = "canvas")]
265 pub fn canvas_profile(&self) -> Result<CanvasKeybindingProfile, KeybindingConfigError> {
266 CanvasKeybindingProfile::with_overrides_toml(
267 self.canvas_preset,
268 &self.canvas_overrides_toml,
269 )
270 .map_err(KeybindingConfigError::Canvas)
271 }
272}
273
274#[cfg(feature = "canvas")]
279fn is_canvas_override_action(name: &str) -> bool {
280 use crate::canvas::CanvasKeyAction as C;
281 !matches!(
282 C::from_name(name),
283 C::Unknown(_) | C::Exit | C::EnterDecider
284 )
285}
286
287#[cfg(feature = "canvas")]
292fn fold_keymap_canvas_overrides(keymap: &mut RawKeymap, overrides: &mut Map<String, Value>) {
293 for section in &mut keymap.sections {
294 if !matches!(section.mode.as_str(), "nor" | "ins" | "sel") {
295 continue;
296 }
297 let mut i = 0;
298 while i < section.bindings.len() {
299 if !is_canvas_override_action(§ion.bindings[i].name) {
300 i += 1;
301 continue;
302 }
303 let binding = section.bindings.remove(i);
304 let mode_table = overrides
305 .entry(section.mode.clone())
306 .or_insert_with(|| Value::Table(Map::new()));
307 if let Value::Table(table) = mode_table {
308 table.insert(
309 binding.name,
310 Value::Array(binding.keys.into_iter().map(Value::String).collect()),
311 );
312 }
313 }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub enum BindingNotice<A> {
319 UserOverridesDefault {
320 mode: String,
321 sequence: Vec<KeyChord>,
322 default_action: A,
323 user_action: A,
324 },
325 RuntimeOverrides {
326 mode: String,
327 sequence: Vec<KeyChord>,
328 previous_source: BindingSource,
329 previous_action: A,
330 runtime_action: A,
331 },
332 SameLayerConflict(BindingConflict<A>),
333 CrossLayerOverlap(BindingConflict<A>),
334 InvalidEntry(NavigationPresetIssue),
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct KeybindingReport<A> {
339 pub notices: Vec<BindingNotice<A>>,
340}
341
342impl<A> Default for KeybindingReport<A> {
343 fn default() -> Self {
344 Self {
345 notices: Vec::new(),
346 }
347 }
348}
349
350impl<A> KeybindingReport<A> {
351 pub fn is_empty(&self) -> bool {
352 self.notices.is_empty()
353 }
354}
355
356impl<A: fmt::Debug> fmt::Display for BindingNotice<A> {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 match self {
359 Self::UserOverridesDefault {
360 mode,
361 sequence,
362 default_action,
363 user_action,
364 } => write!(
365 f,
366 "user binding {sequence:?} in mode {mode:?} overrides default {default_action:?} with {user_action:?}"
367 ),
368 Self::RuntimeOverrides {
369 mode,
370 sequence,
371 previous_source,
372 previous_action,
373 runtime_action,
374 } => write!(
375 f,
376 "runtime binding {sequence:?} in mode {mode:?} overrides {previous_source:?} {previous_action:?} with {runtime_action:?}"
377 ),
378 Self::SameLayerConflict(conflict) => write!(f, "keybinding conflict: {conflict:?}"),
379 Self::CrossLayerOverlap(conflict) => {
380 write!(f, "cross-layer keybinding overlap: {conflict:?}")
381 }
382 Self::InvalidEntry(issue) => write!(f, "invalid keybinding entry skipped: {issue}"),
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
388pub struct KeybindingInheritance<A> {
389 pub target_mode: ModeId,
390 pub target_action: A,
391 pub source_mode: ModeId,
392 pub source_action: A,
393}
394
395impl<A> KeybindingInheritance<A> {
396 pub fn new(
397 target_mode: impl Into<ModeId>,
398 target_action: A,
399 source_mode: impl Into<ModeId>,
400 source_action: A,
401 ) -> Self {
402 Self {
403 target_mode: target_mode.into(),
404 target_action,
405 source_mode: source_mode.into(),
406 source_action,
407 }
408 }
409}
410
411#[derive(Debug, Clone)]
412pub struct BindingStore<A> {
413 pub builtin_keymap: BindingCatalog<A>,
414 pub user_keymap: BindingCatalog<A>,
415 pub runtime_keymap: BindingCatalog<A>,
416 keymap_inheritances: Vec<KeybindingInheritance<A>>,
417 #[cfg(feature = "canvas")]
418 pub builtin_canvas: BindingCatalog<CanvasAction>,
419 #[cfg(feature = "canvas")]
420 pub user_canvas: BindingCatalog<CanvasAction>,
421 #[cfg(feature = "canvas")]
422 pub runtime_canvas: BindingCatalog<CanvasAction>,
423}
424
425impl<A> Default for BindingStore<A> {
426 fn default() -> Self {
427 Self {
428 builtin_keymap: BindingCatalog::new(),
429 user_keymap: BindingCatalog::new(),
430 runtime_keymap: BindingCatalog::new(),
431 keymap_inheritances: Vec::new(),
432 #[cfg(feature = "canvas")]
433 builtin_canvas: BindingCatalog::new(),
434 #[cfg(feature = "canvas")]
435 user_canvas: BindingCatalog::new(),
436 #[cfg(feature = "canvas")]
437 runtime_canvas: BindingCatalog::new(),
438 }
439 }
440}
441
442impl<A> BindingStore<A>
443where
444 A: Clone + PartialEq,
445{
446 pub fn from_registries(
447 builtin_keymap: &InputRegistry<A>,
448 user_keymap: &InputRegistry<A>,
449 ) -> Self {
450 Self {
451 builtin_keymap: BindingCatalog::from_registry(builtin_keymap, BindingSource::Builtin),
452 user_keymap: BindingCatalog::from_registry(user_keymap, BindingSource::Config),
453 ..Self::default()
454 }
455 }
456
457 pub fn effective_registry(&self) -> InputRegistry<A> {
458 let mut registry = InputRegistry::empty();
459 apply_catalog_to_registry(&mut registry, &self.builtin_keymap);
460 remap_catalog_to_registry(&mut registry, &self.user_keymap);
461 remap_catalog_to_registry(&mut registry, &self.runtime_keymap);
462 apply_keymap_inheritances(
463 &mut registry,
464 &self.user_keymap,
465 &self.runtime_keymap,
466 &self.keymap_inheritances,
467 );
468 registry
469 }
470
471 pub fn builtin_registry(&self) -> InputRegistry<A> {
472 let mut registry = InputRegistry::empty();
473 apply_catalog_to_registry(&mut registry, &self.builtin_keymap);
474 registry
475 }
476
477 pub fn report(&self, active_modes: &[impl AsRef<str>]) -> KeybindingReport<A> {
478 let mut notices = Vec::new();
479 notices.extend(user_override_notices(
480 &self.builtin_keymap,
481 &self.user_keymap,
482 ));
483 notices.extend(runtime_override_notices(
484 &self.builtin_keymap,
485 &self.user_keymap,
486 &self.runtime_keymap,
487 ));
488
489 let effective =
490 BindingCatalog::from_registry(&self.effective_registry(), BindingSource::Unknown);
491 notices.extend(
492 analyze_keymap_bindings(&effective, active_modes)
493 .conflicts
494 .into_iter()
495 .map(BindingNotice::SameLayerConflict),
496 );
497
498 #[cfg(feature = "canvas")]
499 {
500 let mut canvas = BindingCatalog::new();
501 canvas.extend(self.builtin_canvas.clone());
502 canvas.extend(self.user_canvas.clone());
503 canvas.extend(self.runtime_canvas.clone());
504 notices.extend(
505 analyze_canvas_overlaps(&effective, &canvas, InputLayerContext::Command)
506 .into_iter()
507 .chain(analyze_canvas_overlaps(
508 &effective,
509 &canvas,
510 InputLayerContext::Text,
511 ))
512 .map(BindingNotice::CrossLayerOverlap),
513 );
514 }
515
516 KeybindingReport { notices }
517 }
518}
519
520impl<A> BindingStore<A>
521where
522 A: Clone + PartialEq,
523{
524 pub fn with_user_config(
528 builtin_keymap: &InputRegistry<A>,
529 config: &KeybindingConfig,
530 actions: &ActionRegistry<A>,
531 ) -> Result<(Self, InputRegistry<A>, KeybindingReport<A>), KeybindingConfigError> {
532 Self::with_user_config_and_inheritances(
533 builtin_keymap,
534 config,
535 actions,
536 Vec::<KeybindingInheritance<A>>::new(),
537 )
538 }
539
540 pub fn with_user_config_and_inheritances(
541 builtin_keymap: &InputRegistry<A>,
542 config: &KeybindingConfig,
543 actions: &ActionRegistry<A>,
544 keymap_inheritances: impl IntoIterator<Item = KeybindingInheritance<A>>,
545 ) -> Result<(Self, InputRegistry<A>, KeybindingReport<A>), KeybindingConfigError> {
546 let mut store = Self::default();
547 store.builtin_keymap =
548 BindingCatalog::from_registry(builtin_keymap, BindingSource::Builtin);
549 store.keymap_inheritances = keymap_inheritances.into_iter().collect();
550 let (user_keymap, unknown_issues) =
551 resolve_raw_keymap(&config.keymap, actions, BindingSource::Config);
552 store.user_keymap = user_keymap;
553
554 #[cfg(feature = "canvas")]
555 {
556 store.builtin_canvas = canvas_default_binding_catalog(config.canvas_preset);
557 let profile = config.canvas_profile()?;
558 store.user_canvas = canvas_profile_overrides_catalog(&profile);
559 }
560
561 let mut report = store.report(&["global", "general", "nor", "ins", "sel"]);
562 report.notices.extend(
563 config
564 .keymap_issues
565 .iter()
566 .cloned()
567 .chain(unknown_issues)
568 .map(BindingNotice::InvalidEntry),
569 );
570 let registry = store.effective_registry();
571 Ok((store, registry, report))
572 }
573}
574
575fn apply_catalog_to_registry<A: Clone>(
576 registry: &mut InputRegistry<A>,
577 catalog: &BindingCatalog<A>,
578) {
579 for binding in &catalog.bindings {
580 registry
581 .map_mut(binding.mode.as_str())
582 .bind(binding.sequence.clone(), binding.action.clone());
583 }
584}
585
586fn remap_catalog_to_registry<A>(registry: &mut InputRegistry<A>, catalog: &BindingCatalog<A>)
587where
588 A: Clone + PartialEq,
589{
590 let mut cleared = Vec::<(String, A)>::new();
591 for binding in &catalog.bindings {
592 if cleared
593 .iter()
594 .any(|(mode, action)| mode == &binding.mode && action == &binding.action)
595 {
596 continue;
597 }
598 registry
599 .map_mut(binding.mode.as_str())
600 .unbind_action(&binding.action);
601 cleared.push((binding.mode.clone(), binding.action.clone()));
602 }
603 apply_catalog_to_registry(registry, catalog);
604}
605
606fn catalog_has_action<A: PartialEq>(
607 catalog: &BindingCatalog<A>,
608 mode: &ModeId,
609 action: &A,
610) -> bool {
611 catalog
612 .bindings
613 .iter()
614 .any(|binding| binding.mode == mode.as_str() && &binding.action == action)
615}
616
617fn registry_bindings_for_action<A: PartialEq + Clone>(
618 registry: &InputRegistry<A>,
619 mode: &ModeId,
620 action: &A,
621) -> Vec<Vec<KeyChord>> {
622 registry
623 .maps
624 .get(mode.as_str())
625 .map(|map| {
626 map.bindings
627 .iter()
628 .filter_map(|(sequence, bound_action)| {
629 if bound_action == action {
630 Some(sequence.clone())
631 } else {
632 None
633 }
634 })
635 .collect()
636 })
637 .unwrap_or_default()
638}
639
640fn apply_keymap_inheritances<A: Clone + PartialEq>(
641 registry: &mut InputRegistry<A>,
642 user_keymap: &BindingCatalog<A>,
643 runtime_keymap: &BindingCatalog<A>,
644 inheritances: &[KeybindingInheritance<A>],
645) {
646 for inheritance in inheritances {
647 let source_sequences = registry_bindings_for_action(
648 registry,
649 &inheritance.source_mode,
650 &inheritance.source_action,
651 );
652 if source_sequences.is_empty() {
653 continue;
654 }
655
656 if catalog_has_action(
657 user_keymap,
658 &inheritance.target_mode,
659 &inheritance.target_action,
660 ) || catalog_has_action(
661 runtime_keymap,
662 &inheritance.target_mode,
663 &inheritance.target_action,
664 ) {
665 continue;
666 }
667
668 registry
669 .map_mut(inheritance.target_mode.as_str())
670 .unbind_action(&inheritance.target_action);
671 for sequence in source_sequences {
672 registry
673 .map_mut(inheritance.target_mode.as_str())
674 .bind(sequence, inheritance.target_action.clone());
675 }
676 }
677}
678
679fn resolve_raw_keymap<A>(
680 keymap: &RawKeymap,
681 actions: &ActionRegistry<A>,
682 source: BindingSource,
683) -> (BindingCatalog<A>, Vec<NavigationPresetIssue>)
684where
685 A: Clone,
686{
687 let mut catalog = BindingCatalog::new();
688 let mut issues = Vec::new();
689 for section in &keymap.sections {
690 for binding in §ion.bindings {
691 let Some(action) = actions.resolve(&binding.name) else {
692 issues.push(NavigationPresetIssue::UnknownAction {
693 section: section.name.clone(),
694 action: binding.name.clone(),
695 });
696 continue;
697 };
698 for key in &binding.keys {
699 let Ok(sequence) = try_parse_binding(key) else {
700 continue;
701 };
702 catalog.push(BindingInfo {
703 layer: BindingLayer::Keymap,
704 mode: section.mode.clone(),
705 sequence,
706 action: action.clone(),
707 source,
708 });
709 }
710 }
711 }
712 (catalog, issues)
713}
714
715fn user_override_notices<A>(
716 builtin: &BindingCatalog<A>,
717 user: &BindingCatalog<A>,
718) -> Vec<BindingNotice<A>>
719where
720 A: Clone + PartialEq,
721{
722 let mut notices = Vec::new();
723 for user_binding in &user.bindings {
724 let mut emitted = false;
725 for default_binding in
726 builtin.bindings_for_sequence(&user_binding.mode, &user_binding.sequence)
727 {
728 if default_binding.action != user_binding.action {
729 notices.push(BindingNotice::UserOverridesDefault {
730 mode: user_binding.mode.clone(),
731 sequence: user_binding.sequence.clone(),
732 default_action: default_binding.action.clone(),
733 user_action: user_binding.action.clone(),
734 });
735 emitted = true;
736 }
737 }
738 if emitted {
739 continue;
740 }
741
742 for default_binding in builtin.bindings_for_action(&user_binding.action) {
743 if default_binding.mode == user_binding.mode
744 && default_binding.sequence != user_binding.sequence
745 {
746 notices.push(BindingNotice::UserOverridesDefault {
747 mode: user_binding.mode.clone(),
748 sequence: user_binding.sequence.clone(),
749 default_action: default_binding.action.clone(),
750 user_action: user_binding.action.clone(),
751 });
752 }
753 }
754 }
755 notices
756}
757
758fn runtime_override_notices<A>(
759 builtin: &BindingCatalog<A>,
760 user: &BindingCatalog<A>,
761 runtime: &BindingCatalog<A>,
762) -> Vec<BindingNotice<A>>
763where
764 A: Clone + PartialEq,
765{
766 let mut notices = Vec::new();
767 for runtime_binding in &runtime.bindings {
768 for previous in builtin
769 .bindings_for_sequence(&runtime_binding.mode, &runtime_binding.sequence)
770 .into_iter()
771 .chain(user.bindings_for_sequence(&runtime_binding.mode, &runtime_binding.sequence))
772 {
773 if previous.action != runtime_binding.action {
774 notices.push(BindingNotice::RuntimeOverrides {
775 mode: runtime_binding.mode.clone(),
776 sequence: runtime_binding.sequence.clone(),
777 previous_source: previous.source,
778 previous_action: previous.action.clone(),
779 runtime_action: runtime_binding.action.clone(),
780 });
781 }
782 }
783 }
784 notices
785}
786
787#[cfg(feature = "canvas")]
788fn canvas_profile_overrides_catalog(
789 profile: &CanvasKeybindingProfile,
790) -> BindingCatalog<CanvasAction> {
791 let mut catalog = BindingCatalog::new();
792 let default_entries = profile.defaults().entries();
793 for entry in profile.current().entries() {
794 let Some(action) = entry.action.to_canvas_action() else {
795 continue;
796 };
797 let default_same = default_entries.iter().any(|default| {
798 default.mode == entry.mode
799 && default.action == entry.action
800 && default.sequence == entry.sequence
801 });
802 if default_same {
803 continue;
804 }
805 catalog.push(BindingInfo {
806 layer: BindingLayer::Canvas,
807 mode: crate::canvas::mode_for_app_mode(entry.mode)
808 .as_str()
809 .to_string(),
810 sequence: entry
811 .sequence
812 .iter()
813 .map(|stroke| KeyChord::new(stroke.code, stroke.modifiers))
814 .collect(),
815 action,
816 source: BindingSource::Config,
817 });
818 }
819 catalog
820}
821
822pub fn export_to_toml<A>(
828 store: &BindingStore<A>,
829 actions: &ActionRegistry<A>,
830 #[cfg(feature = "canvas")] profile: &CanvasKeybindingProfile,
831) -> Result<String, KeybindingConfigError>
832where
833 A: Clone + PartialEq,
834{
835 use std::collections::{BTreeMap, BTreeSet};
836
837 let mut keymap: BTreeMap<String, BTreeMap<&'static str, Vec<String>>> = BTreeMap::new();
839 let mut runtime_owned: BTreeSet<(String, &'static str)> = BTreeSet::new();
842
843 let mut record = |binding: &BindingInfo<A>| {
844 let Some(name) = actions.name_of(&binding.action) else {
845 return;
846 };
847 let sequence = binding
848 .sequence
849 .iter()
850 .map(KeyChord::display_string)
851 .collect::<Vec<_>>()
852 .join(" ");
853 keymap
854 .entry(binding.mode.clone())
855 .or_default()
856 .entry(name)
857 .or_default()
858 .push(sequence);
859 };
860
861 for binding in &store.runtime_keymap.bindings {
862 if actions.name_of(&binding.action).is_some() {
863 runtime_owned.insert((
864 binding.mode.clone(),
865 actions.name_of(&binding.action).unwrap(),
866 ));
867 }
868 record(binding);
869 }
870 for binding in &store.user_keymap.bindings {
871 if let Some(name) = actions.name_of(&binding.action) {
872 if runtime_owned.contains(&(binding.mode.clone(), name)) {
873 continue;
874 }
875 }
876 record(binding);
877 }
878
879 let mut root = Map::new();
880 for (mode, actions_map) in keymap {
881 let section = root.entry(mode).or_insert_with(|| Value::Table(Map::new()));
882 if let Value::Table(section) = section {
883 for (name, keys) in actions_map {
884 section.insert(
885 name.to_string(),
886 Value::Array(keys.into_iter().map(Value::String).collect()),
887 );
888 }
889 }
890 }
891
892 #[cfg(feature = "canvas")]
893 {
894 let mut canvas_table = Map::new();
895 if profile.preset() != BuiltinCanvasKeybindingPreset::Vim {
896 canvas_table.insert(
897 "preset".to_string(),
898 Value::String(profile.preset().to_string()),
899 );
900 }
901 let overrides = profile.overrides_toml();
902 if !overrides.trim().is_empty() {
903 let parsed =
904 toml::from_str::<Value>(&overrides).map_err(KeybindingConfigError::Toml)?;
905 if let Some(table) = parsed.as_table() {
906 for (mode, actions) in table {
907 let section = root
908 .entry(mode.clone())
909 .or_insert_with(|| Value::Table(Map::new()));
910 if let (Value::Table(section), Value::Table(actions)) = (section, actions) {
911 for (name, keys) in actions {
912 section.insert(name.clone(), keys.clone());
913 }
914 }
915 }
916 }
917 }
918 if !canvas_table.is_empty() {
919 root.insert("canvas".to_string(), Value::Table(canvas_table));
920 }
921 }
922
923 toml::to_string(&Value::Table(root)).map_err(KeybindingConfigError::Serialize)
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929 use crate::keybindings::NavigationAction;
930
931 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
932 enum TestAction {
933 Nav(NavigationAction),
934 }
935
936 impl From<NavigationAction> for TestAction {
937 fn from(value: NavigationAction) -> Self {
938 Self::Nav(value)
939 }
940 }
941
942 fn seq(binding: &str) -> Vec<KeyChord> {
943 try_parse_binding(binding).unwrap()
944 }
945
946 #[test]
947 fn keymap_resolves_app_specific_action_names_via_registry() {
948 #[derive(Debug, Clone, PartialEq, Eq)]
951 enum App {
952 DoTheThing,
953 Nav(NavigationAction),
954 }
955 impl From<NavigationAction> for App {
956 fn from(value: NavigationAction) -> Self {
957 App::Nav(value)
958 }
959 }
960
961 let registry = ActionRegistry::from_entries(vec![crate::input::BindableActionInfo {
962 action: App::DoTheThing,
963 name: "do_the_thing",
964 description: "",
965 modes: &["global"],
966 }]);
967 let config = KeybindingConfig::from_toml(
968 r#"
969[keymap.global]
970do_the_thing = "ctrl+t"
971"#,
972 )
973 .unwrap();
974 let builtin = InputRegistry::<App>::empty();
975 let (_store, registry_out, report) =
976 BindingStore::with_user_config(&builtin, &config, ®istry).unwrap();
977
978 assert_eq!(
979 registry_out
980 .maps
981 .get("global")
982 .unwrap()
983 .bindings
984 .get(&seq("ctrl+t")),
985 Some(&App::DoTheThing)
986 );
987 assert!(
988 report
989 .notices
990 .iter()
991 .all(|notice| !matches!(notice, BindingNotice::InvalidEntry(_)))
992 );
993 }
994
995 #[test]
996 fn parses_keymap_section_from_unified_toml() {
997 let config = KeybindingConfig::from_toml(
998 r#"
999[keymap.global]
1000quit = "ctrl+q"
1001
1002[keymap.general]
1003focus_next = ["j", "down"]
1004"#,
1005 )
1006 .unwrap();
1007
1008 let global = config
1009 .keymap
1010 .sections
1011 .iter()
1012 .find(|section| section.name == "global")
1013 .unwrap();
1014 assert_eq!(global.bindings.first().unwrap().name, "quit");
1015 }
1016
1017 #[test]
1018 fn parses_root_mode_sections_from_unified_toml() {
1019 let config = KeybindingConfig::from_toml(
1020 r#"
1021[global]
1022quit = "ctrl+q"
1023
1024[general]
1025focus_next = ["j", "down"]
1026"#,
1027 )
1028 .unwrap();
1029
1030 let global = config
1031 .keymap
1032 .sections
1033 .iter()
1034 .find(|section| section.name == "global")
1035 .unwrap();
1036 assert_eq!(global.bindings.first().unwrap().name, "quit");
1037 }
1038
1039 #[test]
1040 fn reports_user_override_default_and_effective_registry_uses_user_binding() {
1041 let mut builtin = InputRegistry::empty();
1042 builtin
1043 .map_mut("global")
1044 .bind(seq("ctrl+c"), TestAction::Nav(NavigationAction::Quit));
1045
1046 let config = KeybindingConfig::from_toml(
1047 r#"
1048[keymap.global]
1049quit = "ctrl+q"
1050"#,
1051 )
1052 .unwrap();
1053
1054 let (store, registry, report) = BindingStore::<TestAction>::with_user_config(
1055 &builtin,
1056 &config,
1057 &ActionRegistry::navigation(),
1058 )
1059 .unwrap();
1060
1061 assert_eq!(
1062 registry
1063 .maps
1064 .get("global")
1065 .unwrap()
1066 .bindings
1067 .get(&seq("ctrl+q")),
1068 Some(&TestAction::Nav(NavigationAction::Quit))
1069 );
1070 assert!(
1071 !registry
1072 .maps
1073 .get("global")
1074 .unwrap()
1075 .bindings
1076 .contains_key(&seq("ctrl+c"))
1077 );
1078 assert!(report.notices.iter().any(|notice| matches!(
1079 notice,
1080 BindingNotice::UserOverridesDefault {
1081 mode,
1082 sequence,
1083 default_action: TestAction::Nav(NavigationAction::Quit),
1084 user_action: TestAction::Nav(NavigationAction::Quit),
1085 } if mode == "global" && *sequence == seq("ctrl+q")
1086 )));
1087 assert_eq!(store.effective_registry().total_bindings(), 1);
1088 }
1089
1090 #[test]
1091 fn inherited_keybinding_follows_source_user_override() {
1092 #[derive(Debug, Clone, PartialEq, Eq)]
1093 enum App {
1094 Select,
1095 Execute,
1096 }
1097
1098 let actions = ActionRegistry::from_entries(vec![
1099 crate::input::BindableActionInfo {
1100 action: App::Select,
1101 name: "nav_select",
1102 description: "",
1103 modes: &["general"],
1104 },
1105 crate::input::BindableActionInfo {
1106 action: App::Execute,
1107 name: "command_execute",
1108 description: "",
1109 modes: &["command"],
1110 },
1111 ]);
1112 let mut builtin = InputRegistry::empty();
1113 builtin.map_mut("general").bind(seq("enter"), App::Select);
1114 builtin.map_mut("command").bind(seq("ctrl+x"), App::Execute);
1115 let config = KeybindingConfig::from_toml(
1116 r#"
1117[keymap.general]
1118nav_select = "ctrl+j"
1119"#,
1120 )
1121 .unwrap();
1122
1123 let (_store, registry, _report) = BindingStore::with_user_config_and_inheritances(
1124 &builtin,
1125 &config,
1126 &actions,
1127 [KeybindingInheritance::new(
1128 crate::runtime::modes::COMMAND,
1129 App::Execute,
1130 crate::runtime::modes::GENERAL,
1131 App::Select,
1132 )],
1133 )
1134 .unwrap();
1135
1136 let command = registry.maps.get("command").unwrap();
1137 assert_eq!(command.bindings.get(&seq("ctrl+j")), Some(&App::Execute));
1138 assert!(!command.bindings.contains_key(&seq("ctrl+x")));
1139 }
1140
1141 #[test]
1142 fn inherited_keybinding_does_not_replace_explicit_target() {
1143 #[derive(Debug, Clone, PartialEq, Eq)]
1144 enum App {
1145 Select,
1146 Execute,
1147 }
1148
1149 let actions = ActionRegistry::from_entries(vec![
1150 crate::input::BindableActionInfo {
1151 action: App::Select,
1152 name: "nav_select",
1153 description: "",
1154 modes: &["general"],
1155 },
1156 crate::input::BindableActionInfo {
1157 action: App::Execute,
1158 name: "command_execute",
1159 description: "",
1160 modes: &["command"],
1161 },
1162 ]);
1163 let mut builtin = InputRegistry::empty();
1164 builtin.map_mut("general").bind(seq("enter"), App::Select);
1165 builtin.map_mut("command").bind(seq("ctrl+x"), App::Execute);
1166 let config = KeybindingConfig::from_toml(
1167 r#"
1168[keymap.general]
1169nav_select = "ctrl+j"
1170
1171[keymap.command]
1172command_execute = "-"
1173"#,
1174 )
1175 .unwrap();
1176
1177 let (_store, registry, _report) = BindingStore::with_user_config_and_inheritances(
1178 &builtin,
1179 &config,
1180 &actions,
1181 [KeybindingInheritance::new(
1182 crate::runtime::modes::COMMAND,
1183 App::Execute,
1184 crate::runtime::modes::GENERAL,
1185 App::Select,
1186 )],
1187 )
1188 .unwrap();
1189
1190 let command = registry.maps.get("command").unwrap();
1191 assert_eq!(command.bindings.get(&seq("-")), Some(&App::Execute));
1192 assert!(!command.bindings.contains_key(&seq("ctrl+j")));
1193 assert!(!command.bindings.contains_key(&seq("ctrl+x")));
1194 }
1195
1196 #[test]
1197 fn invalid_navigation_entries_are_not_all_or_nothing() {
1198 let mut builtin = InputRegistry::empty();
1199 builtin
1200 .map_mut("global")
1201 .bind(seq("ctrl+c"), TestAction::Nav(NavigationAction::Quit));
1202
1203 let config = KeybindingConfig::from_toml(
1204 r#"
1205[keymap.global]
1206quit = "ctrl+q"
1207not_real = "x"
1208
1209[keymap.general]
1210focus_next = "not+a+key"
1211"#,
1212 )
1213 .unwrap();
1214
1215 let (_store, registry, report) = BindingStore::<TestAction>::with_user_config(
1216 &builtin,
1217 &config,
1218 &ActionRegistry::navigation(),
1219 )
1220 .unwrap();
1221
1222 assert_eq!(
1223 registry
1224 .maps
1225 .get("global")
1226 .unwrap()
1227 .bindings
1228 .get(&seq("ctrl+q")),
1229 Some(&TestAction::Nav(NavigationAction::Quit))
1230 );
1231 assert!(
1232 report
1233 .notices
1234 .iter()
1235 .any(|notice| matches!(notice, BindingNotice::InvalidEntry(_)))
1236 );
1237 }
1238
1239 #[cfg(feature = "canvas")]
1244 #[test]
1245 fn modal_section_canvas_actions_route_to_canvas_profile() {
1246 let config = KeybindingConfig::from_toml(
1247 r#"
1248[keymap.nor]
1249undo = ["ctrl+z"]
1250previous_entry = ["q"]
1251
1252[canvas]
1253preset = "helix"
1254
1255[canvas.bindings.nor]
1256redo = ["ctrl+y"]
1257"#,
1258 )
1259 .unwrap();
1260
1261 let nor = config
1264 .keymap
1265 .sections
1266 .iter()
1267 .find(|section| section.mode == "nor")
1268 .expect("nor keymap section survives");
1269 assert!(nor.bindings.iter().any(|b| b.name == "previous_entry"));
1270 assert!(nor.bindings.iter().all(|b| b.name != "undo"));
1271
1272 let overrides = config.canvas_profile().unwrap().overrides_toml();
1275 assert!(overrides.contains("undo"), "overrides: {overrides}");
1276 assert!(overrides.contains("redo"), "overrides: {overrides}");
1277 }
1278
1279 #[cfg(feature = "canvas")]
1280 #[test]
1281 fn root_modal_sections_split_canvas_and_app_actions() {
1282 #[derive(Debug, Clone, PartialEq, Eq)]
1283 enum App {
1284 MyAppAction,
1285 }
1286
1287 let actions = ActionRegistry::from_entries(vec![crate::input::BindableActionInfo {
1288 action: App::MyAppAction,
1289 name: "my_app_action",
1290 description: "",
1291 modes: &["nor"],
1292 }]);
1293 let config = KeybindingConfig::from_toml(
1294 r#"
1295[nor]
1296undo = ["ctrl+z"]
1297my_app_action = ["x"]
1298"#,
1299 )
1300 .unwrap();
1301 let builtin = InputRegistry::<App>::empty();
1302 let (_store, registry, report) =
1303 BindingStore::with_user_config(&builtin, &config, &actions).unwrap();
1304
1305 assert_eq!(
1306 registry.maps.get("nor").unwrap().bindings.get(&seq("x")),
1307 Some(&App::MyAppAction)
1308 );
1309 assert!(
1310 report
1311 .notices
1312 .iter()
1313 .all(|notice| !matches!(notice, BindingNotice::InvalidEntry(_)))
1314 );
1315
1316 let overrides = config.canvas_profile().unwrap().overrides_toml();
1317 assert!(
1318 overrides.contains("undo = [\"Ctrl+z\"]"),
1319 "overrides: {overrides}"
1320 );
1321 }
1322}