use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Context {
Global,
Tree,
Viewer,
Graph,
Trust,
Computations,
Concept,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
SwitchTab(usize),
OpenPalette,
OpenDiagnostics,
OpenHelp,
Reload,
OpenEditor,
Quit,
Up,
Down,
Left,
Right,
PageUp,
PageDown,
HalfDown,
HalfUp,
Home,
End,
Activate,
Back,
Forward,
NextLink,
PrevLink,
ToggleRawYaml,
Outline,
CycleInspector,
Verify,
ExtendStale,
MoveOrRename,
Remove,
Merge,
SplitSection,
NewConcept,
ZoomIn,
ZoomOut,
NextNode,
PrevNode,
FocusMode,
CycleColor,
ToggleSources,
ToggleBroken,
ToggleDerivations,
PauseLayout,
GraphFilter,
CycleLayout,
CyclePanel,
CycleSort,
CopySketch,
FixAll,
PinToday,
OpenLog,
}
pub struct Binding {
pub context: Context,
pub code: KeyCode,
pub mods: KeyModifiers,
pub action: Action,
pub primary: bool,
pub label: &'static str,
pub help: &'static str,
}
const fn b(
context: Context,
code: KeyCode,
mods: KeyModifiers,
action: Action,
primary: bool,
label: &'static str,
help: &'static str,
) -> Binding {
Binding {
context,
code,
mods,
action,
primary,
label,
help,
}
}
const NONE: KeyModifiers = KeyModifiers::NONE;
const CTRL: KeyModifiers = KeyModifiers::CONTROL;
#[must_use]
pub const fn bindings() -> &'static [Binding] {
BINDINGS
}
#[allow(clippy::too_many_lines)]
const BINDINGS: &[Binding] = {
use Action as A;
use Context as C;
use KeyCode as K;
&[
b(
C::Global,
K::Char('1'),
NONE,
A::SwitchTab(0),
true,
"1",
"Explorer workspace",
),
b(
C::Global,
K::Char('2'),
NONE,
A::SwitchTab(1),
true,
"2",
"Graph workspace",
),
b(
C::Global,
K::Char('3'),
NONE,
A::SwitchTab(2),
true,
"3",
"Trust workspace",
),
b(
C::Global,
K::Char('4'),
NONE,
A::SwitchTab(3),
true,
"4",
"Computations workspace",
),
b(
C::Global,
K::Char('/'),
NONE,
A::OpenPalette,
true,
"/",
"Omnisearch (> for commands)",
),
b(
C::Global,
K::Char('p'),
CTRL,
A::OpenPalette,
false,
"Ctrl+P",
"Command palette",
),
b(
C::Global,
K::Char('!'),
NONE,
A::OpenDiagnostics,
true,
"!",
"Diagnostics & fixes",
),
b(
C::Global,
K::Char('?'),
NONE,
A::OpenHelp,
true,
"?",
"Help / cheatsheet",
),
b(
C::Global,
K::Char('R'),
NONE,
A::Reload,
true,
"R",
"Reload bundle",
),
b(
C::Global,
K::Char('e'),
NONE,
A::OpenEditor,
true,
"e",
"Open in $EDITOR",
),
b(C::Global, K::Char('q'), NONE, A::Quit, true, "q", "Quit"),
b(
C::Concept,
K::Char('v'),
NONE,
A::Verify,
true,
"v",
"Stamp verification",
),
b(
C::Concept,
K::Char('x'),
NONE,
A::ExtendStale,
true,
"x",
"Extend stale_after",
),
b(
C::Concept,
K::F(2),
NONE,
A::MoveOrRename,
true,
"F2",
"Move / rename",
),
b(
C::Concept,
K::Char('m'),
NONE,
A::MoveOrRename,
false,
"m",
"Move / rename",
),
b(
C::Concept,
K::Delete,
NONE,
A::Remove,
true,
"Del",
"Remove concept",
),
b(
C::Concept,
K::Char('M'),
NONE,
A::Merge,
true,
"M",
"Merge into…",
),
b(
C::Concept,
K::Char('n'),
NONE,
A::NewConcept,
true,
"n",
"New concept here",
),
b(C::Tree, K::Up, NONE, A::Up, true, "↑", "Previous row"),
b(C::Tree, K::Down, NONE, A::Down, true, "↓", "Next row"),
b(
C::Tree,
K::Char('k'),
NONE,
A::Up,
false,
"k",
"Previous row",
),
b(C::Tree, K::Char('j'), NONE, A::Down, false, "j", "Next row"),
b(
C::Tree,
K::Left,
NONE,
A::Left,
true,
"←",
"Collapse directory",
),
b(
C::Tree,
K::Right,
NONE,
A::Right,
true,
"→",
"Expand / focus document",
),
b(
C::Tree,
K::Char('h'),
NONE,
A::Left,
false,
"h",
"Collapse directory",
),
b(
C::Tree,
K::Char('l'),
NONE,
A::Right,
false,
"l",
"Expand / focus document",
),
b(
C::Tree,
K::Enter,
NONE,
A::Activate,
true,
"⏎",
"Open selection",
),
b(C::Tree, K::Home, NONE, A::Home, true, "Home", "First row"),
b(C::Tree, K::End, NONE, A::End, true, "End", "Last row"),
b(C::Tree, K::PageUp, NONE, A::PageUp, true, "PgUp", "Page up"),
b(
C::Tree,
K::PageDown,
NONE,
A::PageDown,
true,
"PgDn",
"Page down",
),
b(
C::Tree,
K::Char('d'),
NONE,
A::Remove,
false,
"d",
"Remove concept",
),
b(
C::Tree,
K::Char('i'),
NONE,
A::CycleInspector,
true,
"i",
"Cycle inspector tab",
),
b(C::Viewer, K::Up, NONE, A::Up, true, "↑", "Scroll up"),
b(C::Viewer, K::Down, NONE, A::Down, true, "↓", "Scroll down"),
b(
C::Viewer,
K::Char('k'),
NONE,
A::Up,
false,
"k",
"Scroll up",
),
b(
C::Viewer,
K::Char('j'),
NONE,
A::Down,
false,
"j",
"Scroll down",
),
b(
C::Viewer,
K::PageUp,
NONE,
A::PageUp,
true,
"PgUp",
"Page up",
),
b(
C::Viewer,
K::PageDown,
NONE,
A::PageDown,
true,
"PgDn",
"Page down",
),
b(
C::Viewer,
K::Char('u'),
CTRL,
A::HalfUp,
false,
"Ctrl+U",
"Half page up",
),
b(
C::Viewer,
K::Char('d'),
CTRL,
A::HalfDown,
false,
"Ctrl+D",
"Half page down",
),
b(C::Viewer, K::Home, NONE, A::Home, true, "Home", "Top"),
b(C::Viewer, K::End, NONE, A::End, true, "End", "Bottom"),
b(C::Viewer, K::Char('g'), NONE, A::Home, false, "g", "Top"),
b(C::Viewer, K::Char('G'), NONE, A::End, false, "G", "Bottom"),
b(
C::Viewer,
K::Tab,
NONE,
A::NextLink,
true,
"Tab",
"Focus next link",
),
b(
C::Viewer,
K::BackTab,
NONE,
A::PrevLink,
true,
"S-Tab",
"Focus previous link",
),
b(
C::Viewer,
K::Enter,
NONE,
A::Activate,
true,
"⏎",
"Follow focused link",
),
b(C::Viewer, K::Backspace, NONE, A::Back, true, "⌫", "Back"),
b(
C::Viewer,
K::Char('o'),
CTRL,
A::Back,
false,
"Ctrl+O",
"Back",
),
b(
C::Viewer,
K::Char('i'),
CTRL,
A::Forward,
false,
"Ctrl+I",
"Forward",
),
b(
C::Viewer,
K::Char('y'),
NONE,
A::ToggleRawYaml,
true,
"y",
"Toggle raw YAML",
),
b(
C::Viewer,
K::Char('o'),
NONE,
A::Outline,
true,
"o",
"Outline jump list",
),
b(
C::Viewer,
K::Char('i'),
NONE,
A::CycleInspector,
true,
"i",
"Cycle inspector tab",
),
b(C::Viewer, K::Left, NONE, A::Left, true, "←", "Focus tree"),
b(
C::Viewer,
K::Char('s'),
NONE,
A::SplitSection,
true,
"s",
"Split section out",
),
b(C::Graph, K::Up, NONE, A::Up, true, "↑", "Pan up"),
b(C::Graph, K::Down, NONE, A::Down, true, "↓", "Pan down"),
b(C::Graph, K::Left, NONE, A::Left, true, "←", "Pan left"),
b(C::Graph, K::Right, NONE, A::Right, true, "→", "Pan right"),
b(C::Graph, K::Char('k'), NONE, A::Up, false, "k", "Pan up"),
b(
C::Graph,
K::Char('j'),
NONE,
A::Down,
false,
"j",
"Pan down",
),
b(
C::Graph,
K::Char('h'),
NONE,
A::Left,
false,
"h",
"Pan left",
),
b(
C::Graph,
K::Char('l'),
NONE,
A::Right,
false,
"l",
"Pan right",
),
b(
C::Graph,
K::Char('+'),
NONE,
A::ZoomIn,
true,
"+",
"Zoom in",
),
b(
C::Graph,
K::Char('='),
NONE,
A::ZoomIn,
false,
"=",
"Zoom in",
),
b(
C::Graph,
K::Char('-'),
NONE,
A::ZoomOut,
true,
"-",
"Zoom out",
),
b(
C::Graph,
K::Tab,
NONE,
A::NextNode,
true,
"Tab",
"Next node",
),
b(
C::Graph,
K::Char('n'),
NONE,
A::NextNode,
false,
"n",
"Next node",
),
b(
C::Graph,
K::BackTab,
NONE,
A::PrevNode,
true,
"S-Tab",
"Previous node",
),
b(
C::Graph,
K::Char('p'),
NONE,
A::PrevNode,
false,
"p",
"Previous node",
),
b(
C::Graph,
K::Enter,
NONE,
A::Activate,
true,
"⏎",
"Open node in Explorer",
),
b(
C::Graph,
K::Char('f'),
NONE,
A::FocusMode,
true,
"f",
"Focus mode (k-hop)",
),
b(
C::Graph,
K::Char('c'),
NONE,
A::CycleColor,
true,
"c",
"Cycle node coloring",
),
b(
C::Graph,
K::Char('s'),
NONE,
A::ToggleSources,
true,
"s",
"Toggle source nodes",
),
b(
C::Graph,
K::Char('b'),
NONE,
A::ToggleBroken,
true,
"b",
"Toggle broken targets",
),
b(
C::Graph,
K::Char('d'),
NONE,
A::ToggleDerivations,
true,
"d",
"Toggle derivation edges",
),
b(
C::Graph,
K::Char(' '),
NONE,
A::PauseLayout,
true,
"Space",
"Pause / resume layout",
),
b(
C::Graph,
K::Char('/'),
NONE,
A::GraphFilter,
true,
"/",
"Filter nodes",
),
b(
C::Graph,
K::Char('L'),
NONE,
A::CycleLayout,
true,
"L",
"Cycle layout",
),
b(C::Trust, K::Up, NONE, A::Up, true, "↑", "Previous row"),
b(C::Trust, K::Down, NONE, A::Down, true, "↓", "Next row"),
b(
C::Trust,
K::Char('k'),
NONE,
A::Up,
false,
"k",
"Previous row",
),
b(
C::Trust,
K::Char('j'),
NONE,
A::Down,
false,
"j",
"Next row",
),
b(
C::Trust,
K::Enter,
NONE,
A::Activate,
true,
"⏎",
"Open / filter",
),
b(
C::Trust,
K::Tab,
NONE,
A::CyclePanel,
true,
"Tab",
"Cycle panel focus",
),
b(
C::Trust,
K::Char('s'),
NONE,
A::CycleSort,
true,
"s",
"Cycle queue sort",
),
b(
C::Trust,
K::Char('d'),
NONE,
A::Remove,
false,
"d",
"Remove concept",
),
b(C::Trust, K::Home, NONE, A::Home, true, "Home", "First row"),
b(C::Trust, K::End, NONE, A::End, true, "End", "Last row"),
b(C::Computations, K::Up, NONE, A::Up, true, "↑", "Previous"),
b(C::Computations, K::Down, NONE, A::Down, true, "↓", "Next"),
b(
C::Computations,
K::Char('k'),
NONE,
A::Up,
false,
"k",
"Previous",
),
b(
C::Computations,
K::Char('j'),
NONE,
A::Down,
false,
"j",
"Next",
),
b(
C::Computations,
K::Tab,
NONE,
A::CyclePanel,
true,
"Tab",
"Focus contracts / playground",
),
b(
C::Computations,
K::Enter,
NONE,
A::Activate,
true,
"⏎",
"Open in Explorer",
),
b(
C::Computations,
K::Char('y'),
CTRL,
A::CopySketch,
true,
"Ctrl+Y",
"Copy invocation sketch",
),
]
};
fn matches(binding: &Binding, key: &KeyEvent) -> bool {
if binding.code != key.code {
return false;
}
let ignore = if matches!(key.code, KeyCode::Char(_)) {
KeyModifiers::SHIFT
} else {
KeyModifiers::NONE
};
(key.modifiers - ignore) == binding.mods
}
#[must_use]
pub fn lookup(context: Context, key: &KeyEvent) -> Option<Action> {
bindings()
.iter()
.find(|binding| binding.context == context && matches(binding, key))
.map(|binding| binding.action)
}
#[must_use]
pub fn resolve(contexts: &[Context], key: &KeyEvent) -> Option<Action> {
contexts.iter().find_map(|&c| lookup(c, key))
}
pub fn hints(context: Context) -> impl Iterator<Item = &'static Binding> {
bindings()
.iter()
.filter(move |binding| binding.context == context && binding.primary)
}