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, false, 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, false, 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, false, 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, false, 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, false, 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 editor.set_clip_at_line_ends(false, cx);
202 if let Some(item) = cx.as_mut().read_from_clipboard() {
203 let mut clipboard_text = Cow::Borrowed(item.text());
204 if let Some(mut clipboard_selections) =
205 item.metadata::<Vec<ClipboardSelection>>()
206 {
207 let (display_map, selections) = editor.selections.all_display(cx);
208 let all_selections_were_entire_line =
209 clipboard_selections.iter().all(|s| s.is_entire_line);
210 if clipboard_selections.len() != selections.len() {
211 let mut newline_separated_text = String::new();
212 let mut clipboard_selections =
213 clipboard_selections.drain(..).peekable();
214 let mut ix = 0;
215 while let Some(clipboard_selection) = clipboard_selections.next() {
216 newline_separated_text
217 .push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
218 ix += clipboard_selection.len;
219 if clipboard_selections.peek().is_some() {
220 newline_separated_text.push('\n');
221 }
222 }
223 clipboard_text = Cow::Owned(newline_separated_text);
224 }
225
226 let mut new_selections = Vec::new();
227 editor.buffer().update(cx, |buffer, cx| {
228 let snapshot = buffer.snapshot(cx);
229 let mut start_offset = 0;
230 let mut edits = Vec::new();
231 for (ix, selection) in selections.iter().enumerate() {
232 let to_insert;
233 let linewise;
234 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
235 let end_offset = start_offset + clipboard_selection.len;
236 to_insert = &clipboard_text[start_offset..end_offset];
237 linewise = clipboard_selection.is_entire_line;
238 start_offset = end_offset;
239 } else {
240 to_insert = clipboard_text.as_str();
241 linewise = all_selections_were_entire_line;
242 }
243
244 // If the clipboard text was copied linewise, and the current selection
245 // is empty, then paste the text after this line and move the selection
246 // to the start of the pasted text
247 let insert_at = if linewise {
248 let (point, _) = display_map
249 .next_line_boundary(selection.start.to_point(&display_map));
250
251 if !to_insert.starts_with('\n') {
252 // Add newline before pasted text so that it shows up
253 edits.push((point..point, "\n"));
254 }
255 // Drop selection at the start of the next line
256 let selection_point = Point::new(point.row + 1, 0);
257 new_selections.push(selection.map(|_| selection_point.clone()));
258 point
259 } else {
260 let mut point = selection.end;
261 // Paste the text after the current selection
262 *point.column_mut() = point.column() + 1;
263 let point = display_map
264 .clip_point(point, Bias::Right)
265 .to_point(&display_map);
266
267 new_selections.push(selection.map(|_| point));
268 point
269 };
270
271 if linewise && to_insert.ends_with('\n') {
272 edits.push((
273 insert_at..insert_at,
274 &to_insert[0..to_insert.len().saturating_sub(1)],
275 ))
276 } else {
277 edits.push((insert_at..insert_at, to_insert));
278 }
279 }
280 drop(snapshot);
281 buffer.edit_with_autoindent(edits, cx);
282 });
283
284 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
285 s.select(new_selections)
286 });
287 } else {
288 editor.insert(&clipboard_text, cx);
289 }
290 }
291 editor.set_clip_at_line_ends(true, cx);
292 });
293 });
294 });
295}
296
297#[cfg(test)]
298mod test {
299 use indoc::indoc;
300 use language::Selection;
301 use util::test::marked_text;
302
303 use crate::{
304 state::{
305 Mode::{self, *},
306 Namespace, Operator,
307 },
308 vim_test_context::VimTestContext,
309 };
310
311 #[gpui::test]
312 async fn test_h(cx: &mut gpui::TestAppContext) {
313 let cx = VimTestContext::new(cx, true).await;
314 let mut cx = cx.binding(["h"]);
315 cx.assert("The q|uick", "The |quick");
316 cx.assert("|The quick", "|The quick");
317 cx.assert(
318 indoc! {"
319 The quick
320 |brown"},
321 indoc! {"
322 The quick
323 |brown"},
324 );
325 }
326
327 #[gpui::test]
328 async fn test_backspace(cx: &mut gpui::TestAppContext) {
329 let cx = VimTestContext::new(cx, true).await;
330 let mut cx = cx.binding(["backspace"]);
331 cx.assert("The q|uick", "The |quick");
332 cx.assert("|The quick", "|The quick");
333 cx.assert(
334 indoc! {"
335 The quick
336 |brown"},
337 indoc! {"
338 The quick
339 |brown"},
340 );
341 }
342
343 #[gpui::test]
344 async fn test_j(cx: &mut gpui::TestAppContext) {
345 let cx = VimTestContext::new(cx, true).await;
346 let mut cx = cx.binding(["j"]);
347 cx.assert(
348 indoc! {"
349 The |quick
350 brown fox"},
351 indoc! {"
352 The quick
353 brow|n fox"},
354 );
355 cx.assert(
356 indoc! {"
357 The quick
358 brow|n fox"},
359 indoc! {"
360 The quick
361 brow|n fox"},
362 );
363 cx.assert(
364 indoc! {"
365 The quic|k
366 brown"},
367 indoc! {"
368 The quick
369 brow|n"},
370 );
371 cx.assert(
372 indoc! {"
373 The quick
374 |brown"},
375 indoc! {"
376 The quick
377 |brown"},
378 );
379 }
380
381 #[gpui::test]
382 async fn test_k(cx: &mut gpui::TestAppContext) {
383 let cx = VimTestContext::new(cx, true).await;
384 let mut cx = cx.binding(["k"]);
385 cx.assert(
386 indoc! {"
387 The |quick
388 brown fox"},
389 indoc! {"
390 The |quick
391 brown fox"},
392 );
393 cx.assert(
394 indoc! {"
395 The quick
396 brow|n fox"},
397 indoc! {"
398 The |quick
399 brown fox"},
400 );
401 cx.assert(
402 indoc! {"
403 The
404 quic|k"},
405 indoc! {"
406 Th|e
407 quick"},
408 );
409 }
410
411 #[gpui::test]
412 async fn test_l(cx: &mut gpui::TestAppContext) {
413 let cx = VimTestContext::new(cx, true).await;
414 let mut cx = cx.binding(["l"]);
415 cx.assert("The q|uick", "The qu|ick");
416 cx.assert("The quic|k", "The quic|k");
417 cx.assert(
418 indoc! {"
419 The quic|k
420 brown"},
421 indoc! {"
422 The quic|k
423 brown"},
424 );
425 }
426
427 #[gpui::test]
428 async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
429 let cx = VimTestContext::new(cx, true).await;
430 let mut cx = cx.binding(["shift-$"]);
431 cx.assert("T|est test", "Test tes|t");
432 cx.assert("Test tes|t", "Test tes|t");
433 cx.assert(
434 indoc! {"
435 The |quick
436 brown"},
437 indoc! {"
438 The quic|k
439 brown"},
440 );
441 cx.assert(
442 indoc! {"
443 The quic|k
444 brown"},
445 indoc! {"
446 The quic|k
447 brown"},
448 );
449
450 let mut cx = cx.binding(["0"]);
451 cx.assert("Test |test", "|Test test");
452 cx.assert("|Test test", "|Test test");
453 cx.assert(
454 indoc! {"
455 The |quick
456 brown"},
457 indoc! {"
458 |The quick
459 brown"},
460 );
461 cx.assert(
462 indoc! {"
463 |The quick
464 brown"},
465 indoc! {"
466 |The quick
467 brown"},
468 );
469 }
470
471 #[gpui::test]
472 async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
473 let cx = VimTestContext::new(cx, true).await;
474 let mut cx = cx.binding(["shift-G"]);
475
476 cx.assert(
477 indoc! {"
478 The |quick
479
480 brown fox jumps
481 over the lazy dog"},
482 indoc! {"
483 The quick
484
485 brown fox jumps
486 over| the lazy dog"},
487 );
488 cx.assert(
489 indoc! {"
490 The quick
491
492 brown fox jumps
493 over| the lazy dog"},
494 indoc! {"
495 The quick
496
497 brown fox jumps
498 over| the lazy dog"},
499 );
500 cx.assert(
501 indoc! {"
502 The qui|ck
503
504 brown"},
505 indoc! {"
506 The quick
507
508 brow|n"},
509 );
510 cx.assert(
511 indoc! {"
512 The qui|ck
513
514 "},
515 indoc! {"
516 The quick
517
518 |"},
519 );
520 }
521
522 #[gpui::test]
523 async fn test_w(cx: &mut gpui::TestAppContext) {
524 let mut cx = VimTestContext::new(cx, true).await;
525 let (_, cursor_offsets) = marked_text(indoc! {"
526 The |quick|-|brown
527 |
528 |
529 |fox_jumps |over
530 |th||e"});
531 cx.set_state(
532 indoc! {"
533 |The quick-brown
534
535
536 fox_jumps over
537 the"},
538 Mode::Normal,
539 );
540
541 for cursor_offset in cursor_offsets {
542 cx.simulate_keystroke("w");
543 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
544 }
545
546 // Reset and test ignoring punctuation
547 let (_, cursor_offsets) = marked_text(indoc! {"
548 The |quick-brown
549 |
550 |
551 |fox_jumps |over
552 |th||e"});
553 cx.set_state(
554 indoc! {"
555 |The quick-brown
556
557
558 fox_jumps over
559 the"},
560 Mode::Normal,
561 );
562
563 for cursor_offset in cursor_offsets {
564 cx.simulate_keystroke("shift-W");
565 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
566 }
567 }
568
569 #[gpui::test]
570 async fn test_e(cx: &mut gpui::TestAppContext) {
571 let mut cx = VimTestContext::new(cx, true).await;
572 let (_, cursor_offsets) = marked_text(indoc! {"
573 Th|e quic|k|-brow|n
574
575
576 fox_jump|s ove|r
577 th|e"});
578 cx.set_state(
579 indoc! {"
580 |The quick-brown
581
582
583 fox_jumps over
584 the"},
585 Mode::Normal,
586 );
587
588 for cursor_offset in cursor_offsets {
589 cx.simulate_keystroke("e");
590 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
591 }
592
593 // Reset and test ignoring punctuation
594 let (_, cursor_offsets) = marked_text(indoc! {"
595 Th|e quick-brow|n
596
597
598 fox_jump|s ove|r
599 th||e"});
600 cx.set_state(
601 indoc! {"
602 |The quick-brown
603
604
605 fox_jumps over
606 the"},
607 Mode::Normal,
608 );
609 for cursor_offset in cursor_offsets {
610 cx.simulate_keystroke("shift-E");
611 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
612 }
613 }
614
615 #[gpui::test]
616 async fn test_b(cx: &mut gpui::TestAppContext) {
617 let mut cx = VimTestContext::new(cx, true).await;
618 let (_, cursor_offsets) = marked_text(indoc! {"
619 ||The |quick|-|brown
620 |
621 |
622 |fox_jumps |over
623 |the"});
624 cx.set_state(
625 indoc! {"
626 The quick-brown
627
628
629 fox_jumps over
630 th|e"},
631 Mode::Normal,
632 );
633
634 for cursor_offset in cursor_offsets.into_iter().rev() {
635 cx.simulate_keystroke("b");
636 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
637 }
638
639 // Reset and test ignoring punctuation
640 let (_, cursor_offsets) = marked_text(indoc! {"
641 ||The |quick-brown
642 |
643 |
644 |fox_jumps |over
645 |the"});
646 cx.set_state(
647 indoc! {"
648 The quick-brown
649
650
651 fox_jumps over
652 th|e"},
653 Mode::Normal,
654 );
655 for cursor_offset in cursor_offsets.into_iter().rev() {
656 cx.simulate_keystroke("shift-B");
657 cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
658 }
659 }
660
661 #[gpui::test]
662 async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) {
663 let mut cx = VimTestContext::new(cx, true).await;
664
665 // Can abort with escape to get back to normal mode
666 cx.simulate_keystroke("g");
667 assert_eq!(cx.mode(), Normal);
668 assert_eq!(
669 cx.active_operator(),
670 Some(Operator::Namespace(Namespace::G))
671 );
672 cx.simulate_keystroke("escape");
673 assert_eq!(cx.mode(), Normal);
674 assert_eq!(cx.active_operator(), None);
675 }
676
677 #[gpui::test]
678 async fn test_gg(cx: &mut gpui::TestAppContext) {
679 let cx = VimTestContext::new(cx, true).await;
680 let mut cx = cx.binding(["g", "g"]);
681 cx.assert(
682 indoc! {"
683 The quick
684
685 brown fox jumps
686 over |the lazy dog"},
687 indoc! {"
688 The q|uick
689
690 brown fox jumps
691 over the lazy dog"},
692 );
693 cx.assert(
694 indoc! {"
695 The q|uick
696
697 brown fox jumps
698 over the lazy dog"},
699 indoc! {"
700 The q|uick
701
702 brown fox jumps
703 over the lazy dog"},
704 );
705 cx.assert(
706 indoc! {"
707 The quick
708
709 brown fox jumps
710 over the la|zy dog"},
711 indoc! {"
712 The quic|k
713
714 brown fox jumps
715 over the lazy dog"},
716 );
717 cx.assert(
718 indoc! {"
719
720
721 brown fox jumps
722 over the la|zy dog"},
723 indoc! {"
724 |
725
726 brown fox jumps
727 over the lazy dog"},
728 );
729 }
730
731 #[gpui::test]
732 async fn test_a(cx: &mut gpui::TestAppContext) {
733 let cx = VimTestContext::new(cx, true).await;
734 let mut cx = cx.binding(["a"]).mode_after(Mode::Insert);
735
736 cx.assert("The q|uick", "The qu|ick");
737 cx.assert("The quic|k", "The quick|");
738 }
739
740 #[gpui::test]
741 async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
742 let cx = VimTestContext::new(cx, true).await;
743 let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert);
744 cx.assert("The q|uick", "The quick|");
745 cx.assert("The q|uick ", "The quick |");
746 cx.assert("|", "|");
747 cx.assert(
748 indoc! {"
749 The q|uick
750 brown fox"},
751 indoc! {"
752 The quick|
753 brown fox"},
754 );
755 cx.assert(
756 indoc! {"
757 |
758 The quick"},
759 indoc! {"
760 |
761 The quick"},
762 );
763 }
764
765 #[gpui::test]
766 async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
767 let cx = VimTestContext::new(cx, true).await;
768 let mut cx = cx.binding(["shift-^"]);
769 cx.assert("The q|uick", "|The quick");
770 cx.assert(" The q|uick", " |The quick");
771 cx.assert("|", "|");
772 cx.assert(
773 indoc! {"
774 The q|uick
775 brown fox"},
776 indoc! {"
777 |The quick
778 brown fox"},
779 );
780 cx.assert(
781 indoc! {"
782 |
783 The quick"},
784 indoc! {"
785 |
786 The quick"},
787 );
788 // Indoc disallows trailing whitspace.
789 cx.assert(" | \nThe quick", " | \nThe quick");
790 }
791
792 #[gpui::test]
793 async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
794 let cx = VimTestContext::new(cx, true).await;
795 let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
796 cx.assert("The q|uick", "|The quick");
797 cx.assert(" The q|uick", " |The quick");
798 cx.assert("|", "|");
799 cx.assert(
800 indoc! {"
801 The q|uick
802 brown fox"},
803 indoc! {"
804 |The quick
805 brown fox"},
806 );
807 cx.assert(
808 indoc! {"
809 |
810 The quick"},
811 indoc! {"
812 |
813 The quick"},
814 );
815 }
816
817 #[gpui::test]
818 async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
819 let cx = VimTestContext::new(cx, true).await;
820 let mut cx = cx.binding(["shift-D"]);
821 cx.assert(
822 indoc! {"
823 The q|uick
824 brown fox"},
825 indoc! {"
826 The |q
827 brown fox"},
828 );
829 cx.assert(
830 indoc! {"
831 The quick
832 |
833 brown fox"},
834 indoc! {"
835 The quick
836 |
837 brown fox"},
838 );
839 }
840
841 #[gpui::test]
842 async fn test_x(cx: &mut gpui::TestAppContext) {
843 let cx = VimTestContext::new(cx, true).await;
844 let mut cx = cx.binding(["x"]);
845 cx.assert("|Test", "|est");
846 cx.assert("Te|st", "Te|t");
847 cx.assert("Tes|t", "Te|s");
848 cx.assert(
849 indoc! {"
850 Tes|t
851 test"},
852 indoc! {"
853 Te|s
854 test"},
855 );
856 }
857
858 #[gpui::test]
859 async fn test_delete_left(cx: &mut gpui::TestAppContext) {
860 let cx = VimTestContext::new(cx, true).await;
861 let mut cx = cx.binding(["shift-X"]);
862 cx.assert("Te|st", "T|st");
863 cx.assert("T|est", "|est");
864 cx.assert("|Test", "|Test");
865 cx.assert(
866 indoc! {"
867 Test
868 |test"},
869 indoc! {"
870 Test
871 |test"},
872 );
873 }
874
875 #[gpui::test]
876 async fn test_o(cx: &mut gpui::TestAppContext) {
877 let cx = VimTestContext::new(cx, true).await;
878 let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
879
880 cx.assert(
881 "|",
882 indoc! {"
883
884 |"},
885 );
886 cx.assert(
887 "The |quick",
888 indoc! {"
889 The quick
890 |"},
891 );
892 cx.assert(
893 indoc! {"
894 The quick
895 brown |fox
896 jumps over"},
897 indoc! {"
898 The quick
899 brown fox
900 |
901 jumps over"},
902 );
903 cx.assert(
904 indoc! {"
905 The quick
906 brown fox
907 jumps |over"},
908 indoc! {"
909 The quick
910 brown fox
911 jumps over
912 |"},
913 );
914 cx.assert(
915 indoc! {"
916 The q|uick
917 brown fox
918 jumps over"},
919 indoc! {"
920 The quick
921 |
922 brown fox
923 jumps over"},
924 );
925 cx.assert(
926 indoc! {"
927 The quick
928 |
929 brown fox"},
930 indoc! {"
931 The quick
932
933 |
934 brown fox"},
935 );
936 cx.assert(
937 indoc! {"
938 fn test()
939 println!(|);"},
940 indoc! {"
941 fn test()
942 println!();
943 |"},
944 );
945 cx.assert(
946 indoc! {"
947 fn test(|)
948 println!();"},
949 indoc! {"
950 fn test()
951 |
952 println!();"},
953 );
954 }
955
956 #[gpui::test]
957 async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
958 let cx = VimTestContext::new(cx, true).await;
959 let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
960
961 cx.assert(
962 "|",
963 indoc! {"
964 |
965 "},
966 );
967 cx.assert(
968 "The |quick",
969 indoc! {"
970 |
971 The quick"},
972 );
973 cx.assert(
974 indoc! {"
975 The quick
976 brown |fox
977 jumps over"},
978 indoc! {"
979 The quick
980 |
981 brown fox
982 jumps over"},
983 );
984 cx.assert(
985 indoc! {"
986 The quick
987 brown fox
988 jumps |over"},
989 indoc! {"
990 The quick
991 brown fox
992 |
993 jumps over"},
994 );
995 cx.assert(
996 indoc! {"
997 The q|uick
998 brown fox
999 jumps over"},
1000 indoc! {"
1001 |
1002 The quick
1003 brown fox
1004 jumps over"},
1005 );
1006 cx.assert(
1007 indoc! {"
1008 The quick
1009 |
1010 brown fox"},
1011 indoc! {"
1012 The quick
1013 |
1014
1015 brown fox"},
1016 );
1017 cx.assert(
1018 indoc! {"
1019 fn test()
1020 println!(|);"},
1021 indoc! {"
1022 fn test()
1023 |
1024 println!();"},
1025 );
1026 cx.assert(
1027 indoc! {"
1028 fn test(|)
1029 println!();"},
1030 indoc! {"
1031 |
1032 fn test()
1033 println!();"},
1034 );
1035 }
1036
1037 #[gpui::test]
1038 async fn test_dd(cx: &mut gpui::TestAppContext) {
1039 let cx = VimTestContext::new(cx, true).await;
1040 let mut cx = cx.binding(["d", "d"]);
1041
1042 cx.assert("|", "|");
1043 cx.assert("The |quick", "|");
1044 cx.assert(
1045 indoc! {"
1046 The quick
1047 brown |fox
1048 jumps over"},
1049 indoc! {"
1050 The quick
1051 jumps |over"},
1052 );
1053 cx.assert(
1054 indoc! {"
1055 The quick
1056 brown fox
1057 jumps |over"},
1058 indoc! {"
1059 The quick
1060 brown |fox"},
1061 );
1062 cx.assert(
1063 indoc! {"
1064 The q|uick
1065 brown fox
1066 jumps over"},
1067 indoc! {"
1068 brown| fox
1069 jumps over"},
1070 );
1071 cx.assert(
1072 indoc! {"
1073 The quick
1074 |
1075 brown fox"},
1076 indoc! {"
1077 The quick
1078 |brown fox"},
1079 );
1080 }
1081
1082 #[gpui::test]
1083 async fn test_cc(cx: &mut gpui::TestAppContext) {
1084 let cx = VimTestContext::new(cx, true).await;
1085 let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1086
1087 cx.assert("|", "|");
1088 cx.assert("The |quick", "|");
1089 cx.assert(
1090 indoc! {"
1091 The quick
1092 brown |fox
1093 jumps over"},
1094 indoc! {"
1095 The quick
1096 |
1097 jumps over"},
1098 );
1099 cx.assert(
1100 indoc! {"
1101 The quick
1102 brown fox
1103 jumps |over"},
1104 indoc! {"
1105 The quick
1106 brown fox
1107 |"},
1108 );
1109 cx.assert(
1110 indoc! {"
1111 The q|uick
1112 brown fox
1113 jumps over"},
1114 indoc! {"
1115 |
1116 brown fox
1117 jumps over"},
1118 );
1119 cx.assert(
1120 indoc! {"
1121 The quick
1122 |
1123 brown fox"},
1124 indoc! {"
1125 The quick
1126 |
1127 brown fox"},
1128 );
1129 }
1130
1131 #[gpui::test]
1132 async fn test_p(cx: &mut gpui::TestAppContext) {
1133 let mut cx = VimTestContext::new(cx, true).await;
1134 cx.set_state(
1135 indoc! {"
1136 The quick brown
1137 fox ju|mps over
1138 the lazy dog"},
1139 Mode::Normal,
1140 );
1141
1142 cx.simulate_keystrokes(["d", "d"]);
1143 cx.assert_editor_state(indoc! {"
1144 The quick brown
1145 the la|zy dog"});
1146
1147 cx.simulate_keystroke("p");
1148 cx.assert_state(
1149 indoc! {"
1150 The quick brown
1151 the lazy dog
1152 |fox jumps over"},
1153 Mode::Normal,
1154 );
1155
1156 cx.set_state(
1157 indoc! {"
1158 The quick brown
1159 fox [jump}s over
1160 the lazy dog"},
1161 Mode::Visual { line: false },
1162 );
1163 cx.simulate_keystroke("y");
1164 cx.set_state(
1165 indoc! {"
1166 The quick brown
1167 fox jumps ove|r
1168 the lazy dog"},
1169 Mode::Normal,
1170 );
1171 cx.simulate_keystroke("p");
1172 cx.assert_state(
1173 indoc! {"
1174 The quick brown
1175 fox jumps over|jumps
1176 the lazy dog"},
1177 Mode::Normal,
1178 );
1179 }
1180}