1pub use lattice_host::keymap_visual::*;
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;
17 use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
18 use lattice_grammar::{CommandRegistry, VisualKind, builtins::Builtins, builtins::populate};
19 use lattice_syntax::SyntaxMotionIds;
20 use lattice_syntax::SyntaxTextObjectIds;
21
22 fn ev(code: KeyCode, mods: KeyModifiers) -> KeyChord {
23 let raw = KeyEvent {
24 code,
25 modifiers: mods,
26 kind: KeyEventKind::Press,
27 state: KeyEventState::NONE,
28 };
29 crate::chord::from_event(&raw).expect("test event converts to a chord")
30 }
31
32 fn fixture() -> (
33 CommandRegistry,
34 Builtins,
35 ActionIds,
36 SyntaxTextObjectIds,
37 SyntaxMotionIds,
38 ) {
39 let mut r = CommandRegistry::new();
40 let b = populate(&mut r);
41 let a = crate::actions::populate(&mut r, &b);
42 let so = lattice_syntax::register_syntax_text_objects(&mut r);
43 let sm = lattice_syntax::register_syntax_motions(&mut r);
44 (r, b, a, so, sm)
45 }
46
47 fn populated_handle(
48 r: &CommandRegistry,
49 b: &Builtins,
50 a: &ActionIds,
51 so: &SyntaxTextObjectIds,
52 sm: &SyntaxMotionIds,
53 ) -> KeymapHandle {
54 let h = KeymapHandle::new();
55 h.set_command_registry(std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
59 r.clone(),
60 )));
61 register_visual_bindings(&h, b, a, so);
62 crate::keymap_normal::register_normal_bindings(&h, b, a, so, sm);
69 crate::keymap_normal::expand_grammar_rows(
72 &h,
73 r,
74 b,
75 lattice_host::keymap_trie::KeymapLayer::Builtin,
76 );
77 h
78 }
79
80 fn dv(h: &KeymapHandle, chord: &KeyChord, kind: VisualKind) -> Action {
83 dispatch_visual(h, chord, kind, &[], &[])
84 }
85
86 #[test]
87 fn esc_exits_visual_in_all_kinds() {
88 let (reg, b, a, so, sm) = fixture();
89 let h = populated_handle(®, &b, &a, &so, &sm);
90 for kind in [
91 VisualKind::Charwise,
92 VisualKind::Linewise,
93 VisualKind::Blockwise,
94 ] {
95 let r = dv(&h, &ev(KeyCode::Esc, KeyModifiers::NONE), kind);
96 match r {
97 Action::Invoke(inv) => assert_eq!(inv.command, a.exit_visual),
98 other => panic!("kind={kind:?}: expected Invoke(exit_visual), got {other:?}"),
99 }
100 }
101 }
102
103 #[test]
104 fn lowercase_v_toggles_out_of_visual() {
105 let (reg, b, a, so, sm) = fixture();
106 let h = populated_handle(®, &b, &a, &so, &sm);
107 let r = dv(
108 &h,
109 &ev(KeyCode::Char('v'), KeyModifiers::NONE),
110 VisualKind::Charwise,
111 );
112 match r {
113 Action::Invoke(inv) => assert_eq!(inv.command, a.exit_visual),
114 other => panic!("expected Invoke(exit_visual), got {other:?}"),
115 }
116 }
117
118 #[test]
119 fn uppercase_v_toggles_out_of_visual() {
120 let (reg, b, a, so, sm) = fixture();
121 let h = populated_handle(®, &b, &a, &so, &sm);
122 let r = dv(
123 &h,
124 &ev(KeyCode::Char('V'), KeyModifiers::NONE),
125 VisualKind::Linewise,
126 );
127 match r {
128 Action::Invoke(inv) => assert_eq!(inv.command, a.exit_visual),
129 other => panic!("expected Invoke(exit_visual), got {other:?}"),
130 }
131 }
132
133 #[test]
134 fn motion_h_invokes_char_left() {
135 let (reg, b, a, so, sm) = fixture();
136 let h = populated_handle(®, &b, &a, &so, &sm);
137 let r = dv(
138 &h,
139 &ev(KeyCode::Char('h'), KeyModifiers::NONE),
140 VisualKind::Charwise,
141 );
142 match r {
143 Action::Invoke(inv) => assert_eq!(inv.command, b.char_left.0),
144 other => panic!("expected Invoke(char_left), got {other:?}"),
145 }
146 }
147
148 #[test]
149 fn arrow_left_aliases_to_char_left() {
150 let (reg, b, a, so, sm) = fixture();
151 let h = populated_handle(®, &b, &a, &so, &sm);
152 let r = dv(
153 &h,
154 &ev(KeyCode::Left, KeyModifiers::NONE),
155 VisualKind::Charwise,
156 );
157 match r {
158 Action::Invoke(inv) => assert_eq!(inv.command, b.char_left.0),
159 other => panic!("expected Invoke(char_left), got {other:?}"),
160 }
161 }
162
163 #[test]
164 fn delete_in_visual_carries_selection_range() {
165 let (reg, b, a, so, sm) = fixture();
166 let h = populated_handle(®, &b, &a, &so, &sm);
167 let r = dv(
168 &h,
169 &ev(KeyCode::Char('d'), KeyModifiers::NONE),
170 VisualKind::Charwise,
171 );
172 match r {
173 Action::Invoke(inv) => {
174 assert_eq!(inv.command, b.delete.0);
175 assert!(matches!(inv.range, Some(lattice_grammar::Range::Selection)));
176 }
177 other => panic!("expected Invoke(delete, Selection), got {other:?}"),
178 }
179 }
180
181 #[test]
182 fn x_in_visual_aliases_to_delete() {
183 let (reg, b, a, so, sm) = fixture();
184 let h = populated_handle(®, &b, &a, &so, &sm);
185 let r = dv(
186 &h,
187 &ev(KeyCode::Char('x'), KeyModifiers::NONE),
188 VisualKind::Charwise,
189 );
190 match r {
191 Action::Invoke(inv) => assert_eq!(inv.command, b.delete.0),
192 other => panic!("expected Invoke(delete), got {other:?}"),
193 }
194 }
195
196 #[test]
197 fn s_in_visual_aliases_to_change() {
198 let (reg, b, a, so, sm) = fixture();
199 let h = populated_handle(®, &b, &a, &so, &sm);
200 let r = dv(
201 &h,
202 &ev(KeyCode::Char('s'), KeyModifiers::NONE),
203 VisualKind::Charwise,
204 );
205 match r {
206 Action::Invoke(inv) => assert_eq!(inv.command, b.change.0),
207 other => panic!("expected Invoke(change), got {other:?}"),
208 }
209 }
210
211 #[test]
219 fn contributed_operator_acts_on_visual_selection_by_design() {
220 let (_reg, b, a, so, sm) = fixture();
221 let _ = &a;
222 let h = KeymapHandle::new();
223 let z = crate::keymap_trie::ChordPattern::Literal(KeyChord::char('z'));
224 let n = crate::keymap_trie::ChordPattern::Literal(KeyChord::char('n'));
225 crate::keymap_normal::register_operator_bindings(
226 &h,
227 &[z, n.clone()],
228 b.delete,
229 Some(n),
230 &b,
231 &so,
232 &sm,
233 false,
234 );
235
236 let prefix = [ev(KeyCode::Char('z'), KeyModifiers::NONE)];
239 let r = dispatch_visual(
240 &h,
241 &ev(KeyCode::Char('n'), KeyModifiers::NONE),
242 VisualKind::Charwise,
243 &prefix,
244 &[],
245 );
246 match r {
247 Action::Invoke(inv) => {
248 assert_eq!(inv.command, b.delete.0);
249 assert!(
250 matches!(inv.range, Some(lattice_grammar::Range::Selection)),
251 "a contributed operator must carry Range::Selection in Visual"
252 );
253 }
254 other => panic!("expected Invoke(op, Selection), got {other:?}"),
255 }
256
257 let znj = lattice_host::keymap_normal::lookup_normal_with_prefix(
262 &h,
263 &[KeyChord::char('z'), KeyChord::char('n')],
264 &KeyChord::char('j'),
265 &[],
266 );
267 match znj {
268 Action::Invoke(inv) => assert_eq!(inv.command, b.delete.0),
269 other => panic!("expected Normal `znj` Invoke(op), got {other:?}"),
270 }
271 }
272
273 #[test]
274 fn visual_r_x_resolves_to_replace_char_on_selection() {
275 let (reg, b, a, so, sm) = fixture();
280 let h = populated_handle(®, &b, &a, &so, &sm);
281 let prefix = [ev(KeyCode::Char('r'), KeyModifiers::NONE)];
282 let r = dispatch_visual(
283 &h,
284 &ev(KeyCode::Char('X'), KeyModifiers::NONE),
285 VisualKind::Charwise,
286 &prefix,
287 &[],
288 );
289 match r {
290 Action::Invoke(inv) => {
291 assert_eq!(inv.command, b.replace_char.0);
292 assert!(
293 matches!(inv.range, Some(lattice_grammar::Range::Selection)),
294 "Visual r must carry Range::Selection, got {:?}",
295 inv.range
296 );
297 assert!(
298 matches!(inv.args, lattice_grammar::args::Args::Char('X')),
299 "captured char rides on the operator args, got {:?}",
300 inv.args
301 );
302 }
303 other => panic!("expected Invoke(replace_char, Selection, Char('X')), got {other:?}"),
304 }
305 }
306
307 #[test]
308 fn visual_r_alone_absorbs_partial_chord() {
309 let (reg, b, a, so, sm) = fixture();
310 let h = populated_handle(®, &b, &a, &so, &sm);
311 let r = dv(
312 &h,
313 &ev(KeyCode::Char('r'), KeyModifiers::NONE),
314 VisualKind::Charwise,
315 );
316 assert!(
317 matches!(r, Action::AbsorbPartialChord(c) if c == KeyChord::char('r')),
318 "expected AbsorbPartialChord(r), got {r:?}"
319 );
320 }
321
322 #[test]
323 fn capital_i_only_in_blockwise() {
324 let (reg, b, a, so, sm) = fixture();
325 let h = populated_handle(®, &b, &a, &so, &sm);
326 let r = dv(
328 &h,
329 &ev(KeyCode::Char('I'), KeyModifiers::NONE),
330 VisualKind::Charwise,
331 );
332 assert!(matches!(r, Action::None), "charwise I: {r:?}");
333 let r = dv(
335 &h,
336 &ev(KeyCode::Char('I'), KeyModifiers::NONE),
337 VisualKind::Linewise,
338 );
339 assert!(matches!(r, Action::None), "linewise I: {r:?}");
340 let r = dv(
342 &h,
343 &ev(KeyCode::Char('I'), KeyModifiers::NONE),
344 VisualKind::Blockwise,
345 );
346 assert!(
347 matches!(r, Action::EnterBlockVisualInsert),
348 "block I: {r:?}"
349 );
350 }
351
352 #[test]
353 fn capital_a_only_in_blockwise() {
354 let (reg, b, a, so, sm) = fixture();
355 let h = populated_handle(®, &b, &a, &so, &sm);
356 let r = dv(
357 &h,
358 &ev(KeyCode::Char('A'), KeyModifiers::NONE),
359 VisualKind::Charwise,
360 );
361 assert!(matches!(r, Action::None));
362 let r = dv(
363 &h,
364 &ev(KeyCode::Char('A'), KeyModifiers::NONE),
365 VisualKind::Blockwise,
366 );
367 assert!(matches!(r, Action::EnterBlockVisualAppend));
368 }
369
370 #[test]
371 fn ctrl_modifier_yields_none() {
372 let (reg, b, a, so, sm) = fixture();
373 let h = populated_handle(®, &b, &a, &so, &sm);
374 let r = dv(
375 &h,
376 &ev(KeyCode::Char('h'), KeyModifiers::CONTROL),
377 VisualKind::Charwise,
378 );
379 assert!(matches!(r, Action::None));
380 }
381
382 #[test]
387 fn alt_h_in_visual_invokes_char_left() {
388 let (reg, b, a, so, sm) = fixture();
389 let h = populated_handle(®, &b, &a, &so, &sm);
390 let r = dv(
391 &h,
392 &ev(KeyCode::Char('h'), KeyModifiers::ALT),
393 VisualKind::Charwise,
394 );
395 match r {
396 Action::Invoke(inv) => assert_eq!(inv.command, b.char_left.0),
397 other => panic!("expected Invoke(char_left), got {other:?}"),
398 }
399 }
400
401 #[test]
413 fn bare_i_absorbs_as_text_object_prefix() {
414 let (reg, b, a, so, sm) = fixture();
415 let h = populated_handle(®, &b, &a, &so, &sm);
416 let r = dv(
417 &h,
418 &ev(KeyCode::Char('i'), KeyModifiers::NONE),
419 VisualKind::Charwise,
420 );
421 match r {
422 Action::AbsorbPartialChord(c) => {
423 assert_eq!(c, ev(KeyCode::Char('i'), KeyModifiers::NONE));
424 }
425 other => panic!("expected AbsorbPartialChord(i), got {other:?}"),
426 }
427 }
428
429 #[test]
431 fn bare_a_absorbs_as_text_object_prefix() {
432 let (reg, b, a, so, sm) = fixture();
433 let h = populated_handle(®, &b, &a, &so, &sm);
434 let r = dv(
435 &h,
436 &ev(KeyCode::Char('a'), KeyModifiers::NONE),
437 VisualKind::Charwise,
438 );
439 assert!(
440 matches!(r, Action::AbsorbPartialChord(_)),
441 "expected AbsorbPartialChord(a), got {r:?}"
442 );
443 }
444
445 #[test]
449 fn viw_resolves_to_bare_inner_word() {
450 let (reg, b, a, so, sm) = fixture();
451 let h = populated_handle(®, &b, &a, &so, &sm);
452 let prefix = [ev(KeyCode::Char('i'), KeyModifiers::NONE)];
453 let r = dispatch_visual(
454 &h,
455 &ev(KeyCode::Char('w'), KeyModifiers::NONE),
456 VisualKind::Charwise,
457 &prefix,
458 &[],
459 );
460 match r {
461 Action::Invoke(inv) => {
462 assert_eq!(inv.command, b.inner_word.0);
463 assert!(inv.range.is_none(), "bare text object carries no range");
464 assert!(inv.target.is_none(), "bare text object carries no target");
465 }
466 other => panic!("expected Invoke(inner_word), got {other:?}"),
467 }
468 }
469
470 #[test]
472 fn vaw_resolves_to_bare_around_word() {
473 let (reg, b, a, so, sm) = fixture();
474 let h = populated_handle(®, &b, &a, &so, &sm);
475 let prefix = [ev(KeyCode::Char('a'), KeyModifiers::NONE)];
476 let r = dispatch_visual(
477 &h,
478 &ev(KeyCode::Char('w'), KeyModifiers::NONE),
479 VisualKind::Charwise,
480 &prefix,
481 &[],
482 );
483 match r {
484 Action::Invoke(inv) => assert_eq!(inv.command, b.around_word.0),
485 other => panic!("expected Invoke(around_word), got {other:?}"),
486 }
487 }
488
489 #[test]
492 fn vi_brace_resolves_to_inner_brace() {
493 let (reg, b, a, so, sm) = fixture();
494 let h = populated_handle(®, &b, &a, &so, &sm);
495 let prefix = [ev(KeyCode::Char('i'), KeyModifiers::NONE)];
496 let r = dispatch_visual(
497 &h,
498 &ev(KeyCode::Char('{'), KeyModifiers::NONE),
499 VisualKind::Charwise,
500 &prefix,
501 &[],
502 );
503 match r {
504 Action::Invoke(inv) => assert_eq!(inv.command, b.inner_brace.0),
505 other => panic!("expected Invoke(inner_brace), got {other:?}"),
506 }
507 }
508
509 #[test]
512 fn vaf_resolves_to_around_function() {
513 let (reg, b, a, so, sm) = fixture();
514 let h = populated_handle(®, &b, &a, &so, &sm);
515 let prefix = [ev(KeyCode::Char('a'), KeyModifiers::NONE)];
516 let r = dispatch_visual(
517 &h,
518 &ev(KeyCode::Char('f'), KeyModifiers::NONE),
519 VisualKind::Charwise,
520 &prefix,
521 &[],
522 );
523 match r {
524 Action::Invoke(inv) => assert_eq!(inv.command, so.around_function.0),
525 other => panic!("expected Invoke(around_function), got {other:?}"),
526 }
527 }
528
529 #[test]
531 fn va_capital_c_resolves_to_around_comment() {
532 let (reg, b, a, so, sm) = fixture();
533 let h = populated_handle(®, &b, &a, &so, &sm);
534 let prefix = [ev(KeyCode::Char('a'), KeyModifiers::NONE)];
535 let r = dispatch_visual(
536 &h,
537 &ev(KeyCode::Char('C'), KeyModifiers::NONE),
538 VisualKind::Charwise,
539 &prefix,
540 &[],
541 );
542 match r {
543 Action::Invoke(inv) => assert_eq!(inv.command, b.around_comment.0),
544 other => panic!("expected Invoke(around_comment), got {other:?}"),
545 }
546 }
547
548 #[test]
552 fn unbound_after_prefix_yields_none() {
553 let (reg, b, a, so, sm) = fixture();
554 let h = populated_handle(®, &b, &a, &so, &sm);
555 let prefix = [ev(KeyCode::Char('i'), KeyModifiers::NONE)];
556 let r = dispatch_visual(
557 &h,
558 &ev(KeyCode::Char('z'), KeyModifiers::NONE),
559 VisualKind::Charwise,
560 &prefix,
561 &[],
562 );
563 assert!(matches!(r, Action::None), "expected None, got {r:?}");
564 }
565}