1mod change;
2mod delete;
3
4use std::borrow::Cow;
5
6use crate::{
7 motion::Motion,
8 state::{Mode, Operator},
9 Vim,
10};
11use change::init as change_init;
12use collections::HashSet;
13use editor::{Autoscroll, Bias, ClipboardSelection, DisplayPoint};
14use gpui::{actions, MutableAppContext, ViewContext};
15use language::{Point, SelectionGoal};
16use workspace::Workspace;
17
18use self::{change::change_over, delete::delete_over};
19
20actions!(
21 vim,
22 [
23 InsertAfter,
24 InsertFirstNonWhitespace,
25 InsertEndOfLine,
26 InsertLineAbove,
27 InsertLineBelow,
28 DeleteLeft,
29 DeleteRight,
30 ChangeToEndOfLine,
31 DeleteToEndOfLine,
32 Paste,
33 Yank,
34 ]
35);
36
37pub fn init(cx: &mut MutableAppContext) {
38 cx.add_action(insert_after);
39 cx.add_action(insert_first_non_whitespace);
40 cx.add_action(insert_end_of_line);
41 cx.add_action(insert_line_above);
42 cx.add_action(insert_line_below);
43 cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
44 Vim::update(cx, |vim, cx| {
45 delete_over(vim, Motion::Left, cx);
46 })
47 });
48 cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
49 Vim::update(cx, |vim, cx| {
50 delete_over(vim, Motion::Right, cx);
51 })
52 });
53 cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
54 Vim::update(cx, |vim, cx| {
55 change_over(vim, Motion::EndOfLine, cx);
56 })
57 });
58 cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
59 Vim::update(cx, |vim, cx| {
60 delete_over(vim, Motion::EndOfLine, cx);
61 })
62 });
63 cx.add_action(paste);
64
65 change_init(cx);
66}
67
68pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) {
69 Vim::update(cx, |vim, cx| {
70 match vim.state.operator_stack.pop() {
71 None => move_cursor(vim, motion, cx),
72 Some(Operator::Change) => change_over(vim, motion, cx),
73 Some(Operator::Delete) => delete_over(vim, motion, cx),
74 Some(Operator::Namespace(_)) => {
75 // Can't do anything for a namespace operator. Ignoring
76 }
77 }
78 vim.clear_operator(cx);
79 });
80}
81
82fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
83 vim.update_active_editor(cx, |editor, cx| {
84 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
85 s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal))
86 })
87 });
88}
89
90fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
91 Vim::update(cx, |vim, cx| {
92 vim.switch_mode(Mode::Insert, cx);
93 vim.update_active_editor(cx, |editor, cx| {
94 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
95 s.move_cursors_with(|map, cursor, goal| {
96 Motion::Right.move_point(map, cursor, goal)
97 });
98 });
99 });
100 });
101}
102
103fn insert_first_non_whitespace(
104 _: &mut Workspace,
105 _: &InsertFirstNonWhitespace,
106 cx: &mut ViewContext<Workspace>,
107) {
108 Vim::update(cx, |vim, cx| {
109 vim.switch_mode(Mode::Insert, cx);
110 vim.update_active_editor(cx, |editor, cx| {
111 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
112 s.move_cursors_with(|map, cursor, goal| {
113 Motion::FirstNonWhitespace.move_point(map, cursor, goal)
114 });
115 });
116 });
117 });
118}
119
120fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
121 Vim::update(cx, |vim, cx| {
122 vim.switch_mode(Mode::Insert, cx);
123 vim.update_active_editor(cx, |editor, cx| {
124 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
125 s.move_cursors_with(|map, cursor, goal| {
126 Motion::EndOfLine.move_point(map, cursor, goal)
127 });
128 });
129 });
130 });
131}
132
133fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
134 Vim::update(cx, |vim, cx| {
135 vim.switch_mode(Mode::Insert, cx);
136 vim.update_active_editor(cx, |editor, cx| {
137 editor.transact(cx, |editor, cx| {
138 let (map, old_selections) = editor.selections.all_display(cx);
139 let selection_start_rows: HashSet<u32> = old_selections
140 .into_iter()
141 .map(|selection| selection.start.row())
142 .collect();
143 let edits = selection_start_rows.into_iter().map(|row| {
144 let (indent, _) = map.line_indent(row);
145 let start_of_line = map
146 .clip_point(DisplayPoint::new(row, 0), Bias::Left)
147 .to_point(&map);
148 let mut new_text = " ".repeat(indent as usize);
149 new_text.push('\n');
150 (start_of_line..start_of_line, new_text)
151 });
152 editor.edit_with_autoindent(edits, cx);
153 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
154 s.move_cursors_with(|map, mut cursor, _| {
155 *cursor.row_mut() -= 1;
156 *cursor.column_mut() = map.line_len(cursor.row());
157 (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
158 });
159 });
160 });
161 });
162 });
163}
164
165fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
166 Vim::update(cx, |vim, cx| {
167 vim.switch_mode(Mode::Insert, cx);
168 vim.update_active_editor(cx, |editor, cx| {
169 editor.transact(cx, |editor, cx| {
170 let (map, old_selections) = editor.selections.all_display(cx);
171 let selection_end_rows: HashSet<u32> = old_selections
172 .into_iter()
173 .map(|selection| selection.end.row())
174 .collect();
175 let edits = selection_end_rows.into_iter().map(|row| {
176 let (indent, _) = map.line_indent(row);
177 let end_of_line = map
178 .clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left)
179 .to_point(&map);
180 let mut new_text = "\n".to_string();
181 new_text.push_str(&" ".repeat(indent as usize));
182 (end_of_line..end_of_line, new_text)
183 });
184 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
185 s.move_cursors_with(|map, cursor, goal| {
186 Motion::EndOfLine.move_point(map, cursor, goal)
187 });
188 });
189 editor.edit_with_autoindent(edits, cx);
190 });
191 });
192 });
193}
194
195fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
196 Vim::update(cx, |vim, cx| {
197 vim.update_active_editor(cx, |editor, cx| {
198 editor.transact(cx, |editor, cx| {
199 if let Some(item) = cx.as_mut().read_from_clipboard() {
200 let mut clipboard_text = Cow::Borrowed(item.text());
201 if let Some(mut clipboard_selections) =
202 item.metadata::<Vec<ClipboardSelection>>()
203 {
204 let (display_map, selections) = editor.selections.all_display(cx);
205 let all_selections_were_entire_line =
206 clipboard_selections.iter().all(|s| s.is_entire_line);
207 if clipboard_selections.len() != selections.len() {
208 let mut newline_separated_text = String::new();
209 let mut clipboard_selections =
210 clipboard_selections.drain(..).peekable();
211 let mut ix = 0;
212 while let Some(clipboard_selection) = clipboard_selections.next() {
213 newline_separated_text
214 .push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
215 ix += clipboard_selection.len;
216 if clipboard_selections.peek().is_some() {
217 newline_separated_text.push('\n');
218 }
219 }
220 clipboard_text = Cow::Owned(newline_separated_text);
221 }
222
223 let mut new_selections = Vec::new();
224 editor.buffer().update(cx, |buffer, cx| {
225 let snapshot = buffer.snapshot(cx);
226 let mut start_offset = 0;
227 let mut edits = Vec::new();
228 for (ix, selection) in selections.iter().enumerate() {
229 let to_insert;
230 let linewise;
231 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
232 let end_offset = start_offset + clipboard_selection.len;
233 to_insert = &clipboard_text[start_offset..end_offset];
234 linewise = clipboard_selection.is_entire_line;
235 start_offset = end_offset;
236 } else {
237 to_insert = clipboard_text.as_str();
238 linewise = all_selections_were_entire_line;
239 }
240
241 // If the clipboard text was copied linewise, and the current selection
242 // is empty, then paste the text after this line and move the selection
243 // to the start of the pasted text
244 let range = if selection.is_empty() && linewise {
245 let (point, _) = display_map
246 .next_line_boundary(selection.start.to_point(&display_map));
247
248 if !to_insert.starts_with('\n') {
249 // Add newline before pasted text so that it shows up
250 edits.push((point..point, "\n"));
251 }
252 // Drop selection at the start of the next line
253 let selection_point = Point::new(point.row + 1, 0);
254 new_selections.push(selection.map(|_| selection_point.clone()));
255 point..point
256 } else {
257 let range = selection.map(|p| p.to_point(&display_map)).range();
258 new_selections.push(selection.map(|_| range.start.clone()));
259 range
260 };
261
262 if linewise && to_insert.ends_with('\n') {
263 edits.push((
264 range,
265 &to_insert[0..to_insert.len().saturating_sub(1)],
266 ))
267 } else {
268 edits.push((range, to_insert));
269 }
270 }
271 drop(snapshot);
272 buffer.edit_with_autoindent(edits, cx);
273 });
274
275 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
276 s.select(new_selections)
277 });
278 } else {
279 editor.insert(&clipboard_text, cx);
280 }
281 }
282 });
283 });
284 });
285}
286
287#[cfg(test)]
288mod test {
289 use indoc::indoc;
290 use language::Selection;
291 use util::test::marked_text;
292
293 use crate::{
294 state::{
295 Mode::{self, *},
296 Namespace, Operator,
297 },
298 vim_test_context::VimTestContext,
299 };
300
301 #[gpui::test]
302 async fn test_h(cx: &mut gpui::TestAppContext) {
303 let cx = VimTestContext::new(cx, true).await;
304 let mut cx = cx.binding(["h"]);
305 cx.assert("The q|uick", "The |quick");
306 cx.assert("|The quick", "|The quick");
307 cx.assert(
308 indoc! {"
309 The quick
310 |brown"},
311 indoc! {"
312 The quick
313 |brown"},
314 );
315 }
316
317 #[gpui::test]
318 async fn test_backspace(cx: &mut gpui::TestAppContext) {
319 let cx = VimTestContext::new(cx, true).await;
320 let mut cx = cx.binding(["backspace"]);
321 cx.assert("The q|uick", "The |quick");
322 cx.assert("|The quick", "|The quick");
323 cx.assert(
324 indoc! {"
325 The quick
326 |brown"},
327 indoc! {"
328 The quick
329 |brown"},
330 );
331 }
332
333 #[gpui::test]
334 async fn test_j(cx: &mut gpui::TestAppContext) {
335 let cx = VimTestContext::new(cx, true).await;
336 let mut cx = cx.binding(["j"]);
337 cx.assert(
338 indoc! {"
339 The |quick
340 brown fox"},
341 indoc! {"
342 The quick
343 brow|n fox"},
344 );
345 cx.assert(
346 indoc! {"
347 The quick
348 brow|n fox"},
349 indoc! {"
350 The quick
351 brow|n fox"},
352 );
353 cx.assert(
354 indoc! {"
355 The quic|k
356 brown"},
357 indoc! {"
358 The quick
359 brow|n"},
360 );
361 cx.assert(
362 indoc! {"
363 The quick
364 |brown"},
365 indoc! {"
366 The quick
367 |brown"},
368 );
369 }
370
371 #[gpui::test]
372 async fn test_k(cx: &mut gpui::TestAppContext) {
373 let cx = VimTestContext::new(cx, true).await;
374 let mut cx = cx.binding(["k"]);
375 cx.assert(
376 indoc! {"
377 The |quick
378 brown fox"},
379 indoc! {"
380 The |quick
381 brown fox"},
382 );
383 cx.assert(
384 indoc! {"
385 The quick
386 brow|n fox"},
387 indoc! {"
388 The |quick
389 brown fox"},
390 );
391 cx.assert(
392 indoc! {"
393 The
394 quic|k"},
395 indoc! {"
396 Th|e
397 quick"},
398 );
399 }
400
401 #[gpui::test]
402 async fn test_l(cx: &mut gpui::TestAppContext) {
403 let cx = VimTestContext::new(cx, true).await;
404 let mut cx = cx.binding(["l"]);
405 cx.assert("The q|uick", "The qu|ick");
406 cx.assert("The quic|k", "The quic|k");
407 cx.assert(
408 indoc! {"
409 The quic|k
410 brown"},
411 indoc! {"
412 The quic|k
413 brown"},
414 );
415 }
416
417 #[gpui::test]
418 async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
419 let cx = VimTestContext::new(cx, true).await;
420 let mut cx = cx.binding(["shift-$"]);
421 cx.assert("T|est test", "Test tes|t");
422 cx.assert("Test tes|t", "Test tes|t");
423 cx.assert(
424 indoc! {"
425 The |quick
426 brown"},
427 indoc! {"
428 The quic|k
429 brown"},
430 );
431 cx.assert(
432 indoc! {"
433 The quic|k
434 brown"},
435 indoc! {"
436 The quic|k
437 brown"},
438 );
439
440 let mut cx = cx.binding(["0"]);
441 cx.assert("Test |test", "|Test test");
442 cx.assert("|Test test", "|Test test");
443 cx.assert(
444 indoc! {"
445 The |quick
446 brown"},
447 indoc! {"
448 |The quick
449 brown"},
450 );
451 cx.assert(
452 indoc! {"
453 |The quick
454 brown"},
455 indoc! {"
456 |The quick
457 brown"},
458 );
459 }
460
461 #[gpui::test]
462 async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
463 let cx = VimTestContext::new(cx, true).await;
464 let mut cx = cx.binding(["shift-G"]);
465
466 cx.assert(
467 indoc! {"
468 The |quick
469
470 brown fox jumps
471 over the lazy dog"},
472 indoc! {"
473 The quick
474
475 brown fox jumps
476 over| the lazy dog"},
477 );
478 cx.assert(
479 indoc! {"
480 The quick
481
482 brown fox jumps
483 over| the lazy dog"},
484 indoc! {"
485 The quick
486
487 brown fox jumps
488 over| the lazy dog"},
489 );
490 cx.assert(
491 indoc! {"
492 The qui|ck
493
494 brown"},
495 indoc! {"
496 The quick
497
498 brow|n"},
499 );
500 cx.assert(
501 indoc! {"
502 The qui|ck
503
504 "},
505 indoc! {"
506 The quick
507
508 |"},
509 );
510 }
511
512 #[gpui::test]
513 async fn test_w(cx: &mut gpui::TestAppContext) {
514 let mut cx = VimTestContext::new(cx, true).await;
515 let (_, cursor_offsets) = marked_text(indoc! {"
516 The |quick|-|brown
517 |
518 |
519 |fox_jumps |over
520 |th||e"});
521 cx.set_state(
522 indoc! {"
523 |The quick-brown
524
525
526 fox_jumps over
527 the"},
528 Mode::Normal,
529 );
530
531 for cursor_offset in cursor_offsets {
532 cx.simulate_keystroke("w");
533 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
534 }
535
536 // Reset and test ignoring punctuation
537 let (_, cursor_offsets) = marked_text(indoc! {"
538 The |quick-brown
539 |
540 |
541 |fox_jumps |over
542 |th||e"});
543 cx.set_state(
544 indoc! {"
545 |The quick-brown
546
547
548 fox_jumps over
549 the"},
550 Mode::Normal,
551 );
552
553 for cursor_offset in cursor_offsets {
554 cx.simulate_keystroke("shift-W");
555 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
556 }
557 }
558
559 #[gpui::test]
560 async fn test_e(cx: &mut gpui::TestAppContext) {
561 let mut cx = VimTestContext::new(cx, true).await;
562 let (_, cursor_offsets) = marked_text(indoc! {"
563 Th|e quic|k|-brow|n
564
565
566 fox_jump|s ove|r
567 th|e"});
568 cx.set_state(
569 indoc! {"
570 |The quick-brown
571
572
573 fox_jumps over
574 the"},
575 Mode::Normal,
576 );
577
578 for cursor_offset in cursor_offsets {
579 cx.simulate_keystroke("e");
580 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
581 }
582
583 // Reset and test ignoring punctuation
584 let (_, cursor_offsets) = marked_text(indoc! {"
585 Th|e quick-brow|n
586
587
588 fox_jump|s ove|r
589 th||e"});
590 cx.set_state(
591 indoc! {"
592 |The quick-brown
593
594
595 fox_jumps over
596 the"},
597 Mode::Normal,
598 );
599 for cursor_offset in cursor_offsets {
600 cx.simulate_keystroke("shift-E");
601 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
602 }
603 }
604
605 #[gpui::test]
606 async fn test_b(cx: &mut gpui::TestAppContext) {
607 let mut cx = VimTestContext::new(cx, true).await;
608 let (_, cursor_offsets) = marked_text(indoc! {"
609 ||The |quick|-|brown
610 |
611 |
612 |fox_jumps |over
613 |the"});
614 cx.set_state(
615 indoc! {"
616 The quick-brown
617
618
619 fox_jumps over
620 th|e"},
621 Mode::Normal,
622 );
623
624 for cursor_offset in cursor_offsets.into_iter().rev() {
625 cx.simulate_keystroke("b");
626 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
627 }
628
629 // Reset and test ignoring punctuation
630 let (_, cursor_offsets) = marked_text(indoc! {"
631 ||The |quick-brown
632 |
633 |
634 |fox_jumps |over
635 |the"});
636 cx.set_state(
637 indoc! {"
638 The quick-brown
639
640
641 fox_jumps over
642 th|e"},
643 Mode::Normal,
644 );
645 for cursor_offset in cursor_offsets.into_iter().rev() {
646 cx.simulate_keystroke("shift-B");
647 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
648 }
649 }
650
651 #[gpui::test]
652 async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) {
653 let mut cx = VimTestContext::new(cx, true).await;
654
655 // Can abort with escape to get back to normal mode
656 cx.simulate_keystroke("g");
657 assert_eq!(cx.mode(), Normal);
658 assert_eq!(
659 cx.active_operator(),
660 Some(Operator::Namespace(Namespace::G))
661 );
662 cx.simulate_keystroke("escape");
663 assert_eq!(cx.mode(), Normal);
664 assert_eq!(cx.active_operator(), None);
665 }
666
667 #[gpui::test]
668 async fn test_gg(cx: &mut gpui::TestAppContext) {
669 let cx = VimTestContext::new(cx, true).await;
670 let mut cx = cx.binding(["g", "g"]);
671 cx.assert(
672 indoc! {"
673 The quick
674
675 brown fox jumps
676 over |the lazy dog"},
677 indoc! {"
678 The q|uick
679
680 brown fox jumps
681 over the lazy dog"},
682 );
683 cx.assert(
684 indoc! {"
685 The q|uick
686
687 brown fox jumps
688 over the lazy dog"},
689 indoc! {"
690 The q|uick
691
692 brown fox jumps
693 over the lazy dog"},
694 );
695 cx.assert(
696 indoc! {"
697 The quick
698
699 brown fox jumps
700 over the la|zy dog"},
701 indoc! {"
702 The quic|k
703
704 brown fox jumps
705 over the lazy dog"},
706 );
707 cx.assert(
708 indoc! {"
709
710
711 brown fox jumps
712 over the la|zy dog"},
713 indoc! {"
714 |
715
716 brown fox jumps
717 over the lazy dog"},
718 );
719 }
720
721 #[gpui::test]
722 async fn test_a(cx: &mut gpui::TestAppContext) {
723 let cx = VimTestContext::new(cx, true).await;
724 let mut cx = cx.binding(["a"]).mode_after(Mode::Insert);
725
726 cx.assert("The q|uick", "The qu|ick");
727 cx.assert("The quic|k", "The quick|");
728 }
729
730 #[gpui::test]
731 async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
732 let cx = VimTestContext::new(cx, true).await;
733 let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert);
734 cx.assert("The q|uick", "The quick|");
735 cx.assert("The q|uick ", "The quick |");
736 cx.assert("|", "|");
737 cx.assert(
738 indoc! {"
739 The q|uick
740 brown fox"},
741 indoc! {"
742 The quick|
743 brown fox"},
744 );
745 cx.assert(
746 indoc! {"
747 |
748 The quick"},
749 indoc! {"
750 |
751 The quick"},
752 );
753 }
754
755 #[gpui::test]
756 async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
757 let cx = VimTestContext::new(cx, true).await;
758 let mut cx = cx.binding(["shift-^"]);
759 cx.assert("The q|uick", "|The quick");
760 cx.assert(" The q|uick", " |The quick");
761 cx.assert("|", "|");
762 cx.assert(
763 indoc! {"
764 The q|uick
765 brown fox"},
766 indoc! {"
767 |The quick
768 brown fox"},
769 );
770 cx.assert(
771 indoc! {"
772 |
773 The quick"},
774 indoc! {"
775 |
776 The quick"},
777 );
778 cx.assert(
779 indoc! {"
780 |
781 The quick"},
782 indoc! {"
783 |
784 The quick"},
785 );
786 }
787
788 #[gpui::test]
789 async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
790 let cx = VimTestContext::new(cx, true).await;
791 let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
792 cx.assert("The q|uick", "|The quick");
793 cx.assert(" The q|uick", " |The quick");
794 cx.assert("|", "|");
795 cx.assert(
796 indoc! {"
797 The q|uick
798 brown fox"},
799 indoc! {"
800 |The quick
801 brown fox"},
802 );
803 cx.assert(
804 indoc! {"
805 |
806 The quick"},
807 indoc! {"
808 |
809 The quick"},
810 );
811 }
812
813 #[gpui::test]
814 async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
815 let cx = VimTestContext::new(cx, true).await;
816 let mut cx = cx.binding(["shift-D"]);
817 cx.assert(
818 indoc! {"
819 The q|uick
820 brown fox"},
821 indoc! {"
822 The |q
823 brown fox"},
824 );
825 cx.assert(
826 indoc! {"
827 The quick
828 |
829 brown fox"},
830 indoc! {"
831 The quick
832 |
833 brown fox"},
834 );
835 }
836
837 #[gpui::test]
838 async fn test_x(cx: &mut gpui::TestAppContext) {
839 let cx = VimTestContext::new(cx, true).await;
840 let mut cx = cx.binding(["x"]);
841 cx.assert("|Test", "|est");
842 cx.assert("Te|st", "Te|t");
843 cx.assert("Tes|t", "Te|s");
844 cx.assert(
845 indoc! {"
846 Tes|t
847 test"},
848 indoc! {"
849 Te|s
850 test"},
851 );
852 }
853
854 #[gpui::test]
855 async fn test_delete_left(cx: &mut gpui::TestAppContext) {
856 let cx = VimTestContext::new(cx, true).await;
857 let mut cx = cx.binding(["shift-X"]);
858 cx.assert("Te|st", "T|st");
859 cx.assert("T|est", "|est");
860 cx.assert("|Test", "|Test");
861 cx.assert(
862 indoc! {"
863 Test
864 |test"},
865 indoc! {"
866 Test
867 |test"},
868 );
869 }
870
871 #[gpui::test]
872 async fn test_o(cx: &mut gpui::TestAppContext) {
873 let cx = VimTestContext::new(cx, true).await;
874 let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
875
876 cx.assert(
877 "|",
878 indoc! {"
879
880 |"},
881 );
882 cx.assert(
883 "The |quick",
884 indoc! {"
885 The quick
886 |"},
887 );
888 cx.assert(
889 indoc! {"
890 The quick
891 brown |fox
892 jumps over"},
893 indoc! {"
894 The quick
895 brown fox
896 |
897 jumps over"},
898 );
899 cx.assert(
900 indoc! {"
901 The quick
902 brown fox
903 jumps |over"},
904 indoc! {"
905 The quick
906 brown fox
907 jumps over
908 |"},
909 );
910 cx.assert(
911 indoc! {"
912 The q|uick
913 brown fox
914 jumps over"},
915 indoc! {"
916 The quick
917 |
918 brown fox
919 jumps over"},
920 );
921 cx.assert(
922 indoc! {"
923 The quick
924 |
925 brown fox"},
926 indoc! {"
927 The quick
928
929 |
930 brown fox"},
931 );
932 cx.assert(
933 indoc! {"
934 fn test()
935 println!(|);"},
936 indoc! {"
937 fn test()
938 println!();
939 |"},
940 );
941 cx.assert(
942 indoc! {"
943 fn test(|)
944 println!();"},
945 indoc! {"
946 fn test()
947 |
948 println!();"},
949 );
950 }
951
952 #[gpui::test]
953 async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
954 let cx = VimTestContext::new(cx, true).await;
955 let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
956
957 cx.assert(
958 "|",
959 indoc! {"
960 |
961 "},
962 );
963 cx.assert(
964 "The |quick",
965 indoc! {"
966 |
967 The quick"},
968 );
969 cx.assert(
970 indoc! {"
971 The quick
972 brown |fox
973 jumps over"},
974 indoc! {"
975 The quick
976 |
977 brown fox
978 jumps over"},
979 );
980 cx.assert(
981 indoc! {"
982 The quick
983 brown fox
984 jumps |over"},
985 indoc! {"
986 The quick
987 brown fox
988 |
989 jumps over"},
990 );
991 cx.assert(
992 indoc! {"
993 The q|uick
994 brown fox
995 jumps over"},
996 indoc! {"
997 |
998 The quick
999 brown fox
1000 jumps over"},
1001 );
1002 cx.assert(
1003 indoc! {"
1004 The quick
1005 |
1006 brown fox"},
1007 indoc! {"
1008 The quick
1009 |
1010
1011 brown fox"},
1012 );
1013 cx.assert(
1014 indoc! {"
1015 fn test()
1016 println!(|);"},
1017 indoc! {"
1018 fn test()
1019 |
1020 println!();"},
1021 );
1022 cx.assert(
1023 indoc! {"
1024 fn test(|)
1025 println!();"},
1026 indoc! {"
1027 |
1028 fn test()
1029 println!();"},
1030 );
1031 }
1032
1033 #[gpui::test]
1034 async fn test_dd(cx: &mut gpui::TestAppContext) {
1035 let cx = VimTestContext::new(cx, true).await;
1036 let mut cx = cx.binding(["d", "d"]);
1037
1038 cx.assert("|", "|");
1039 cx.assert("The |quick", "|");
1040 cx.assert(
1041 indoc! {"
1042 The quick
1043 brown |fox
1044 jumps over"},
1045 indoc! {"
1046 The quick
1047 jumps |over"},
1048 );
1049 cx.assert(
1050 indoc! {"
1051 The quick
1052 brown fox
1053 jumps |over"},
1054 indoc! {"
1055 The quick
1056 brown |fox"},
1057 );
1058 cx.assert(
1059 indoc! {"
1060 The q|uick
1061 brown fox
1062 jumps over"},
1063 indoc! {"
1064 brown| fox
1065 jumps over"},
1066 );
1067 cx.assert(
1068 indoc! {"
1069 The quick
1070 |
1071 brown fox"},
1072 indoc! {"
1073 The quick
1074 |brown fox"},
1075 );
1076 }
1077
1078 #[gpui::test]
1079 async fn test_cc(cx: &mut gpui::TestAppContext) {
1080 let cx = VimTestContext::new(cx, true).await;
1081 let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1082
1083 cx.assert("|", "|");
1084 cx.assert("The |quick", "|");
1085 cx.assert(
1086 indoc! {"
1087 The quick
1088 brown |fox
1089 jumps over"},
1090 indoc! {"
1091 The quick
1092 |
1093 jumps over"},
1094 );
1095 cx.assert(
1096 indoc! {"
1097 The quick
1098 brown fox
1099 jumps |over"},
1100 indoc! {"
1101 The quick
1102 brown fox
1103 |"},
1104 );
1105 cx.assert(
1106 indoc! {"
1107 The q|uick
1108 brown fox
1109 jumps over"},
1110 indoc! {"
1111 |
1112 brown fox
1113 jumps over"},
1114 );
1115 cx.assert(
1116 indoc! {"
1117 The quick
1118 |
1119 brown fox"},
1120 indoc! {"
1121 The quick
1122 |
1123 brown fox"},
1124 );
1125 }
1126
1127 #[gpui::test]
1128 async fn test_p(cx: &mut gpui::TestAppContext) {
1129 let mut cx = VimTestContext::new(cx, true).await;
1130 cx.set_state(
1131 indoc! {"
1132 The quick brown
1133 fox ju|mps over
1134 the lazy dog"},
1135 Mode::Normal,
1136 );
1137
1138 cx.simulate_keystrokes(["d", "d"]);
1139 cx.assert_editor_state(indoc! {"
1140 The quick brown
1141 the la|zy dog"});
1142
1143 cx.simulate_keystroke("p");
1144 cx.assert_editor_state(indoc! {"
1145 The quick brown
1146 the lazy dog
1147 |fox jumps over"});
1148 }
1149}