supercode_harness/tui/
keymap.rs1use std::collections::HashMap;
10
11use super::key::{Key, KeyEvent};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum KeymapAction {
16 Submit,
18 Newline,
20 Quit,
24 HistorySearch,
27 ToggleTheme,
29 ScrollUp,
31 ScrollDown,
33 ExternalEditor,
35}
36
37impl KeymapAction {
38 pub const ALL: &'static [KeymapAction] = &[
41 KeymapAction::Submit,
42 KeymapAction::Newline,
43 KeymapAction::Quit,
44 KeymapAction::HistorySearch,
45 KeymapAction::ToggleTheme,
46 KeymapAction::ScrollUp,
47 KeymapAction::ScrollDown,
48 KeymapAction::ExternalEditor,
49 ];
50
51 pub fn name(self) -> &'static str {
54 match self {
55 KeymapAction::Submit => "submit",
56 KeymapAction::Newline => "newline",
57 KeymapAction::Quit => "quit",
58 KeymapAction::HistorySearch => "history_search",
59 KeymapAction::ToggleTheme => "toggle_theme",
60 KeymapAction::ScrollUp => "scroll_up",
61 KeymapAction::ScrollDown => "scroll_down",
62 KeymapAction::ExternalEditor => "external_editor",
63 }
64 }
65
66 pub fn default_key(self) -> KeyEvent {
68 match self {
69 KeymapAction::Submit => KeyEvent::plain(Key::Enter),
70 KeymapAction::Newline => KeyEvent {
71 key: Key::Enter,
72 ctrl: false,
73 alt: true,
74 shift: false,
75 },
76 KeymapAction::Quit => KeyEvent::ctrl(Key::Char('c')),
77 KeymapAction::HistorySearch => KeyEvent::ctrl(Key::Char('r')),
78 KeymapAction::ToggleTheme => KeyEvent::ctrl(Key::Char('t')),
79 KeymapAction::ScrollUp => KeyEvent::plain(Key::PageUp),
80 KeymapAction::ScrollDown => KeyEvent::plain(Key::PageDown),
81 KeymapAction::ExternalEditor => KeyEvent::ctrl(Key::Char('e')),
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct Keymap {
90 bindings: HashMap<KeymapAction, KeyEvent>,
91}
92
93impl Default for Keymap {
94 fn default() -> Self {
95 let bindings = KeymapAction::ALL
96 .iter()
97 .map(|&a| (a, a.default_key()))
98 .collect();
99 Keymap { bindings }
100 }
101}
102
103impl Keymap {
104 pub fn with_overrides(overrides: &HashMap<String, String>) -> Self {
111 let mut km = Keymap::default();
112 for action in KeymapAction::ALL {
113 if let Some(spec) = overrides.get(action.name()) {
114 if let Some(key) = KeyEvent::parse(spec) {
115 km.bindings.insert(*action, key);
116 }
117 }
118 }
119 km
120 }
121
122 pub fn key_for(&self, action: KeymapAction) -> KeyEvent {
124 self.bindings
125 .get(&action)
126 .copied()
127 .unwrap_or_else(|| action.default_key())
128 }
129
130 pub fn action_for(&self, key: KeyEvent) -> Option<KeymapAction> {
132 self.bindings
133 .iter()
134 .find(|(_, &bound)| bound == key)
135 .map(|(&a, _)| a)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn default_keymap_round_trips_every_action() {
145 let km = Keymap::default();
146 for &action in KeymapAction::ALL {
147 assert_eq!(km.key_for(action), action.default_key());
148 assert_eq!(km.action_for(action.default_key()), Some(action));
149 }
150 }
151
152 #[test]
153 fn override_replaces_one_binding_leaves_others_default() {
154 let mut overrides = HashMap::new();
155 overrides.insert("history_search".to_string(), "ctrl+h".to_string());
156 let km = Keymap::with_overrides(&overrides);
157 assert_eq!(
158 km.key_for(KeymapAction::HistorySearch),
159 KeyEvent::ctrl(Key::Char('h'))
160 );
161 assert_eq!(
163 km.key_for(KeymapAction::Submit),
164 KeymapAction::Submit.default_key()
165 );
166 }
167
168 #[test]
169 fn unknown_action_name_is_ignored() {
170 let mut overrides = HashMap::new();
171 overrides.insert("not_a_real_action".to_string(), "ctrl+z".to_string());
172 let km = Keymap::with_overrides(&overrides);
173 assert_eq!(km, Keymap::default());
174 }
175
176 #[test]
177 fn unparseable_key_spec_keeps_default() {
178 let mut overrides = HashMap::new();
179 overrides.insert("submit".to_string(), "not a key".to_string());
180 let km = Keymap::with_overrides(&overrides);
181 assert_eq!(
182 km.key_for(KeymapAction::Submit),
183 KeymapAction::Submit.default_key()
184 );
185 }
186
187 #[test]
188 fn action_for_unbound_key_is_none() {
189 let km = Keymap::default();
190 assert_eq!(km.action_for(KeyEvent::ch('q')), None);
191 }
192}