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