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