1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Context {
15 Global,
17 Tree,
19 Viewer,
21 Graph,
23 Trust,
25 Computations,
27 Concept,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Action {
34 SwitchTab(usize),
36 OpenPalette,
38 OpenDiagnostics,
40 OpenHelp,
42 Reload,
44 OpenEditor,
46 Quit,
48 Up,
50 Down,
52 Left,
54 Right,
56 PageUp,
58 PageDown,
60 HalfDown,
62 HalfUp,
64 Home,
66 End,
68 Activate,
70 Back,
72 Forward,
74 NextLink,
76 PrevLink,
78 ToggleRawYaml,
80 Outline,
82 CycleInspector,
84 Verify,
86 ExtendStale,
88 MoveOrRename,
90 Remove,
92 Merge,
94 SplitSection,
96 NewConcept,
98 ZoomIn,
100 ZoomOut,
102 NextNode,
104 PrevNode,
106 FocusMode,
108 CycleColor,
110 ToggleSources,
112 ToggleBroken,
114 ToggleDerivations,
116 PauseLayout,
118 GraphFilter,
120 CycleLayout,
122 CyclePanel,
124 CycleSort,
126 CopySketch,
128 FixAll,
130 PinToday,
132 OpenLog,
134}
135
136pub struct Binding {
138 pub context: Context,
140 pub code: KeyCode,
142 pub mods: KeyModifiers,
144 pub action: Action,
146 pub primary: bool,
148 pub label: &'static str,
150 pub help: &'static str,
152}
153
154const fn b(
155 context: Context,
156 code: KeyCode,
157 mods: KeyModifiers,
158 action: Action,
159 primary: bool,
160 label: &'static str,
161 help: &'static str,
162) -> Binding {
163 Binding {
164 context,
165 code,
166 mods,
167 action,
168 primary,
169 label,
170 help,
171 }
172}
173
174const NONE: KeyModifiers = KeyModifiers::NONE;
175const CTRL: KeyModifiers = KeyModifiers::CONTROL;
176
177#[must_use]
179pub const fn bindings() -> &'static [Binding] {
180 BINDINGS
181}
182
183#[allow(clippy::too_many_lines)]
184const BINDINGS: &[Binding] = {
185 use Action as A;
186 use Context as C;
187 use KeyCode as K;
188 &[
189 b(
191 C::Global,
192 K::Char('1'),
193 NONE,
194 A::SwitchTab(0),
195 true,
196 "1",
197 "Explorer workspace",
198 ),
199 b(
200 C::Global,
201 K::Char('2'),
202 NONE,
203 A::SwitchTab(1),
204 true,
205 "2",
206 "Graph workspace",
207 ),
208 b(
209 C::Global,
210 K::Char('3'),
211 NONE,
212 A::SwitchTab(2),
213 true,
214 "3",
215 "Trust workspace",
216 ),
217 b(
218 C::Global,
219 K::Char('4'),
220 NONE,
221 A::SwitchTab(3),
222 true,
223 "4",
224 "Computations workspace",
225 ),
226 b(
227 C::Global,
228 K::Char('/'),
229 NONE,
230 A::OpenPalette,
231 true,
232 "/",
233 "Omnisearch (> for commands)",
234 ),
235 b(
236 C::Global,
237 K::Char('p'),
238 CTRL,
239 A::OpenPalette,
240 false,
241 "Ctrl+P",
242 "Command palette",
243 ),
244 b(
245 C::Global,
246 K::Char('!'),
247 NONE,
248 A::OpenDiagnostics,
249 true,
250 "!",
251 "Diagnostics & fixes",
252 ),
253 b(
254 C::Global,
255 K::Char('?'),
256 NONE,
257 A::OpenHelp,
258 true,
259 "?",
260 "Help / cheatsheet",
261 ),
262 b(
263 C::Global,
264 K::Char('R'),
265 NONE,
266 A::Reload,
267 true,
268 "R",
269 "Reload bundle",
270 ),
271 b(
272 C::Global,
273 K::Char('e'),
274 NONE,
275 A::OpenEditor,
276 true,
277 "e",
278 "Open in $EDITOR",
279 ),
280 b(C::Global, K::Char('q'), NONE, A::Quit, true, "q", "Quit"),
281 b(
283 C::Concept,
284 K::Char('v'),
285 NONE,
286 A::Verify,
287 true,
288 "v",
289 "Stamp verification",
290 ),
291 b(
292 C::Concept,
293 K::Char('x'),
294 NONE,
295 A::ExtendStale,
296 true,
297 "x",
298 "Extend stale_after",
299 ),
300 b(
301 C::Concept,
302 K::F(2),
303 NONE,
304 A::MoveOrRename,
305 true,
306 "F2",
307 "Move / rename",
308 ),
309 b(
310 C::Concept,
311 K::Char('m'),
312 NONE,
313 A::MoveOrRename,
314 false,
315 "m",
316 "Move / rename",
317 ),
318 b(
319 C::Concept,
320 K::Delete,
321 NONE,
322 A::Remove,
323 true,
324 "Del",
325 "Remove concept",
326 ),
327 b(
328 C::Concept,
329 K::Char('M'),
330 NONE,
331 A::Merge,
332 true,
333 "M",
334 "Merge into…",
335 ),
336 b(
337 C::Concept,
338 K::Char('n'),
339 NONE,
340 A::NewConcept,
341 true,
342 "n",
343 "New concept here",
344 ),
345 b(C::Tree, K::Up, NONE, A::Up, true, "↑", "Previous row"),
347 b(C::Tree, K::Down, NONE, A::Down, true, "↓", "Next row"),
348 b(
349 C::Tree,
350 K::Char('k'),
351 NONE,
352 A::Up,
353 false,
354 "k",
355 "Previous row",
356 ),
357 b(C::Tree, K::Char('j'), NONE, A::Down, false, "j", "Next row"),
358 b(
359 C::Tree,
360 K::Left,
361 NONE,
362 A::Left,
363 true,
364 "←",
365 "Collapse directory",
366 ),
367 b(
368 C::Tree,
369 K::Right,
370 NONE,
371 A::Right,
372 true,
373 "→",
374 "Expand / focus document",
375 ),
376 b(
377 C::Tree,
378 K::Char('h'),
379 NONE,
380 A::Left,
381 false,
382 "h",
383 "Collapse directory",
384 ),
385 b(
386 C::Tree,
387 K::Char('l'),
388 NONE,
389 A::Right,
390 false,
391 "l",
392 "Expand / focus document",
393 ),
394 b(
395 C::Tree,
396 K::Enter,
397 NONE,
398 A::Activate,
399 true,
400 "⏎",
401 "Open selection",
402 ),
403 b(C::Tree, K::Home, NONE, A::Home, true, "Home", "First row"),
404 b(C::Tree, K::End, NONE, A::End, true, "End", "Last row"),
405 b(C::Tree, K::PageUp, NONE, A::PageUp, true, "PgUp", "Page up"),
406 b(
407 C::Tree,
408 K::PageDown,
409 NONE,
410 A::PageDown,
411 true,
412 "PgDn",
413 "Page down",
414 ),
415 b(
416 C::Tree,
417 K::Char('d'),
418 NONE,
419 A::Remove,
420 false,
421 "d",
422 "Remove concept",
423 ),
424 b(
425 C::Tree,
426 K::Char('i'),
427 NONE,
428 A::CycleInspector,
429 true,
430 "i",
431 "Cycle inspector tab",
432 ),
433 b(C::Viewer, K::Up, NONE, A::Up, true, "↑", "Scroll up"),
435 b(C::Viewer, K::Down, NONE, A::Down, true, "↓", "Scroll down"),
436 b(
437 C::Viewer,
438 K::Char('k'),
439 NONE,
440 A::Up,
441 false,
442 "k",
443 "Scroll up",
444 ),
445 b(
446 C::Viewer,
447 K::Char('j'),
448 NONE,
449 A::Down,
450 false,
451 "j",
452 "Scroll down",
453 ),
454 b(
455 C::Viewer,
456 K::PageUp,
457 NONE,
458 A::PageUp,
459 true,
460 "PgUp",
461 "Page up",
462 ),
463 b(
464 C::Viewer,
465 K::PageDown,
466 NONE,
467 A::PageDown,
468 true,
469 "PgDn",
470 "Page down",
471 ),
472 b(
473 C::Viewer,
474 K::Char('u'),
475 CTRL,
476 A::HalfUp,
477 false,
478 "Ctrl+U",
479 "Half page up",
480 ),
481 b(
482 C::Viewer,
483 K::Char('d'),
484 CTRL,
485 A::HalfDown,
486 false,
487 "Ctrl+D",
488 "Half page down",
489 ),
490 b(C::Viewer, K::Home, NONE, A::Home, true, "Home", "Top"),
491 b(C::Viewer, K::End, NONE, A::End, true, "End", "Bottom"),
492 b(C::Viewer, K::Char('g'), NONE, A::Home, false, "g", "Top"),
493 b(C::Viewer, K::Char('G'), NONE, A::End, false, "G", "Bottom"),
494 b(
495 C::Viewer,
496 K::Tab,
497 NONE,
498 A::NextLink,
499 true,
500 "Tab",
501 "Focus next link",
502 ),
503 b(
504 C::Viewer,
505 K::BackTab,
506 NONE,
507 A::PrevLink,
508 true,
509 "S-Tab",
510 "Focus previous link",
511 ),
512 b(
513 C::Viewer,
514 K::Enter,
515 NONE,
516 A::Activate,
517 true,
518 "⏎",
519 "Follow focused link",
520 ),
521 b(C::Viewer, K::Backspace, NONE, A::Back, true, "⌫", "Back"),
522 b(
523 C::Viewer,
524 K::Char('o'),
525 CTRL,
526 A::Back,
527 false,
528 "Ctrl+O",
529 "Back",
530 ),
531 b(
532 C::Viewer,
533 K::Char('i'),
534 CTRL,
535 A::Forward,
536 false,
537 "Ctrl+I",
538 "Forward",
539 ),
540 b(
541 C::Viewer,
542 K::Char('y'),
543 NONE,
544 A::ToggleRawYaml,
545 true,
546 "y",
547 "Toggle raw YAML",
548 ),
549 b(
550 C::Viewer,
551 K::Char('o'),
552 NONE,
553 A::Outline,
554 true,
555 "o",
556 "Outline jump list",
557 ),
558 b(
559 C::Viewer,
560 K::Char('i'),
561 NONE,
562 A::CycleInspector,
563 true,
564 "i",
565 "Cycle inspector tab",
566 ),
567 b(C::Viewer, K::Left, NONE, A::Left, true, "←", "Focus tree"),
568 b(
569 C::Viewer,
570 K::Char('s'),
571 NONE,
572 A::SplitSection,
573 true,
574 "s",
575 "Split section out",
576 ),
577 b(C::Graph, K::Up, NONE, A::Up, true, "↑", "Pan up"),
579 b(C::Graph, K::Down, NONE, A::Down, true, "↓", "Pan down"),
580 b(C::Graph, K::Left, NONE, A::Left, true, "←", "Pan left"),
581 b(C::Graph, K::Right, NONE, A::Right, true, "→", "Pan right"),
582 b(C::Graph, K::Char('k'), NONE, A::Up, false, "k", "Pan up"),
583 b(
584 C::Graph,
585 K::Char('j'),
586 NONE,
587 A::Down,
588 false,
589 "j",
590 "Pan down",
591 ),
592 b(
593 C::Graph,
594 K::Char('h'),
595 NONE,
596 A::Left,
597 false,
598 "h",
599 "Pan left",
600 ),
601 b(
602 C::Graph,
603 K::Char('l'),
604 NONE,
605 A::Right,
606 false,
607 "l",
608 "Pan right",
609 ),
610 b(
611 C::Graph,
612 K::Char('+'),
613 NONE,
614 A::ZoomIn,
615 true,
616 "+",
617 "Zoom in",
618 ),
619 b(
620 C::Graph,
621 K::Char('='),
622 NONE,
623 A::ZoomIn,
624 false,
625 "=",
626 "Zoom in",
627 ),
628 b(
629 C::Graph,
630 K::Char('-'),
631 NONE,
632 A::ZoomOut,
633 true,
634 "-",
635 "Zoom out",
636 ),
637 b(
638 C::Graph,
639 K::Tab,
640 NONE,
641 A::NextNode,
642 true,
643 "Tab",
644 "Next node",
645 ),
646 b(
647 C::Graph,
648 K::Char('n'),
649 NONE,
650 A::NextNode,
651 false,
652 "n",
653 "Next node",
654 ),
655 b(
656 C::Graph,
657 K::BackTab,
658 NONE,
659 A::PrevNode,
660 true,
661 "S-Tab",
662 "Previous node",
663 ),
664 b(
665 C::Graph,
666 K::Char('p'),
667 NONE,
668 A::PrevNode,
669 false,
670 "p",
671 "Previous node",
672 ),
673 b(
674 C::Graph,
675 K::Enter,
676 NONE,
677 A::Activate,
678 true,
679 "⏎",
680 "Open node in Explorer",
681 ),
682 b(
683 C::Graph,
684 K::Char('f'),
685 NONE,
686 A::FocusMode,
687 true,
688 "f",
689 "Focus mode (k-hop)",
690 ),
691 b(
692 C::Graph,
693 K::Char('c'),
694 NONE,
695 A::CycleColor,
696 true,
697 "c",
698 "Cycle node coloring",
699 ),
700 b(
701 C::Graph,
702 K::Char('s'),
703 NONE,
704 A::ToggleSources,
705 true,
706 "s",
707 "Toggle source nodes",
708 ),
709 b(
710 C::Graph,
711 K::Char('b'),
712 NONE,
713 A::ToggleBroken,
714 true,
715 "b",
716 "Toggle broken targets",
717 ),
718 b(
719 C::Graph,
720 K::Char('d'),
721 NONE,
722 A::ToggleDerivations,
723 true,
724 "d",
725 "Toggle derivation edges",
726 ),
727 b(
728 C::Graph,
729 K::Char(' '),
730 NONE,
731 A::PauseLayout,
732 true,
733 "Space",
734 "Pause / resume layout",
735 ),
736 b(
737 C::Graph,
738 K::Char('/'),
739 NONE,
740 A::GraphFilter,
741 true,
742 "/",
743 "Filter nodes",
744 ),
745 b(
746 C::Graph,
747 K::Char('L'),
748 NONE,
749 A::CycleLayout,
750 true,
751 "L",
752 "Cycle layout",
753 ),
754 b(C::Trust, K::Up, NONE, A::Up, true, "↑", "Previous row"),
756 b(C::Trust, K::Down, NONE, A::Down, true, "↓", "Next row"),
757 b(
758 C::Trust,
759 K::Char('k'),
760 NONE,
761 A::Up,
762 false,
763 "k",
764 "Previous row",
765 ),
766 b(
767 C::Trust,
768 K::Char('j'),
769 NONE,
770 A::Down,
771 false,
772 "j",
773 "Next row",
774 ),
775 b(
776 C::Trust,
777 K::Enter,
778 NONE,
779 A::Activate,
780 true,
781 "⏎",
782 "Open / filter",
783 ),
784 b(
785 C::Trust,
786 K::Tab,
787 NONE,
788 A::CyclePanel,
789 true,
790 "Tab",
791 "Cycle panel focus",
792 ),
793 b(
794 C::Trust,
795 K::Char('s'),
796 NONE,
797 A::CycleSort,
798 true,
799 "s",
800 "Cycle queue sort",
801 ),
802 b(
803 C::Trust,
804 K::Char('d'),
805 NONE,
806 A::Remove,
807 false,
808 "d",
809 "Remove concept",
810 ),
811 b(C::Trust, K::Home, NONE, A::Home, true, "Home", "First row"),
812 b(C::Trust, K::End, NONE, A::End, true, "End", "Last row"),
813 b(C::Computations, K::Up, NONE, A::Up, true, "↑", "Previous"),
815 b(C::Computations, K::Down, NONE, A::Down, true, "↓", "Next"),
816 b(
817 C::Computations,
818 K::Char('k'),
819 NONE,
820 A::Up,
821 false,
822 "k",
823 "Previous",
824 ),
825 b(
826 C::Computations,
827 K::Char('j'),
828 NONE,
829 A::Down,
830 false,
831 "j",
832 "Next",
833 ),
834 b(
835 C::Computations,
836 K::Tab,
837 NONE,
838 A::CyclePanel,
839 true,
840 "Tab",
841 "Focus contracts / playground",
842 ),
843 b(
844 C::Computations,
845 K::Enter,
846 NONE,
847 A::Activate,
848 true,
849 "⏎",
850 "Open in Explorer",
851 ),
852 b(
853 C::Computations,
854 K::Char('y'),
855 CTRL,
856 A::CopySketch,
857 true,
858 "Ctrl+Y",
859 "Copy invocation sketch",
860 ),
861 ]
862};
863
864fn matches(binding: &Binding, key: &KeyEvent) -> bool {
867 if binding.code != key.code {
868 return false;
869 }
870 let ignore = if matches!(key.code, KeyCode::Char(_)) {
871 KeyModifiers::SHIFT
872 } else {
873 KeyModifiers::NONE
874 };
875 (key.modifiers - ignore) == binding.mods
876}
877
878#[must_use]
880pub fn lookup(context: Context, key: &KeyEvent) -> Option<Action> {
881 bindings()
882 .iter()
883 .find(|binding| binding.context == context && matches(binding, key))
884 .map(|binding| binding.action)
885}
886
887#[must_use]
889pub fn resolve(contexts: &[Context], key: &KeyEvent) -> Option<Action> {
890 contexts.iter().find_map(|&c| lookup(c, key))
891}
892
893pub fn hints(context: Context) -> impl Iterator<Item = &'static Binding> {
895 bindings()
896 .iter()
897 .filter(move |binding| binding.context == context && binding.primary)
898}