1pub use lattice_host::keymap_insert::*;
8
9#[cfg(test)]
10mod tests {
11 #![allow(clippy::unwrap_used, clippy::panic, unused_imports)]
12 use super::*;
13 use crate::actions::ActionIds;
14 use crate::app::Action;
15 use crate::chord::KeyChord;
16 use crate::keymap_registry::{KeymapHandle, PushLayerKind};
17 use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
18
19 fn ev(code: KeyCode, mods: KeyModifiers) -> KeyChord {
20 let raw = KeyEvent {
21 code,
22 modifiers: mods,
23 kind: KeyEventKind::Press,
24 state: KeyEventState::NONE,
25 };
26 crate::chord::from_event(&raw).expect("test event converts to a chord")
27 }
28
29 fn shared_init() -> &'static (lattice_grammar::CommandRegistry, ActionIds) {
40 use std::sync::OnceLock;
41 static INIT: OnceLock<(lattice_grammar::CommandRegistry, ActionIds)> = OnceLock::new();
42 INIT.get_or_init(|| {
43 let mut r = lattice_grammar::CommandRegistry::new();
44 let b = lattice_grammar::builtins::populate(&mut r);
45 let _ = lattice_grammar::ex_commands::populate(&mut r);
46 let a = crate::actions::populate(&mut r, &b);
47 (r, a)
48 })
49 }
50
51 fn shared_actions() -> &'static ActionIds {
52 &shared_init().1
53 }
54
55 fn shared_registry() -> &'static lattice_grammar::CommandRegistry {
56 &shared_init().0
57 }
58
59 fn populated_handle() -> KeymapHandle {
60 let h = KeymapHandle::new();
61 register_insert_bindings(&h, shared_actions());
62 h
63 }
64
65 fn populated_handle_with_popup() -> KeymapHandle {
66 let h = populated_handle();
67 h.push_layer(
68 PushLayerKind::MinorMode(completion_popup_mode_id()),
69 "completion-popup",
70 completion_popup_layer_bindings(shared_actions()),
71 );
72 h
73 }
74
75 fn push_snippet_layer_via_k24(h: &KeymapHandle) {
78 let mut mr = lattice_mode::ModeRegistry::new();
82 mr.register(lattice_snippet::modes::SnippetActiveMode)
83 .expect("register active-snippet-mode");
84 mr.register(lattice_snippet::modes::SnippetMode::new())
89 .expect("register snippet-mode");
90 lattice_host::keymap_mode_contributions::translate_mode_keymaps(h, &mr, shared_registry());
91 }
92
93 fn populated_handle_with_snippet() -> KeymapHandle {
94 let h = populated_handle();
95 push_snippet_layer_via_k24(&h);
96 h
97 }
98
99 fn populated_handle_with_both() -> KeymapHandle {
100 let h = populated_handle();
107 push_snippet_layer_via_k24(&h);
108 h.push_layer(
109 PushLayerKind::MinorMode(completion_popup_mode_id()),
110 "completion-popup",
111 completion_popup_layer_bindings(shared_actions()),
112 );
113 h
114 }
115
116 fn popup_minors() -> Vec<lattice_mode::ModeId> {
124 vec![completion_popup_mode_id()]
125 }
126 fn snippet_minors() -> Vec<lattice_mode::ModeId> {
127 vec![
128 lattice_snippet::modes::SnippetMode::mode_id(),
129 lattice_snippet::modes::SnippetActiveMode::mode_id(),
130 ]
131 }
132 fn both_minors() -> Vec<lattice_mode::ModeId> {
133 let mut v = snippet_minors();
134 v.push(completion_popup_mode_id());
135 v
136 }
137
138 #[test]
141 fn esc_in_base_insert_returns_to_normal() {
142 let h = populated_handle();
143 let a = shared_actions();
144 match dispatch_insert(
145 &h,
146 lattice_keymap::BindingMode::Insert,
147 &ev(KeyCode::Esc, KeyModifiers::NONE),
148 &[],
149 &[],
150 ) {
151 Action::Invoke(inv) => assert_eq!(inv.command, a.enter_mode_normal),
152 other => panic!("expected Invoke(enter_mode_normal), got {other:?}"),
153 }
154 }
155
156 #[test]
157 fn backspace_in_base_insert_deletes_char_backward() {
158 let h = populated_handle();
159 let a = shared_actions();
160 let r = dispatch_insert(
161 &h,
162 lattice_keymap::BindingMode::Insert,
163 &ev(KeyCode::Backspace, KeyModifiers::NONE),
164 &[],
165 &[],
166 );
167 match r {
168 Action::Invoke(inv) => assert_eq!(inv.command, a.delete_char_backward),
169 other => panic!("expected Invoke(delete_char_backward), got {other:?}"),
170 }
171 }
172
173 #[test]
174 fn enter_in_base_insert_inserts_newline() {
175 let h = populated_handle();
176 let a = shared_actions();
177 match dispatch_insert(
178 &h,
179 lattice_keymap::BindingMode::Insert,
180 &ev(KeyCode::Enter, KeyModifiers::NONE),
181 &[],
182 &[],
183 ) {
184 Action::Invoke(inv) => assert_eq!(inv.command, a.insert_newline),
185 other => panic!("expected Invoke(insert_newline), got {other:?}"),
186 }
187 }
188
189 #[test]
190 fn tab_in_base_insert_inserts_tab() {
191 let h = populated_handle();
192 let a = shared_actions();
193 match dispatch_insert(
194 &h,
195 lattice_keymap::BindingMode::Insert,
196 &ev(KeyCode::Tab, KeyModifiers::NONE),
197 &[],
198 &[],
199 ) {
200 Action::Invoke(inv) => assert_eq!(inv.command, a.insert_tab),
201 other => panic!("expected Invoke(insert_tab), got {other:?}"),
202 }
203 }
204
205 #[test]
206 fn printable_char_in_base_insert_falls_through_to_insert() {
207 let h = populated_handle();
208 for c in ['a', 'A', '1', '$', ' '] {
209 match dispatch_insert(
210 &h,
211 lattice_keymap::BindingMode::Insert,
212 &ev(KeyCode::Char(c), KeyModifiers::NONE),
213 &[],
214 &[],
215 ) {
216 Action::Insert(s) => assert_eq!(s, c.to_string()),
217 other => panic!("char {c:?}: expected Insert, got {other:?}"),
218 }
219 }
220 }
221
222 #[test]
223 fn ctrl_letter_unbound_in_base_insert_yields_none() {
224 let h = populated_handle();
225 let r = dispatch_insert(
227 &h,
228 lattice_keymap::BindingMode::Insert,
229 &ev(KeyCode::Char('y'), KeyModifiers::CONTROL),
230 &[],
231 &[],
232 );
233 assert!(matches!(r, Action::None));
234 }
235
236 #[test]
237 fn ctrl_space_in_base_insert_triggers_completion() {
238 let h = populated_handle();
239 let a = shared_actions();
240 let r = dispatch_insert(
241 &h,
242 lattice_keymap::BindingMode::Insert,
243 &ev(KeyCode::Char(' '), KeyModifiers::CONTROL),
244 &[],
245 &[],
246 );
247 match r {
248 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_trigger),
249 other => panic!("expected Invoke(completion_trigger), got {other:?}"),
250 }
251 }
252
253 #[test]
254 fn ctrl_x_in_base_insert_absorbs_partial_chord() {
255 let h = populated_handle_with_snippet();
267 let r = dispatch_insert(
268 &h,
269 lattice_keymap::BindingMode::Insert,
270 &ev(KeyCode::Char('x'), KeyModifiers::CONTROL),
271 &[],
272 &snippet_minors(),
273 );
274 match r {
275 Action::AbsorbPartialChord(c) => assert_eq!(c, KeyChord::ctrl('x')),
276 other => panic!("expected AbsorbPartialChord(<C-x>), got {other:?}"),
277 }
278 }
279
280 #[test]
281 fn ctrl_x_then_ctrl_o_no_longer_binds() {
282 let h = populated_handle();
286 let r = dispatch_insert(
287 &h,
288 lattice_keymap::BindingMode::Insert,
289 &ev(KeyCode::Char('o'), KeyModifiers::CONTROL),
290 &[KeyChord::ctrl('x')],
291 &[],
292 );
293 assert!(
294 !matches!(r, Action::Invoke(_)),
295 "<C-x><C-o> should no longer resolve to an Invoke; got {r:?}",
296 );
297 }
298
299 #[test]
300 fn ctrl_x_then_ctrl_s_resolves_to_snippet_expand() {
301 let h = populated_handle_with_snippet();
306 let a = shared_actions();
307 let r = dispatch_insert(
308 &h,
309 lattice_keymap::BindingMode::Insert,
310 &ev(KeyCode::Char('s'), KeyModifiers::CONTROL),
311 &[KeyChord::ctrl('x')],
312 &snippet_minors(),
313 );
314 match r {
315 Action::Invoke(inv) => assert_eq!(inv.command, a.snippet_expand),
316 other => panic!("expected Invoke(snippet_expand), got {other:?}"),
317 }
318 }
319
320 #[test]
321 fn ctrl_x_then_unrecognized_drops_partial_chord() {
322 let h = populated_handle();
328 let r = dispatch_insert(
329 &h,
330 lattice_keymap::BindingMode::Insert,
331 &ev(KeyCode::Char('q'), KeyModifiers::CONTROL),
332 &[KeyChord::ctrl('x')],
333 &[],
334 );
335 assert!(matches!(r, Action::None));
336 }
337
338 #[test]
341 fn popup_ctrl_n_navigates_next() {
342 let h = populated_handle_with_popup();
343 let a = shared_actions();
344 let r = dispatch_insert(
345 &h,
346 lattice_keymap::BindingMode::Insert,
347 &ev(KeyCode::Char('n'), KeyModifiers::CONTROL),
348 &[],
349 &popup_minors(),
350 );
351 match r {
352 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_next),
353 other => panic!("expected Invoke(completion_next), got {other:?}"),
354 }
355 }
356
357 #[test]
358 fn popup_down_arrow_navigates_next() {
359 let h = populated_handle_with_popup();
360 let a = shared_actions();
361 let r = dispatch_insert(
362 &h,
363 lattice_keymap::BindingMode::Insert,
364 &ev(KeyCode::Down, KeyModifiers::NONE),
365 &[],
366 &popup_minors(),
367 );
368 match r {
369 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_next),
370 other => panic!("expected Invoke(completion_next), got {other:?}"),
371 }
372 }
373
374 #[test]
375 fn popup_tab_accepts() {
376 let h = populated_handle_with_popup();
377 let a = shared_actions();
378 let r = dispatch_insert(
379 &h,
380 lattice_keymap::BindingMode::Insert,
381 &ev(KeyCode::Tab, KeyModifiers::NONE),
382 &[],
383 &popup_minors(),
384 );
385 match r {
386 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_accept),
387 other => panic!("expected Invoke(completion_accept), got {other:?}"),
388 }
389 }
390
391 #[test]
392 fn popup_enter_accepts() {
393 let h = populated_handle_with_popup();
394 let a = shared_actions();
395 let r = dispatch_insert(
396 &h,
397 lattice_keymap::BindingMode::Insert,
398 &ev(KeyCode::Enter, KeyModifiers::NONE),
399 &[],
400 &popup_minors(),
401 );
402 match r {
403 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_accept),
404 other => panic!("expected Invoke(completion_accept), got {other:?}"),
405 }
406 }
407
408 #[test]
409 fn popup_esc_cancels_and_exits_insert() {
410 let h = populated_handle_with_popup();
411 let a = shared_actions();
412 let r = dispatch_insert(
413 &h,
414 lattice_keymap::BindingMode::Insert,
415 &ev(KeyCode::Esc, KeyModifiers::NONE),
416 &[],
417 &popup_minors(),
418 );
419 match r {
420 Action::Invoke(inv) => {
421 assert_eq!(inv.command, a.completion_cancel_and_exit_insert)
422 }
423 other => panic!("expected Invoke(completion_cancel_and_exit_insert), got {other:?}"),
424 }
425 }
426
427 #[test]
428 fn popup_ctrl_e_cancels_only() {
429 let h = populated_handle_with_popup();
430 let a = shared_actions();
431 let r = dispatch_insert(
432 &h,
433 lattice_keymap::BindingMode::Insert,
434 &ev(KeyCode::Char('e'), KeyModifiers::CONTROL),
435 &[],
436 &popup_minors(),
437 );
438 match r {
439 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_cancel),
440 other => panic!("expected Invoke(completion_cancel), got {other:?}"),
441 }
442 }
443
444 #[test]
445 fn popup_bare_char_routes_through_accept_then_insert() {
446 use lattice_grammar::args::Args;
447 let h = populated_handle_with_popup();
448 let a = shared_actions();
449 let r = dispatch_insert(
450 &h,
451 lattice_keymap::BindingMode::Insert,
452 &ev(KeyCode::Char('a'), KeyModifiers::NONE),
453 &[],
454 &popup_minors(),
455 );
456 match r {
457 Action::Invoke(inv) => {
458 assert_eq!(inv.command, a.completion_accept_then_insert);
459 assert!(matches!(inv.args, Args::Char('a')));
460 }
461 other => {
462 panic!("expected Invoke(completion_accept_then_insert, Char('a')), got {other:?}")
463 }
464 }
465 }
466
467 #[test]
471 fn popup_ctrl_b_filters_to_buffer_words() {
472 use lattice_grammar::args::Args;
473 let h = populated_handle_with_popup();
474 let a = shared_actions();
475 let r = dispatch_insert(
476 &h,
477 lattice_keymap::BindingMode::Insert,
478 &ev(KeyCode::Char('b'), KeyModifiers::CONTROL),
479 &[],
480 &popup_minors(),
481 );
482 match r {
483 Action::Invoke(inv) => {
484 assert_eq!(inv.command, a.completion_filter_to_source);
485 match inv.args {
486 Args::String(s) => {
487 assert_eq!(s, lattice_completion::insert::BufferWordsSource::ID)
488 }
489 other => panic!("expected Args::String, got {other:?}"),
490 }
491 }
492 other => panic!(
493 "expected Invoke(completion_filter_to_source, \"gen:buffer-words\"), got {other:?}"
494 ),
495 }
496 }
497
498 #[test]
500 fn popup_ctrl_o_filters_to_lsp() {
501 use lattice_grammar::args::Args;
502 let h = populated_handle_with_popup();
503 let a = shared_actions();
504 let r = dispatch_insert(
505 &h,
506 lattice_keymap::BindingMode::Insert,
507 &ev(KeyCode::Char('o'), KeyModifiers::CONTROL),
508 &[],
509 &popup_minors(),
510 );
511 match r {
512 Action::Invoke(inv) => {
513 assert_eq!(inv.command, a.completion_filter_to_source);
514 match inv.args {
515 Args::String(s) => {
516 assert_eq!(s, lattice_completion::insert::LSP_COMPLETION_SOURCE_ID)
517 }
518 other => panic!("expected Args::String, got {other:?}"),
519 }
520 }
521 other => panic!("expected Invoke(completion_filter_to_source, lsp), got {other:?}"),
522 }
523 }
524
525 #[test]
529 fn popup_ctrl_space_clears_filter() {
530 let h = populated_handle_with_popup();
531 let a = shared_actions();
532 let r = dispatch_insert(
533 &h,
534 lattice_keymap::BindingMode::Insert,
535 &ev(KeyCode::Char(' '), KeyModifiers::CONTROL),
536 &[],
537 &popup_minors(),
538 );
539 match r {
540 Action::Invoke(inv) => {
541 assert_eq!(inv.command, a.completion_filter_clear);
542 }
543 other => panic!("expected Invoke(completion_filter_clear), got {other:?}"),
544 }
545 }
546
547 #[test]
550 fn popup_page_down_scrolls_docs() {
551 let h = populated_handle_with_popup();
552 let a = shared_actions();
553 let r = dispatch_insert(
554 &h,
555 lattice_keymap::BindingMode::Insert,
556 &ev(KeyCode::PageDown, KeyModifiers::NONE),
557 &[],
558 &popup_minors(),
559 );
560 match r {
561 Action::Invoke(inv) => {
562 assert_eq!(inv.command, a.completion_docs_scroll_down)
563 }
564 other => panic!("expected Invoke(completion_docs_scroll_down), got {other:?}"),
565 }
566 }
567
568 #[test]
569 fn popup_ctrl_x_falls_through_to_base_insert_partial_chord() {
570 let h = populated_handle_with_both();
582 let r = dispatch_insert(
583 &h,
584 lattice_keymap::BindingMode::Insert,
585 &ev(KeyCode::Char('x'), KeyModifiers::CONTROL),
586 &[],
587 &both_minors(),
588 );
589 match r {
590 Action::AbsorbPartialChord(c) => assert_eq!(c, KeyChord::ctrl('x')),
591 other => panic!("expected AbsorbPartialChord(<C-x>), got {other:?}"),
592 }
593 }
594
595 #[test]
598 fn snippet_tab_jumps_to_next_placeholder() {
599 let h = populated_handle_with_snippet();
600 let a = shared_actions();
601 let r = dispatch_insert(
602 &h,
603 lattice_keymap::BindingMode::Insert,
604 &ev(KeyCode::Tab, KeyModifiers::NONE),
605 &[],
606 &snippet_minors(),
607 );
608 match r {
609 Action::Invoke(inv) => {
610 assert_eq!(inv.command, a.snippet_next_placeholder)
611 }
612 other => panic!("expected Invoke(snippet_next_placeholder), got {other:?}"),
613 }
614 }
615
616 #[test]
617 fn snippet_back_tab_jumps_to_prev_placeholder() {
618 let h = populated_handle_with_snippet();
619 let a = shared_actions();
620 let r = dispatch_insert(
621 &h,
622 lattice_keymap::BindingMode::Insert,
623 &ev(KeyCode::BackTab, KeyModifiers::NONE),
624 &[],
625 &snippet_minors(),
626 );
627 match r {
628 Action::Invoke(inv) => {
629 assert_eq!(inv.command, a.snippet_prev_placeholder)
630 }
631 other => panic!("expected Invoke(snippet_prev_placeholder), got {other:?}"),
632 }
633 }
634
635 #[test]
636 fn snippet_shift_tab_jumps_to_prev_placeholder() {
637 let h = populated_handle_with_snippet();
638 let a = shared_actions();
639 let r = dispatch_insert(
640 &h,
641 lattice_keymap::BindingMode::Insert,
642 &ev(KeyCode::Tab, KeyModifiers::SHIFT),
643 &[],
644 &snippet_minors(),
645 );
646 match r {
647 Action::Invoke(inv) => {
648 assert_eq!(inv.command, a.snippet_prev_placeholder)
649 }
650 other => panic!("expected Invoke(snippet_prev_placeholder), got {other:?}"),
651 }
652 }
653
654 #[test]
655 fn snippet_esc_leaves_snippet() {
656 let h = populated_handle_with_snippet();
657 let a = shared_actions();
658 let r = dispatch_insert(
659 &h,
660 lattice_keymap::BindingMode::Insert,
661 &ev(KeyCode::Esc, KeyModifiers::NONE),
662 &[],
663 &snippet_minors(),
664 );
665 match r {
669 Action::Chain(v) => match (v.first(), v.get(1)) {
670 (Some(Action::Invoke(leave)), Some(Action::Invoke(native))) => {
671 assert_eq!(leave.command, a.snippet_leave);
672 assert_eq!(native.command, a.enter_mode_normal);
673 assert_eq!(v.len(), 2);
674 }
675 _ => panic!("expected Chain([snippet_leave, enter_mode_normal]), got {v:?}"),
676 },
677 other => panic!("expected Chain, got {other:?}"),
678 }
679 }
680
681 #[test]
682 fn snippet_unrelated_key_falls_through_to_base_insert() {
683 let h = populated_handle_with_snippet();
684 let r = dispatch_insert(
685 &h,
686 lattice_keymap::BindingMode::Insert,
687 &ev(KeyCode::Char('z'), KeyModifiers::NONE),
688 &[],
689 &snippet_minors(),
690 );
691 match r {
692 Action::Insert(s) => assert_eq!(s, "z"),
693 other => panic!("expected Insert(z), got {other:?}"),
694 }
695 }
696
697 #[test]
700 fn popup_wins_over_snippet_for_tab() {
701 let h = populated_handle_with_both();
702 let a = shared_actions();
703 let r = dispatch_insert(
704 &h,
705 lattice_keymap::BindingMode::Insert,
706 &ev(KeyCode::Tab, KeyModifiers::NONE),
707 &[],
708 &both_minors(),
709 );
710 match r {
714 Action::Invoke(inv) => assert_eq!(inv.command, a.completion_accept),
715 other => panic!("expected Invoke(completion_accept), got {other:?}"),
716 }
717 }
718
719 #[test]
720 fn popup_wins_over_snippet_for_esc() {
721 let h = populated_handle_with_both();
722 let a = shared_actions();
723 let r = dispatch_insert(
724 &h,
725 lattice_keymap::BindingMode::Insert,
726 &ev(KeyCode::Esc, KeyModifiers::NONE),
727 &[],
728 &both_minors(),
729 );
730 match r {
731 Action::Invoke(inv) => {
732 assert_eq!(inv.command, a.completion_cancel_and_exit_insert)
733 }
734 other => panic!("expected Invoke(completion_cancel_and_exit_insert), got {other:?}"),
735 }
736 }
737
738 #[test]
739 fn shift_tab_with_both_overlays_resolves_via_snippet_layer() {
740 let h = populated_handle_with_both();
743 let a = shared_actions();
744 let r = dispatch_insert(
745 &h,
746 lattice_keymap::BindingMode::Insert,
747 &ev(KeyCode::BackTab, KeyModifiers::NONE),
748 &[],
749 &both_minors(),
750 );
751 match r {
752 Action::Invoke(inv) => {
753 assert_eq!(inv.command, a.snippet_prev_placeholder)
754 }
755 other => panic!("expected Invoke(snippet_prev_placeholder), got {other:?}"),
756 }
757 }
758}