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 // Indoc disallows trailing whitspace.
781 cx.assert(" | \nThe quick", " | \nThe quick");
782 }
783
784 #[gpui::test]
785 async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
786 let cx = VimTestContext::new(cx, true).await;
787 let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
788 cx.assert("The q|uick", "|The quick");
789 cx.assert(" The q|uick", " |The quick");
790 cx.assert("|", "|");
791 cx.assert(
792 indoc! {"
793 The q|uick
794 brown fox"},
795 indoc! {"
796 |The quick
797 brown fox"},
798 );
799 cx.assert(
800 indoc! {"
801 |
802 The quick"},
803 indoc! {"
804 |
805 The quick"},
806 );
807 }
808
809 #[gpui::test]
810 async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
811 let cx = VimTestContext::new(cx, true).await;
812 let mut cx = cx.binding(["shift-D"]);
813 cx.assert(
814 indoc! {"
815 The q|uick
816 brown fox"},
817 indoc! {"
818 The |q
819 brown fox"},
820 );
821 cx.assert(
822 indoc! {"
823 The quick
824 |
825 brown fox"},
826 indoc! {"
827 The quick
828 |
829 brown fox"},
830 );
831 }
832
833 #[gpui::test]
834 async fn test_x(cx: &mut gpui::TestAppContext) {
835 let cx = VimTestContext::new(cx, true).await;
836 let mut cx = cx.binding(["x"]);
837 cx.assert("|Test", "|est");
838 cx.assert("Te|st", "Te|t");
839 cx.assert("Tes|t", "Te|s");
840 cx.assert(
841 indoc! {"
842 Tes|t
843 test"},
844 indoc! {"
845 Te|s
846 test"},
847 );
848 }
849
850 #[gpui::test]
851 async fn test_delete_left(cx: &mut gpui::TestAppContext) {
852 let cx = VimTestContext::new(cx, true).await;
853 let mut cx = cx.binding(["shift-X"]);
854 cx.assert("Te|st", "T|st");
855 cx.assert("T|est", "|est");
856 cx.assert("|Test", "|Test");
857 cx.assert(
858 indoc! {"
859 Test
860 |test"},
861 indoc! {"
862 Test
863 |test"},
864 );
865 }
866
867 #[gpui::test]
868 async fn test_o(cx: &mut gpui::TestAppContext) {
869 let cx = VimTestContext::new(cx, true).await;
870 let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
871
872 cx.assert(
873 "|",
874 indoc! {"
875
876 |"},
877 );
878 cx.assert(
879 "The |quick",
880 indoc! {"
881 The quick
882 |"},
883 );
884 cx.assert(
885 indoc! {"
886 The quick
887 brown |fox
888 jumps over"},
889 indoc! {"
890 The quick
891 brown fox
892 |
893 jumps over"},
894 );
895 cx.assert(
896 indoc! {"
897 The quick
898 brown fox
899 jumps |over"},
900 indoc! {"
901 The quick
902 brown fox
903 jumps over
904 |"},
905 );
906 cx.assert(
907 indoc! {"
908 The q|uick
909 brown fox
910 jumps over"},
911 indoc! {"
912 The quick
913 |
914 brown fox
915 jumps over"},
916 );
917 cx.assert(
918 indoc! {"
919 The quick
920 |
921 brown fox"},
922 indoc! {"
923 The quick
924
925 |
926 brown fox"},
927 );
928 cx.assert(
929 indoc! {"
930 fn test()
931 println!(|);"},
932 indoc! {"
933 fn test()
934 println!();
935 |"},
936 );
937 cx.assert(
938 indoc! {"
939 fn test(|)
940 println!();"},
941 indoc! {"
942 fn test()
943 |
944 println!();"},
945 );
946 }
947
948 #[gpui::test]
949 async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
950 let cx = VimTestContext::new(cx, true).await;
951 let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
952
953 cx.assert(
954 "|",
955 indoc! {"
956 |
957 "},
958 );
959 cx.assert(
960 "The |quick",
961 indoc! {"
962 |
963 The quick"},
964 );
965 cx.assert(
966 indoc! {"
967 The quick
968 brown |fox
969 jumps over"},
970 indoc! {"
971 The quick
972 |
973 brown fox
974 jumps over"},
975 );
976 cx.assert(
977 indoc! {"
978 The quick
979 brown fox
980 jumps |over"},
981 indoc! {"
982 The quick
983 brown fox
984 |
985 jumps over"},
986 );
987 cx.assert(
988 indoc! {"
989 The q|uick
990 brown fox
991 jumps over"},
992 indoc! {"
993 |
994 The quick
995 brown fox
996 jumps over"},
997 );
998 cx.assert(
999 indoc! {"
1000 The quick
1001 |
1002 brown fox"},
1003 indoc! {"
1004 The quick
1005 |
1006
1007 brown fox"},
1008 );
1009 cx.assert(
1010 indoc! {"
1011 fn test()
1012 println!(|);"},
1013 indoc! {"
1014 fn test()
1015 |
1016 println!();"},
1017 );
1018 cx.assert(
1019 indoc! {"
1020 fn test(|)
1021 println!();"},
1022 indoc! {"
1023 |
1024 fn test()
1025 println!();"},
1026 );
1027 }
1028
1029 #[gpui::test]
1030 async fn test_dd(cx: &mut gpui::TestAppContext) {
1031 let cx = VimTestContext::new(cx, true).await;
1032 let mut cx = cx.binding(["d", "d"]);
1033
1034 cx.assert("|", "|");
1035 cx.assert("The |quick", "|");
1036 cx.assert(
1037 indoc! {"
1038 The quick
1039 brown |fox
1040 jumps over"},
1041 indoc! {"
1042 The quick
1043 jumps |over"},
1044 );
1045 cx.assert(
1046 indoc! {"
1047 The quick
1048 brown fox
1049 jumps |over"},
1050 indoc! {"
1051 The quick
1052 brown |fox"},
1053 );
1054 cx.assert(
1055 indoc! {"
1056 The q|uick
1057 brown fox
1058 jumps over"},
1059 indoc! {"
1060 brown| fox
1061 jumps over"},
1062 );
1063 cx.assert(
1064 indoc! {"
1065 The quick
1066 |
1067 brown fox"},
1068 indoc! {"
1069 The quick
1070 |brown fox"},
1071 );
1072 }
1073
1074 #[gpui::test]
1075 async fn test_cc(cx: &mut gpui::TestAppContext) {
1076 let cx = VimTestContext::new(cx, true).await;
1077 let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1078
1079 cx.assert("|", "|");
1080 cx.assert("The |quick", "|");
1081 cx.assert(
1082 indoc! {"
1083 The quick
1084 brown |fox
1085 jumps over"},
1086 indoc! {"
1087 The quick
1088 |
1089 jumps over"},
1090 );
1091 cx.assert(
1092 indoc! {"
1093 The quick
1094 brown fox
1095 jumps |over"},
1096 indoc! {"
1097 The quick
1098 brown fox
1099 |"},
1100 );
1101 cx.assert(
1102 indoc! {"
1103 The q|uick
1104 brown fox
1105 jumps over"},
1106 indoc! {"
1107 |
1108 brown fox
1109 jumps over"},
1110 );
1111 cx.assert(
1112 indoc! {"
1113 The quick
1114 |
1115 brown fox"},
1116 indoc! {"
1117 The quick
1118 |
1119 brown fox"},
1120 );
1121 }
1122
1123 #[gpui::test]
1124 async fn test_p(cx: &mut gpui::TestAppContext) {
1125 let mut cx = VimTestContext::new(cx, true).await;
1126 cx.set_state(
1127 indoc! {"
1128 The quick brown
1129 fox ju|mps over
1130 the lazy dog"},
1131 Mode::Normal,
1132 );
1133
1134 cx.simulate_keystrokes(["d", "d"]);
1135 cx.assert_editor_state(indoc! {"
1136 The quick brown
1137 the la|zy dog"});
1138
1139 cx.simulate_keystroke("p");
1140 cx.assert_editor_state(indoc! {"
1141 The quick brown
1142 the lazy dog
1143 |fox jumps over"});
1144 }
1145}