1mod case;
2mod change;
3mod delete;
4mod paste;
5mod repeat;
6mod scroll;
7mod search;
8pub mod substitute;
9mod yank;
10
11use std::sync::Arc;
12
13use crate::{
14 motion::{self, Motion},
15 object::Object,
16 state::{Mode, Operator},
17 Vim,
18};
19use collections::HashSet;
20use editor::scroll::autoscroll::Autoscroll;
21use editor::{Bias, DisplayPoint};
22use gpui::{actions, AppContext, ViewContext, WindowContext};
23use language::SelectionGoal;
24use log::error;
25use workspace::Workspace;
26
27use self::{
28 case::change_case,
29 change::{change_motion, change_object},
30 delete::{delete_motion, delete_object},
31 yank::{yank_motion, yank_object},
32};
33
34actions!(
35 vim,
36 [
37 InsertAfter,
38 InsertBefore,
39 InsertFirstNonWhitespace,
40 InsertEndOfLine,
41 InsertLineAbove,
42 InsertLineBelow,
43 DeleteLeft,
44 DeleteRight,
45 ChangeToEndOfLine,
46 DeleteToEndOfLine,
47 Yank,
48 ChangeCase,
49 ]
50);
51
52pub fn init(cx: &mut AppContext) {
53 paste::init(cx);
54 repeat::init(cx);
55 scroll::init(cx);
56 search::init(cx);
57 substitute::init(cx);
58
59 cx.add_action(insert_after);
60 cx.add_action(insert_before);
61 cx.add_action(insert_first_non_whitespace);
62 cx.add_action(insert_end_of_line);
63 cx.add_action(insert_line_above);
64 cx.add_action(insert_line_below);
65 cx.add_action(change_case);
66
67 cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
68 Vim::update(cx, |vim, cx| {
69 vim.record_current_action();
70 let times = vim.pop_number_operator(cx);
71 delete_motion(vim, Motion::Left, times, cx);
72 })
73 });
74 cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
75 Vim::update(cx, |vim, cx| {
76 vim.record_current_action();
77 let times = vim.pop_number_operator(cx);
78 delete_motion(vim, Motion::Right, times, cx);
79 })
80 });
81 cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
82 Vim::update(cx, |vim, cx| {
83 vim.start_recording();
84 let times = vim.pop_number_operator(cx);
85 change_motion(
86 vim,
87 Motion::EndOfLine {
88 display_lines: false,
89 },
90 times,
91 cx,
92 );
93 })
94 });
95 cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
96 Vim::update(cx, |vim, cx| {
97 vim.record_current_action();
98 let times = vim.pop_number_operator(cx);
99 delete_motion(
100 vim,
101 Motion::EndOfLine {
102 display_lines: false,
103 },
104 times,
105 cx,
106 );
107 })
108 });
109}
110
111pub fn normal_motion(
112 motion: Motion,
113 operator: Option<Operator>,
114 times: Option<usize>,
115 cx: &mut WindowContext,
116) {
117 Vim::update(cx, |vim, cx| {
118 match operator {
119 None => move_cursor(vim, motion, times, cx),
120 Some(Operator::Change) => change_motion(vim, motion, times, cx),
121 Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
122 Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
123 Some(operator) => {
124 // Can't do anything for text objects, Ignoring
125 error!("Unexpected normal mode motion operator: {:?}", operator)
126 }
127 }
128 });
129}
130
131pub fn normal_object(object: Object, cx: &mut WindowContext) {
132 Vim::update(cx, |vim, cx| {
133 match vim.maybe_pop_operator() {
134 Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
135 Some(Operator::Change) => change_object(vim, object, around, cx),
136 Some(Operator::Delete) => delete_object(vim, object, around, cx),
137 Some(Operator::Yank) => yank_object(vim, object, around, cx),
138 _ => {
139 // Can't do anything for namespace operators. Ignoring
140 }
141 },
142 _ => {
143 // Can't do anything with change/delete/yank and text objects. Ignoring
144 }
145 }
146 vim.clear_operator(cx);
147 })
148}
149
150fn move_cursor(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
151 vim.update_active_editor(cx, |editor, cx| {
152 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
153 s.move_cursors_with(|map, cursor, goal| {
154 motion
155 .move_point(map, cursor, goal, times)
156 .unwrap_or((cursor, goal))
157 })
158 })
159 });
160}
161
162fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
163 Vim::update(cx, |vim, cx| {
164 vim.start_recording();
165 vim.switch_mode(Mode::Insert, false, cx);
166 vim.update_active_editor(cx, |editor, cx| {
167 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
168 s.maybe_move_cursors_with(|map, cursor, goal| {
169 Motion::Right.move_point(map, cursor, goal, None)
170 });
171 });
172 });
173 });
174}
175
176fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
177 Vim::update(cx, |vim, cx| {
178 vim.start_recording();
179 vim.switch_mode(Mode::Insert, false, cx);
180 });
181}
182
183fn insert_first_non_whitespace(
184 _: &mut Workspace,
185 _: &InsertFirstNonWhitespace,
186 cx: &mut ViewContext<Workspace>,
187) {
188 Vim::update(cx, |vim, cx| {
189 vim.start_recording();
190 vim.switch_mode(Mode::Insert, false, cx);
191 vim.update_active_editor(cx, |editor, cx| {
192 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
193 s.maybe_move_cursors_with(|map, cursor, goal| {
194 Motion::FirstNonWhitespace {
195 display_lines: false,
196 }
197 .move_point(map, cursor, goal, None)
198 });
199 });
200 });
201 });
202}
203
204fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
205 Vim::update(cx, |vim, cx| {
206 vim.start_recording();
207 vim.switch_mode(Mode::Insert, false, cx);
208 vim.update_active_editor(cx, |editor, cx| {
209 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
210 s.maybe_move_cursors_with(|map, cursor, goal| {
211 Motion::CurrentLine.move_point(map, cursor, goal, None)
212 });
213 });
214 });
215 });
216}
217
218fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
219 Vim::update(cx, |vim, cx| {
220 vim.start_recording();
221 vim.switch_mode(Mode::Insert, false, cx);
222 vim.update_active_editor(cx, |editor, cx| {
223 editor.transact(cx, |editor, cx| {
224 let (map, old_selections) = editor.selections.all_display(cx);
225 let selection_start_rows: HashSet<u32> = old_selections
226 .into_iter()
227 .map(|selection| selection.start.row())
228 .collect();
229 let edits = selection_start_rows.into_iter().map(|row| {
230 let (indent, _) = map.line_indent(row);
231 let start_of_line =
232 motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
233 .to_point(&map);
234 let mut new_text = " ".repeat(indent as usize);
235 new_text.push('\n');
236 (start_of_line..start_of_line, new_text)
237 });
238 editor.edit_with_autoindent(edits, cx);
239 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
240 s.move_cursors_with(|map, cursor, _| {
241 let previous_line = motion::up(map, cursor, SelectionGoal::None, 1).0;
242 let insert_point = motion::end_of_line(map, false, previous_line);
243 (insert_point, SelectionGoal::None)
244 });
245 });
246 });
247 });
248 });
249}
250
251fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
252 Vim::update(cx, |vim, cx| {
253 vim.start_recording();
254 vim.switch_mode(Mode::Insert, false, cx);
255 vim.update_active_editor(cx, |editor, cx| {
256 editor.transact(cx, |editor, cx| {
257 let (map, old_selections) = editor.selections.all_display(cx);
258
259 let selection_end_rows: HashSet<u32> = old_selections
260 .into_iter()
261 .map(|selection| selection.end.row())
262 .collect();
263 let edits = selection_end_rows.into_iter().map(|row| {
264 let (indent, _) = map.line_indent(row);
265 let end_of_line =
266 motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
267
268 let mut new_text = "\n".to_string();
269 new_text.push_str(&" ".repeat(indent as usize));
270 (end_of_line..end_of_line, new_text)
271 });
272 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
273 s.maybe_move_cursors_with(|map, cursor, goal| {
274 Motion::CurrentLine.move_point(map, cursor, goal, None)
275 });
276 });
277 editor.edit_with_autoindent(edits, cx);
278 });
279 });
280 });
281}
282
283pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
284 Vim::update(cx, |vim, cx| {
285 vim.stop_recording();
286 vim.update_active_editor(cx, |editor, cx| {
287 editor.transact(cx, |editor, cx| {
288 editor.set_clip_at_line_ends(false, cx);
289 let (map, display_selections) = editor.selections.all_display(cx);
290 // Selections are biased right at the start. So we need to store
291 // anchors that are biased left so that we can restore the selections
292 // after the change
293 let stable_anchors = editor
294 .selections
295 .disjoint_anchors()
296 .into_iter()
297 .map(|selection| {
298 let start = selection.start.bias_left(&map.buffer_snapshot);
299 start..start
300 })
301 .collect::<Vec<_>>();
302
303 let edits = display_selections
304 .into_iter()
305 .map(|selection| {
306 let mut range = selection.range();
307 *range.end.column_mut() += 1;
308 range.end = map.clip_point(range.end, Bias::Right);
309
310 (
311 range.start.to_offset(&map, Bias::Left)
312 ..range.end.to_offset(&map, Bias::Left),
313 text.clone(),
314 )
315 })
316 .collect::<Vec<_>>();
317
318 editor.buffer().update(cx, |buffer, cx| {
319 buffer.edit(edits, None, cx);
320 });
321 editor.set_clip_at_line_ends(true, cx);
322 editor.change_selections(None, cx, |s| {
323 s.select_anchor_ranges(stable_anchors);
324 });
325 });
326 });
327 vim.pop_operator(cx)
328 });
329}
330
331#[cfg(test)]
332mod test {
333 use gpui::TestAppContext;
334 use indoc::indoc;
335
336 use crate::{
337 state::Mode::{self},
338 test::{ExemptionFeatures, NeovimBackedTestContext},
339 };
340
341 #[gpui::test]
342 async fn test_h(cx: &mut gpui::TestAppContext) {
343 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
344 cx.assert_all(indoc! {"
345 ˇThe qˇuick
346 ˇbrown"
347 })
348 .await;
349 }
350
351 #[gpui::test]
352 async fn test_backspace(cx: &mut gpui::TestAppContext) {
353 let mut cx = NeovimBackedTestContext::new(cx)
354 .await
355 .binding(["backspace"]);
356 cx.assert_all(indoc! {"
357 ˇThe qˇuick
358 ˇbrown"
359 })
360 .await;
361 }
362
363 #[gpui::test]
364 async fn test_j(cx: &mut gpui::TestAppContext) {
365 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["j"]);
366 cx.assert_all(indoc! {"
367 ˇThe qˇuick broˇwn
368 ˇfox jumps"
369 })
370 .await;
371 }
372
373 #[gpui::test]
374 async fn test_enter(cx: &mut gpui::TestAppContext) {
375 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
376 cx.assert_all(indoc! {"
377 ˇThe qˇuick broˇwn
378 ˇfox jumps"
379 })
380 .await;
381 }
382
383 #[gpui::test]
384 async fn test_k(cx: &mut gpui::TestAppContext) {
385 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
386 cx.assert_all(indoc! {"
387 ˇThe qˇuick
388 ˇbrown fˇox jumˇps"
389 })
390 .await;
391 }
392
393 #[gpui::test]
394 async fn test_l(cx: &mut gpui::TestAppContext) {
395 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
396 cx.assert_all(indoc! {"
397 ˇThe qˇuicˇk
398 ˇbrowˇn"})
399 .await;
400 }
401
402 #[gpui::test]
403 async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
404 let mut cx = NeovimBackedTestContext::new(cx).await;
405 cx.assert_binding_matches_all(
406 ["$"],
407 indoc! {"
408 ˇThe qˇuicˇk
409 ˇbrowˇn"},
410 )
411 .await;
412 cx.assert_binding_matches_all(
413 ["0"],
414 indoc! {"
415 ˇThe qˇuicˇk
416 ˇbrowˇn"},
417 )
418 .await;
419 }
420
421 #[gpui::test]
422 async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
423 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
424
425 cx.assert_all(indoc! {"
426 The ˇquick
427
428 brown fox jumps
429 overˇ the lazy doˇg"})
430 .await;
431 cx.assert(indoc! {"
432 The quiˇck
433
434 brown"})
435 .await;
436 cx.assert(indoc! {"
437 The quiˇck
438
439 "})
440 .await;
441 }
442
443 #[gpui::test]
444 async fn test_w(cx: &mut gpui::TestAppContext) {
445 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
446 cx.assert_all(indoc! {"
447 The ˇquickˇ-ˇbrown
448 ˇ
449 ˇ
450 ˇfox_jumps ˇover
451 ˇthˇe"})
452 .await;
453 let mut cx = cx.binding(["shift-w"]);
454 cx.assert_all(indoc! {"
455 The ˇquickˇ-ˇbrown
456 ˇ
457 ˇ
458 ˇfox_jumps ˇover
459 ˇthˇe"})
460 .await;
461 }
462
463 #[gpui::test]
464 async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
465 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
466 cx.assert_all(indoc! {"
467 Thˇe quicˇkˇ-browˇn
468
469
470 fox_jumpˇs oveˇr
471 thˇe"})
472 .await;
473 let mut cx = cx.binding(["shift-e"]);
474 cx.assert_all(indoc! {"
475 Thˇe quicˇkˇ-browˇn
476
477
478 fox_jumpˇs oveˇr
479 thˇe"})
480 .await;
481 }
482
483 #[gpui::test]
484 async fn test_b(cx: &mut gpui::TestAppContext) {
485 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
486 cx.assert_all(indoc! {"
487 ˇThe ˇquickˇ-ˇbrown
488 ˇ
489 ˇ
490 ˇfox_jumps ˇover
491 ˇthe"})
492 .await;
493 let mut cx = cx.binding(["shift-b"]);
494 cx.assert_all(indoc! {"
495 ˇThe ˇquickˇ-ˇbrown
496 ˇ
497 ˇ
498 ˇfox_jumps ˇover
499 ˇthe"})
500 .await;
501 }
502
503 #[gpui::test]
504 async fn test_gg(cx: &mut gpui::TestAppContext) {
505 let mut cx = NeovimBackedTestContext::new(cx).await;
506 cx.assert_binding_matches_all(
507 ["g", "g"],
508 indoc! {"
509 The qˇuick
510
511 brown fox jumps
512 over ˇthe laˇzy dog"},
513 )
514 .await;
515 cx.assert_binding_matches(
516 ["g", "g"],
517 indoc! {"
518
519
520 brown fox jumps
521 over the laˇzy dog"},
522 )
523 .await;
524 cx.assert_binding_matches(
525 ["2", "g", "g"],
526 indoc! {"
527 ˇ
528
529 brown fox jumps
530 over the lazydog"},
531 )
532 .await;
533 }
534
535 #[gpui::test]
536 async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
537 let mut cx = NeovimBackedTestContext::new(cx).await;
538 cx.assert_binding_matches_all(
539 ["shift-g"],
540 indoc! {"
541 The qˇuick
542
543 brown fox jumps
544 over ˇthe laˇzy dog"},
545 )
546 .await;
547 cx.assert_binding_matches(
548 ["shift-g"],
549 indoc! {"
550
551
552 brown fox jumps
553 over the laˇzy dog"},
554 )
555 .await;
556 cx.assert_binding_matches(
557 ["2", "shift-g"],
558 indoc! {"
559 ˇ
560
561 brown fox jumps
562 over the lazydog"},
563 )
564 .await;
565 }
566
567 #[gpui::test]
568 async fn test_a(cx: &mut gpui::TestAppContext) {
569 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
570 cx.assert_all("The qˇuicˇk").await;
571 }
572
573 #[gpui::test]
574 async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
575 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
576 cx.assert_all(indoc! {"
577 ˇ
578 The qˇuick
579 brown ˇfox "})
580 .await;
581 }
582
583 #[gpui::test]
584 async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
585 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
586 cx.assert("The qˇuick").await;
587 cx.assert(" The qˇuick").await;
588 cx.assert("ˇ").await;
589 cx.assert(indoc! {"
590 The qˇuick
591 brown fox"})
592 .await;
593 cx.assert(indoc! {"
594 ˇ
595 The quick"})
596 .await;
597 // Indoc disallows trailing whitespace.
598 cx.assert(" ˇ \nThe quick").await;
599 }
600
601 #[gpui::test]
602 async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
603 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
604 cx.assert("The qˇuick").await;
605 cx.assert(" The qˇuick").await;
606 cx.assert("ˇ").await;
607 cx.assert(indoc! {"
608 The qˇuick
609 brown fox"})
610 .await;
611 cx.assert(indoc! {"
612 ˇ
613 The quick"})
614 .await;
615 }
616
617 #[gpui::test]
618 async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
619 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
620 cx.assert(indoc! {"
621 The qˇuick
622 brown fox"})
623 .await;
624 cx.assert(indoc! {"
625 The quick
626 ˇ
627 brown fox"})
628 .await;
629 }
630
631 #[gpui::test]
632 async fn test_x(cx: &mut gpui::TestAppContext) {
633 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
634 cx.assert_all("ˇTeˇsˇt").await;
635 cx.assert(indoc! {"
636 Tesˇt
637 test"})
638 .await;
639 }
640
641 #[gpui::test]
642 async fn test_delete_left(cx: &mut gpui::TestAppContext) {
643 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
644 cx.assert_all("ˇTˇeˇsˇt").await;
645 cx.assert(indoc! {"
646 Test
647 ˇtest"})
648 .await;
649 }
650
651 #[gpui::test]
652 async fn test_o(cx: &mut gpui::TestAppContext) {
653 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
654 cx.assert("ˇ").await;
655 cx.assert("The ˇquick").await;
656 cx.assert_all(indoc! {"
657 The qˇuick
658 brown ˇfox
659 jumps ˇover"})
660 .await;
661 cx.assert(indoc! {"
662 The quick
663 ˇ
664 brown fox"})
665 .await;
666
667 cx.assert_manual(
668 indoc! {"
669 fn test() {
670 println!(ˇ);
671 }"},
672 Mode::Normal,
673 indoc! {"
674 fn test() {
675 println!();
676 ˇ
677 }"},
678 Mode::Insert,
679 );
680
681 cx.assert_manual(
682 indoc! {"
683 fn test(ˇ) {
684 println!();
685 }"},
686 Mode::Normal,
687 indoc! {"
688 fn test() {
689 ˇ
690 println!();
691 }"},
692 Mode::Insert,
693 );
694 }
695
696 #[gpui::test]
697 async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
698 let cx = NeovimBackedTestContext::new(cx).await;
699 let mut cx = cx.binding(["shift-o"]);
700 cx.assert("ˇ").await;
701 cx.assert("The ˇquick").await;
702 cx.assert_all(indoc! {"
703 The qˇuick
704 brown ˇfox
705 jumps ˇover"})
706 .await;
707 cx.assert(indoc! {"
708 The quick
709 ˇ
710 brown fox"})
711 .await;
712
713 // Our indentation is smarter than vims. So we don't match here
714 cx.assert_manual(
715 indoc! {"
716 fn test() {
717 println!(ˇ);
718 }"},
719 Mode::Normal,
720 indoc! {"
721 fn test() {
722 ˇ
723 println!();
724 }"},
725 Mode::Insert,
726 );
727 cx.assert_manual(
728 indoc! {"
729 fn test(ˇ) {
730 println!();
731 }"},
732 Mode::Normal,
733 indoc! {"
734 ˇ
735 fn test() {
736 println!();
737 }"},
738 Mode::Insert,
739 );
740 }
741
742 #[gpui::test]
743 async fn test_dd(cx: &mut gpui::TestAppContext) {
744 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "d"]);
745 cx.assert("ˇ").await;
746 cx.assert("The ˇquick").await;
747 cx.assert_all(indoc! {"
748 The qˇuick
749 brown ˇfox
750 jumps ˇover"})
751 .await;
752 cx.assert_exempted(
753 indoc! {"
754 The quick
755 ˇ
756 brown fox"},
757 ExemptionFeatures::DeletionOnEmptyLine,
758 )
759 .await;
760 }
761
762 #[gpui::test]
763 async fn test_cc(cx: &mut gpui::TestAppContext) {
764 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
765 cx.assert("ˇ").await;
766 cx.assert("The ˇquick").await;
767 cx.assert_all(indoc! {"
768 The quˇick
769 brown ˇfox
770 jumps ˇover"})
771 .await;
772 cx.assert(indoc! {"
773 The quick
774 ˇ
775 brown fox"})
776 .await;
777 }
778
779 #[gpui::test]
780 async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
781 let mut cx = NeovimBackedTestContext::new(cx).await;
782
783 for count in 1..=5 {
784 cx.assert_binding_matches_all(
785 [&count.to_string(), "w"],
786 indoc! {"
787 ˇThe quˇickˇ browˇn
788 ˇ
789 ˇfox ˇjumpsˇ-ˇoˇver
790 ˇthe lazy dog
791 "},
792 )
793 .await;
794 }
795 }
796
797 #[gpui::test]
798 async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
799 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
800 cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
801 }
802
803 #[gpui::test]
804 async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
805 let mut cx = NeovimBackedTestContext::new(cx).await;
806 for count in 1..=3 {
807 let test_case = indoc! {"
808 ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
809 ˇ ˇbˇaaˇa ˇbˇbˇb
810 ˇ
811 ˇb
812 "};
813
814 cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
815 .await;
816
817 cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
818 .await;
819 }
820 }
821
822 #[gpui::test]
823 async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
824 let mut cx = NeovimBackedTestContext::new(cx).await;
825 let test_case = indoc! {"
826 ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
827 ˇ ˇbˇaaˇa ˇbˇbˇb
828 ˇ•••
829 ˇb
830 "
831 };
832
833 for count in 1..=3 {
834 cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
835 .await;
836
837 cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
838 .await;
839 }
840 }
841
842 #[gpui::test]
843 async fn test_percent(cx: &mut TestAppContext) {
844 let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
845 cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
846 cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
847 .await;
848 cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
849 }
850}