1use super::{CopyModeAction, Direction, KeyAction, KeyCode, KeyCombo, KeyTable, SearchAction};
7use std::collections::HashMap;
8
9pub fn default_copy_mode_table() -> KeyTable {
23 let mut table = KeyTable::new("copy_mode");
24
25 table.bind(
27 KeyCombo::key(KeyCode::KeyH),
28 KeyAction::CopyMode(CopyModeAction::MoveLeft),
29 );
30 table.bind(
31 KeyCombo::key(KeyCode::KeyJ),
32 KeyAction::CopyMode(CopyModeAction::MoveDown),
33 );
34 table.bind(
35 KeyCombo::key(KeyCode::KeyK),
36 KeyAction::CopyMode(CopyModeAction::MoveUp),
37 );
38 table.bind(
39 KeyCombo::key(KeyCode::KeyL),
40 KeyAction::CopyMode(CopyModeAction::MoveRight),
41 );
42
43 table.bind(
45 KeyCombo::key(KeyCode::Left),
46 KeyAction::CopyMode(CopyModeAction::MoveLeft),
47 );
48 table.bind(
49 KeyCombo::key(KeyCode::Down),
50 KeyAction::CopyMode(CopyModeAction::MoveDown),
51 );
52 table.bind(
53 KeyCombo::key(KeyCode::Up),
54 KeyAction::CopyMode(CopyModeAction::MoveUp),
55 );
56 table.bind(
57 KeyCombo::key(KeyCode::Right),
58 KeyAction::CopyMode(CopyModeAction::MoveRight),
59 );
60
61 table.bind(
63 KeyCombo::key(KeyCode::KeyW),
64 KeyAction::CopyMode(CopyModeAction::MoveWordForward),
65 );
66 table.bind(
67 KeyCombo::key(KeyCode::KeyB),
68 KeyAction::CopyMode(CopyModeAction::MoveWordBackward),
69 );
70 table.bind(
71 KeyCombo::key(KeyCode::KeyE),
72 KeyAction::CopyMode(CopyModeAction::MoveWordForward),
73 );
74
75 table.bind(
77 KeyCombo::key(KeyCode::Digit0),
78 KeyAction::CopyMode(CopyModeAction::MoveToLineStart),
79 );
80 table.bind(
81 KeyCombo::shift(KeyCode::Digit4), KeyAction::CopyMode(CopyModeAction::MoveToLineEnd),
83 );
84 table.bind(
85 KeyCombo::shift(KeyCode::Digit6), KeyAction::CopyMode(CopyModeAction::MoveToLineStart),
87 );
88 table.bind(
89 KeyCombo::key(KeyCode::Home),
90 KeyAction::CopyMode(CopyModeAction::MoveToLineStart),
91 );
92 table.bind(
93 KeyCombo::key(KeyCode::End),
94 KeyAction::CopyMode(CopyModeAction::MoveToLineEnd),
95 );
96
97 table.bind(
99 KeyCombo::key(KeyCode::KeyG),
100 KeyAction::CopyMode(CopyModeAction::MoveToTop),
101 );
102 table.bind(
103 KeyCombo::shift(KeyCode::KeyG), KeyAction::CopyMode(CopyModeAction::MoveToBottom),
105 );
106
107 table.bind(
109 KeyCombo::ctrl(KeyCode::KeyU),
110 KeyAction::ScrollByPage(-1), );
112 table.bind(
113 KeyCombo::ctrl(KeyCode::KeyD),
114 KeyAction::ScrollByPage(1), );
116 table.bind(
117 KeyCombo::ctrl(KeyCode::KeyB),
118 KeyAction::ScrollByPage(-2), );
120 table.bind(
121 KeyCombo::ctrl(KeyCode::KeyF),
122 KeyAction::ScrollByPage(2), );
124 table.bind(KeyCombo::key(KeyCode::PageUp), KeyAction::ScrollByPage(-1));
125 table.bind(KeyCombo::key(KeyCode::PageDown), KeyAction::ScrollByPage(1));
126
127 table.bind(
129 KeyCombo::key(KeyCode::KeyV),
130 KeyAction::CopyMode(CopyModeAction::ToggleSelection),
131 );
132 table.bind(
133 KeyCombo::shift(KeyCode::KeyV), KeyAction::CopyMode(CopyModeAction::ToggleLineSelection),
135 );
136 table.bind(
137 KeyCombo::ctrl(KeyCode::KeyV),
138 KeyAction::CopyMode(CopyModeAction::ToggleBlockSelection),
139 );
140
141 table.bind(
143 KeyCombo::key(KeyCode::Slash), KeyAction::CopyMode(CopyModeAction::SearchForward),
145 );
146 table.bind(
147 KeyCombo::shift(KeyCode::Slash), KeyAction::CopyMode(CopyModeAction::SearchBackward),
149 );
150 table.bind(
151 KeyCombo::key(KeyCode::KeyN),
152 KeyAction::CopyMode(CopyModeAction::NextMatch),
153 );
154 table.bind(
155 KeyCombo::shift(KeyCode::KeyN), KeyAction::CopyMode(CopyModeAction::PrevMatch),
157 );
158
159 table.bind(
161 KeyCombo::key(KeyCode::KeyY),
162 KeyAction::CopyMode(CopyModeAction::CopyAndExit),
163 );
164
165 table.bind(
167 KeyCombo::key(KeyCode::Escape),
168 KeyAction::CopyMode(CopyModeAction::Exit),
169 );
170 table.bind(
171 KeyCombo::key(KeyCode::KeyQ),
172 KeyAction::CopyMode(CopyModeAction::Exit),
173 );
174 table.bind(
175 KeyCombo::ctrl(KeyCode::KeyC),
176 KeyAction::CopyMode(CopyModeAction::Exit),
177 );
178
179 table
180}
181
182pub fn default_search_mode_table() -> KeyTable {
190 let mut table = KeyTable::new("search_mode");
191
192 table.bind(
194 KeyCombo::key(KeyCode::KeyN),
195 KeyAction::Search(SearchAction::NextMatch),
196 );
197 table.bind(
198 KeyCombo::shift(KeyCode::KeyN), KeyAction::Search(SearchAction::PrevMatch),
200 );
201
202 table.bind(
204 KeyCombo::key(KeyCode::Enter),
205 KeyAction::Search(SearchAction::Confirm),
206 );
207
208 table.bind(
210 KeyCombo::key(KeyCode::Escape),
211 KeyAction::Search(SearchAction::Cancel),
212 );
213 table.bind(
214 KeyCombo::ctrl(KeyCode::KeyC),
215 KeyAction::Search(SearchAction::Cancel),
216 );
217
218 table.bind(
220 KeyCombo::key(KeyCode::Down),
221 KeyAction::Search(SearchAction::NextMatch),
222 );
223 table.bind(
224 KeyCombo::key(KeyCode::Up),
225 KeyAction::Search(SearchAction::PrevMatch),
226 );
227
228 table.bind(
230 KeyCombo::ctrl(KeyCode::KeyN),
231 KeyAction::Search(SearchAction::NextMatch),
232 );
233 table.bind(
234 KeyCombo::ctrl(KeyCode::KeyP),
235 KeyAction::Search(SearchAction::PrevMatch),
236 );
237
238 table
239}
240
241pub fn default_resize_mode_table() -> KeyTable {
249 let mut table = KeyTable::new("resize_pane");
250
251 table.bind(
254 KeyCombo::key(KeyCode::KeyH),
255 KeyAction::AdjustPaneSize {
256 direction: Direction::Left,
257 amount: 2,
258 },
259 );
260 table.bind(
261 KeyCombo::key(KeyCode::KeyJ),
262 KeyAction::AdjustPaneSize {
263 direction: Direction::Down,
264 amount: 2,
265 },
266 );
267 table.bind(
268 KeyCombo::key(KeyCode::KeyK),
269 KeyAction::AdjustPaneSize {
270 direction: Direction::Up,
271 amount: 2,
272 },
273 );
274 table.bind(
275 KeyCombo::key(KeyCode::KeyL),
276 KeyAction::AdjustPaneSize {
277 direction: Direction::Right,
278 amount: 2,
279 },
280 );
281
282 table.bind(
284 KeyCombo::key(KeyCode::Left),
285 KeyAction::AdjustPaneSize {
286 direction: Direction::Left,
287 amount: 2,
288 },
289 );
290 table.bind(
291 KeyCombo::key(KeyCode::Down),
292 KeyAction::AdjustPaneSize {
293 direction: Direction::Down,
294 amount: 2,
295 },
296 );
297 table.bind(
298 KeyCombo::key(KeyCode::Up),
299 KeyAction::AdjustPaneSize {
300 direction: Direction::Up,
301 amount: 2,
302 },
303 );
304 table.bind(
305 KeyCombo::key(KeyCode::Right),
306 KeyAction::AdjustPaneSize {
307 direction: Direction::Right,
308 amount: 2,
309 },
310 );
311
312 table.bind(
314 KeyCombo::shift(KeyCode::KeyH),
315 KeyAction::AdjustPaneSize {
316 direction: Direction::Left,
317 amount: 5,
318 },
319 );
320 table.bind(
321 KeyCombo::shift(KeyCode::KeyJ),
322 KeyAction::AdjustPaneSize {
323 direction: Direction::Down,
324 amount: 5,
325 },
326 );
327 table.bind(
328 KeyCombo::shift(KeyCode::KeyK),
329 KeyAction::AdjustPaneSize {
330 direction: Direction::Up,
331 amount: 5,
332 },
333 );
334 table.bind(
335 KeyCombo::shift(KeyCode::KeyL),
336 KeyAction::AdjustPaneSize {
337 direction: Direction::Right,
338 amount: 5,
339 },
340 );
341
342 table.bind(KeyCombo::key(KeyCode::Enter), KeyAction::PopKeyTable);
344 table.bind(KeyCombo::key(KeyCode::Escape), KeyAction::PopKeyTable);
345 table.bind(KeyCombo::ctrl(KeyCode::KeyC), KeyAction::PopKeyTable);
346 table.bind(KeyCombo::key(KeyCode::KeyQ), KeyAction::PopKeyTable);
347
348 table
349}
350
351pub struct KeyTableRegistry {
355 tables: HashMap<String, KeyTable>,
356}
357
358impl KeyTableRegistry {
359 pub fn new() -> Self {
361 let mut registry = Self {
362 tables: HashMap::new(),
363 };
364
365 registry.register_table(default_copy_mode_table());
367 registry.register_table(default_search_mode_table());
368 registry.register_table(default_resize_mode_table());
369
370 registry
371 }
372
373 pub fn get(&self, name: &str) -> Option<&KeyTable> {
375 self.tables.get(name)
376 }
377
378 pub fn register(&mut self, name: impl Into<String>, table: KeyTable) {
380 self.tables.insert(name.into(), table);
381 }
382
383 fn register_table(&mut self, table: KeyTable) {
385 let name = table.name.clone();
386 self.tables.insert(name, table);
387 }
388
389 pub fn get_mut(&mut self, name: &str) -> Option<&mut KeyTable> {
391 self.tables.get_mut(name)
392 }
393
394 pub fn contains(&self, name: &str) -> bool {
396 self.tables.contains_key(name)
397 }
398
399 pub fn table_names(&self) -> Vec<&str> {
401 self.tables.keys().map(|s| s.as_str()).collect()
402 }
403}
404
405impl Default for KeyTableRegistry {
406 fn default() -> Self {
407 Self::new()
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_default_copy_mode_table() {
417 let table = default_copy_mode_table();
418 assert_eq!(table.name, "copy_mode");
419
420 let h_action = table.get(&KeyCombo::key(KeyCode::KeyH));
422 assert!(matches!(
423 h_action,
424 Some(KeyAction::CopyMode(CopyModeAction::MoveLeft))
425 ));
426
427 let j_action = table.get(&KeyCombo::key(KeyCode::KeyJ));
428 assert!(matches!(
429 j_action,
430 Some(KeyAction::CopyMode(CopyModeAction::MoveDown))
431 ));
432
433 let esc_action = table.get(&KeyCombo::key(KeyCode::Escape));
435 assert!(matches!(
436 esc_action,
437 Some(KeyAction::CopyMode(CopyModeAction::Exit))
438 ));
439
440 let q_action = table.get(&KeyCombo::key(KeyCode::KeyQ));
441 assert!(matches!(
442 q_action,
443 Some(KeyAction::CopyMode(CopyModeAction::Exit))
444 ));
445
446 let v_action = table.get(&KeyCombo::key(KeyCode::KeyV));
448 assert!(matches!(
449 v_action,
450 Some(KeyAction::CopyMode(CopyModeAction::ToggleSelection))
451 ));
452
453 let y_action = table.get(&KeyCombo::key(KeyCode::KeyY));
455 assert!(matches!(
456 y_action,
457 Some(KeyAction::CopyMode(CopyModeAction::CopyAndExit))
458 ));
459
460 let slash_action = table.get(&KeyCombo::key(KeyCode::Slash));
462 assert!(matches!(
463 slash_action,
464 Some(KeyAction::CopyMode(CopyModeAction::SearchForward))
465 ));
466
467 let shift_slash_action = table.get(&KeyCombo::shift(KeyCode::Slash));
468 assert!(matches!(
469 shift_slash_action,
470 Some(KeyAction::CopyMode(CopyModeAction::SearchBackward))
471 ));
472
473 let n_action = table.get(&KeyCombo::key(KeyCode::KeyN));
474 assert!(matches!(
475 n_action,
476 Some(KeyAction::CopyMode(CopyModeAction::NextMatch))
477 ));
478
479 let shift_n_action = table.get(&KeyCombo::shift(KeyCode::KeyN));
480 assert!(matches!(
481 shift_n_action,
482 Some(KeyAction::CopyMode(CopyModeAction::PrevMatch))
483 ));
484 }
485
486 #[test]
487 fn test_default_search_mode_table() {
488 let table = default_search_mode_table();
489 assert_eq!(table.name, "search_mode");
490
491 let n_action = table.get(&KeyCombo::key(KeyCode::KeyN));
493 assert!(matches!(
494 n_action,
495 Some(KeyAction::Search(SearchAction::NextMatch))
496 ));
497
498 let shift_n_action = table.get(&KeyCombo::shift(KeyCode::KeyN));
499 assert!(matches!(
500 shift_n_action,
501 Some(KeyAction::Search(SearchAction::PrevMatch))
502 ));
503
504 let enter_action = table.get(&KeyCombo::key(KeyCode::Enter));
506 assert!(matches!(
507 enter_action,
508 Some(KeyAction::Search(SearchAction::Confirm))
509 ));
510
511 let esc_action = table.get(&KeyCombo::key(KeyCode::Escape));
513 assert!(matches!(
514 esc_action,
515 Some(KeyAction::Search(SearchAction::Cancel))
516 ));
517 }
518
519 #[test]
520 fn test_default_resize_mode_table() {
521 let table = default_resize_mode_table();
522 assert_eq!(table.name, "resize_pane");
523
524 let h_action = table.get(&KeyCombo::key(KeyCode::KeyH));
526 assert!(matches!(
527 h_action,
528 Some(KeyAction::AdjustPaneSize {
529 direction: Direction::Left,
530 amount: 2
531 })
532 ));
533
534 let shift_h_action = table.get(&KeyCombo::shift(KeyCode::KeyH));
536 assert!(matches!(
537 shift_h_action,
538 Some(KeyAction::AdjustPaneSize {
539 direction: Direction::Left,
540 amount: 5
541 })
542 ));
543
544 let enter_action = table.get(&KeyCombo::key(KeyCode::Enter));
546 assert_eq!(enter_action, Some(&KeyAction::PopKeyTable));
547
548 let esc_action = table.get(&KeyCombo::key(KeyCode::Escape));
549 assert_eq!(esc_action, Some(&KeyAction::PopKeyTable));
550 }
551
552 #[test]
553 fn test_key_table_registry_creation() {
554 let registry = KeyTableRegistry::new();
555
556 assert!(registry.contains("copy_mode"));
558 assert!(registry.contains("search_mode"));
559 assert!(registry.contains("resize_pane"));
560 }
561
562 #[test]
563 fn test_key_table_registry_lookup() {
564 let registry = KeyTableRegistry::new();
565
566 let copy_table = registry.get("copy_mode");
568 assert!(copy_table.is_some());
569 assert_eq!(copy_table.unwrap().name, "copy_mode");
570
571 let nonexistent = registry.get("nonexistent");
572 assert!(nonexistent.is_none());
573 }
574
575 #[test]
576 fn test_key_table_registry_custom_registration() {
577 let mut registry = KeyTableRegistry::new();
578
579 let mut custom_table = KeyTable::new("custom");
580 custom_table.bind(KeyCombo::key(KeyCode::KeyA), KeyAction::Noop);
581
582 registry.register("custom", custom_table);
583
584 assert!(registry.contains("custom"));
585 let retrieved = registry.get("custom");
586 assert!(retrieved.is_some());
587 assert_eq!(retrieved.unwrap().name, "custom");
588 }
589
590 #[test]
591 fn test_key_table_registry_table_names() {
592 let registry = KeyTableRegistry::new();
593 let names = registry.table_names();
594
595 assert_eq!(names.len(), 3);
596 assert!(names.contains(&"copy_mode"));
597 assert!(names.contains(&"search_mode"));
598 assert!(names.contains(&"resize_pane"));
599 }
600
601 #[test]
602 fn test_copy_mode_arrow_keys() {
603 let table = default_copy_mode_table();
604
605 let left_action = table.get(&KeyCombo::key(KeyCode::Left));
607 assert!(matches!(
608 left_action,
609 Some(KeyAction::CopyMode(CopyModeAction::MoveLeft))
610 ));
611
612 let down_action = table.get(&KeyCombo::key(KeyCode::Down));
613 assert!(matches!(
614 down_action,
615 Some(KeyAction::CopyMode(CopyModeAction::MoveDown))
616 ));
617 }
618
619 #[test]
620 fn test_copy_mode_word_movement() {
621 let table = default_copy_mode_table();
622
623 let w_action = table.get(&KeyCombo::key(KeyCode::KeyW));
624 assert!(matches!(
625 w_action,
626 Some(KeyAction::CopyMode(CopyModeAction::MoveWordForward))
627 ));
628
629 let b_action = table.get(&KeyCombo::key(KeyCode::KeyB));
630 assert!(matches!(
631 b_action,
632 Some(KeyAction::CopyMode(CopyModeAction::MoveWordBackward))
633 ));
634 }
635
636 #[test]
637 fn test_copy_mode_page_movement() {
638 let table = default_copy_mode_table();
639
640 let ctrl_u = table.get(&KeyCombo::ctrl(KeyCode::KeyU));
641 assert!(matches!(ctrl_u, Some(KeyAction::ScrollByPage(-1))));
642
643 let ctrl_d = table.get(&KeyCombo::ctrl(KeyCode::KeyD));
644 assert!(matches!(ctrl_d, Some(KeyAction::ScrollByPage(1))));
645 }
646}