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