agent_core/tui/keys/
bindings.rs1use crossterm::event::{KeyCode, KeyEvent};
4
5use super::types::KeyCombo;
6
7pub const DEFAULT_EXIT_TIMEOUT_SECS: u64 = 2;
9
10#[derive(Debug, Clone)]
20pub struct KeyBindings {
21 pub move_up: Vec<KeyCombo>,
24 pub move_down: Vec<KeyCombo>,
26 pub move_left: Vec<KeyCombo>,
28 pub move_right: Vec<KeyCombo>,
30 pub move_line_start: Vec<KeyCombo>,
32 pub move_line_end: Vec<KeyCombo>,
34
35 pub delete_char_before: Vec<KeyCombo>,
38 pub delete_char_at: Vec<KeyCombo>,
40 pub kill_line: Vec<KeyCombo>,
42 pub insert_newline: Vec<KeyCombo>,
44
45 pub submit: Vec<KeyCombo>,
48 pub interrupt: Vec<KeyCombo>,
50 pub quit: Vec<KeyCombo>,
52 pub force_quit: Vec<KeyCombo>,
54 pub enter_exit_mode: Vec<KeyCombo>,
56 pub exit_timeout_secs: u64,
58}
59
60impl Default for KeyBindings {
61 fn default() -> Self {
62 Self::bare_minimum()
63 }
64}
65
66impl KeyBindings {
67 pub fn bare_minimum() -> Self {
79 Self {
80 move_up: vec![KeyCombo::key(KeyCode::Up)],
81 move_down: vec![KeyCombo::key(KeyCode::Down)],
82 move_left: vec![KeyCombo::key(KeyCode::Left)],
83 move_right: vec![KeyCombo::key(KeyCode::Right)],
84 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
85 move_line_end: vec![KeyCombo::key(KeyCode::End)],
86
87 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
88 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
89 kill_line: vec![],
90 insert_newline: vec![],
91
92 submit: vec![KeyCombo::key(KeyCode::Enter)],
93 interrupt: vec![],
94 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')],
96 enter_exit_mode: vec![],
97 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
98 }
99 }
100
101 pub fn emacs() -> Self {
110 Self {
111 move_up: vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')],
112 move_down: vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')],
113 move_left: vec![KeyCombo::key(KeyCode::Left), KeyCombo::ctrl('b')],
114 move_right: vec![KeyCombo::key(KeyCode::Right), KeyCombo::ctrl('f')],
115 move_line_start: vec![KeyCombo::key(KeyCode::Home), KeyCombo::ctrl('a')],
116 move_line_end: vec![KeyCombo::key(KeyCode::End), KeyCombo::ctrl('e')],
117
118 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
119 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
120 kill_line: vec![KeyCombo::ctrl('k')],
121 insert_newline: vec![KeyCombo::ctrl('j')],
122
123 submit: vec![KeyCombo::key(KeyCode::Enter)],
124 interrupt: vec![KeyCombo::key(KeyCode::Esc)],
125 quit: vec![], force_quit: vec![KeyCombo::ctrl('q')],
127 enter_exit_mode: vec![KeyCombo::ctrl('d')],
128 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
129 }
130 }
131
132 pub fn minimal() -> Self {
139 Self {
140 move_up: vec![KeyCombo::key(KeyCode::Up)],
141 move_down: vec![KeyCombo::key(KeyCode::Down)],
142 move_left: vec![KeyCombo::key(KeyCode::Left)],
143 move_right: vec![KeyCombo::key(KeyCode::Right)],
144 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
145 move_line_end: vec![KeyCombo::key(KeyCode::End)],
146
147 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
148 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
149 kill_line: vec![],
150 insert_newline: vec![KeyCombo::ctrl('j')],
151
152 submit: vec![KeyCombo::key(KeyCode::Enter)],
153 interrupt: vec![],
154 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')], enter_exit_mode: vec![],
157 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
158 }
159 }
160
161 pub(crate) fn matches_any(combos: &[KeyCombo], event: &KeyEvent) -> bool {
163 combos.iter().any(|combo| combo.matches(event))
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_emacs_bindings() {
173 let bindings = KeyBindings::emacs();
174
175 let ctrl_p = KeyCombo::ctrl('p');
177 assert!(bindings.move_up.contains(&ctrl_p));
178
179 let up = KeyCombo::key(KeyCode::Up);
181 assert!(bindings.move_up.contains(&up));
182
183 assert!(bindings.quit.is_empty());
185 }
186
187 #[test]
188 fn test_minimal_bindings() {
189 let bindings = KeyBindings::minimal();
190
191 let esc = KeyCombo::key(KeyCode::Esc);
193 assert!(bindings.quit.contains(&esc));
194
195 let ctrl_p = KeyCombo::ctrl('p');
197 assert!(!bindings.move_up.contains(&ctrl_p));
198 }
199
200 #[test]
201 fn test_bare_minimum_bindings() {
202 let bindings = KeyBindings::bare_minimum();
203
204 let esc = KeyCombo::key(KeyCode::Esc);
206 assert!(bindings.quit.contains(&esc));
207 assert!(bindings.interrupt.is_empty());
208
209 let ctrl_p = KeyCombo::ctrl('p');
211 assert!(!bindings.move_up.contains(&ctrl_p));
212
213 assert!(bindings.enter_exit_mode.is_empty());
215 }
216}