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::{AutoindentMode, SelectionGoal};
17use rope::point::Point;
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, false, 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, false, 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, false, 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, false, 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, false, 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
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 editor.set_clip_at_line_ends(false, cx);
203 if let Some(item) = cx.as_mut().read_from_clipboard() {
204 let mut clipboard_text = Cow::Borrowed(item.text());
205 if let Some(mut clipboard_selections) =
206 item.metadata::<Vec<ClipboardSelection>>()
207 {
208 let (display_map, selections) = editor.selections.all_display(cx);
209 let all_selections_were_entire_line =
210 clipboard_selections.iter().all(|s| s.is_entire_line);
211 if clipboard_selections.len() != selections.len() {
212 let mut newline_separated_text = String::new();
213 let mut clipboard_selections =
214 clipboard_selections.drain(..).peekable();
215 let mut ix = 0;
216 while let Some(clipboard_selection) = clipboard_selections.next() {
217 newline_separated_text
218 .push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
219 ix += clipboard_selection.len;
220 if clipboard_selections.peek().is_some() {
221 newline_separated_text.push('\n');
222 }
223 }
224 clipboard_text = Cow::Owned(newline_separated_text);
225 }
226
227 let mut new_selections = Vec::new();
228 editor.buffer().update(cx, |buffer, cx| {
229 let snapshot = buffer.snapshot(cx);
230 let mut start_offset = 0;
231 let mut edits = Vec::new();
232 for (ix, selection) in selections.iter().enumerate() {
233 let to_insert;
234 let linewise;
235 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
236 let end_offset = start_offset + clipboard_selection.len;
237 to_insert = &clipboard_text[start_offset..end_offset];
238 linewise = clipboard_selection.is_entire_line;
239 start_offset = end_offset;
240 } else {
241 to_insert = clipboard_text.as_str();
242 linewise = all_selections_were_entire_line;
243 }
244
245 // If the clipboard text was copied linewise, and the current selection
246 // is empty, then paste the text after this line and move the selection
247 // to the start of the pasted text
248 let insert_at = if linewise {
249 let (point, _) = display_map
250 .next_line_boundary(selection.start.to_point(&display_map));
251
252 if !to_insert.starts_with('\n') {
253 // Add newline before pasted text so that it shows up
254 edits.push((point..point, "\n"));
255 }
256 // Drop selection at the start of the next line
257 let selection_point = Point::new(point.row + 1, 0);
258 new_selections.push(selection.map(|_| selection_point));
259 point
260 } else {
261 let mut point = selection.end;
262 // Paste the text after the current selection
263 *point.column_mut() = point.column() + 1;
264 let point = display_map
265 .clip_point(point, Bias::Right)
266 .to_point(&display_map);
267
268 new_selections.push(selection.map(|_| point));
269 point
270 };
271
272 if linewise && to_insert.ends_with('\n') {
273 edits.push((
274 insert_at..insert_at,
275 &to_insert[0..to_insert.len().saturating_sub(1)],
276 ))
277 } else {
278 edits.push((insert_at..insert_at, to_insert));
279 }
280 }
281 drop(snapshot);
282 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
283 });
284
285 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
286 s.select(new_selections)
287 });
288 } else {
289 editor.insert(&clipboard_text, cx);
290 }
291 }
292 editor.set_clip_at_line_ends(true, cx);
293 });
294 });
295 });
296}
297
298#[cfg(test)]
299mod test {
300 use indoc::indoc;
301 use util::test::marked_text_offsets;
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(["$"]);
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_offsets(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![cursor_offset..cursor_offset]);
544 }
545
546 // Reset and test ignoring punctuation
547 let (_, cursor_offsets) = marked_text_offsets(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![cursor_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_offsets(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![cursor_offset..cursor_offset]);
591 }
592
593 // Reset and test ignoring punctuation
594 let (_, cursor_offsets) = marked_text_offsets(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![cursor_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_offsets(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![cursor_offset..cursor_offset]);
637 }
638
639 // Reset and test ignoring punctuation
640 let (_, cursor_offsets) = marked_text_offsets(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![cursor_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(["^"]);
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 }
941 "},
942 indoc! {"
943 fn test() {
944 println!();
945 ˇ
946 }
947 "},
948 );
949 cx.assert(
950 indoc! {"
951 fn test(ˇ) {
952 println!();
953 }"},
954 indoc! {"
955 fn test() {
956 ˇ
957 println!();
958 }"},
959 );
960 }
961
962 #[gpui::test]
963 async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
964 let cx = VimTestContext::new(cx, true).await;
965 let mut cx = cx.binding(["shift-o"]).mode_after(Mode::Insert);
966
967 cx.assert(
968 "ˇ",
969 indoc! {"
970 ˇ
971 "},
972 );
973 cx.assert(
974 "The ˇquick",
975 indoc! {"
976 ˇ
977 The quick"},
978 );
979 cx.assert(
980 indoc! {"
981 The quick
982 brown ˇfox
983 jumps over"},
984 indoc! {"
985 The quick
986 ˇ
987 brown fox
988 jumps over"},
989 );
990 cx.assert(
991 indoc! {"
992 The quick
993 brown fox
994 jumps ˇover"},
995 indoc! {"
996 The quick
997 brown fox
998 ˇ
999 jumps over"},
1000 );
1001 cx.assert(
1002 indoc! {"
1003 The qˇuick
1004 brown fox
1005 jumps over"},
1006 indoc! {"
1007 ˇ
1008 The quick
1009 brown fox
1010 jumps over"},
1011 );
1012 cx.assert(
1013 indoc! {"
1014 The quick
1015 ˇ
1016 brown fox"},
1017 indoc! {"
1018 The quick
1019 ˇ
1020
1021 brown fox"},
1022 );
1023 cx.assert(
1024 indoc! {"
1025 fn test()
1026 println!(ˇ);"},
1027 indoc! {"
1028 fn test()
1029 ˇ
1030 println!();"},
1031 );
1032 cx.assert(
1033 indoc! {"
1034 fn test(ˇ) {
1035 println!();
1036 }"},
1037 indoc! {"
1038 ˇ
1039 fn test() {
1040 println!();
1041 }"},
1042 );
1043 }
1044
1045 #[gpui::test]
1046 async fn test_dd(cx: &mut gpui::TestAppContext) {
1047 let cx = VimTestContext::new(cx, true).await;
1048 let mut cx = cx.binding(["d", "d"]);
1049
1050 cx.assert("ˇ", "ˇ");
1051 cx.assert("The ˇquick", "ˇ");
1052 cx.assert(
1053 indoc! {"
1054 The quick
1055 brown ˇfox
1056 jumps over"},
1057 indoc! {"
1058 The quick
1059 jumps ˇover"},
1060 );
1061 cx.assert(
1062 indoc! {"
1063 The quick
1064 brown fox
1065 jumps ˇover"},
1066 indoc! {"
1067 The quick
1068 brown ˇfox"},
1069 );
1070 cx.assert(
1071 indoc! {"
1072 The qˇuick
1073 brown fox
1074 jumps over"},
1075 indoc! {"
1076 brownˇ fox
1077 jumps over"},
1078 );
1079 cx.assert(
1080 indoc! {"
1081 The quick
1082 ˇ
1083 brown fox"},
1084 indoc! {"
1085 The quick
1086 ˇbrown fox"},
1087 );
1088 }
1089
1090 #[gpui::test]
1091 async fn test_cc(cx: &mut gpui::TestAppContext) {
1092 let cx = VimTestContext::new(cx, true).await;
1093 let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1094
1095 cx.assert("ˇ", "ˇ");
1096 cx.assert("The ˇquick", "ˇ");
1097 cx.assert(
1098 indoc! {"
1099 The quick
1100 brown ˇfox
1101 jumps over"},
1102 indoc! {"
1103 The quick
1104 ˇ
1105 jumps over"},
1106 );
1107 cx.assert(
1108 indoc! {"
1109 The quick
1110 brown fox
1111 jumps ˇover"},
1112 indoc! {"
1113 The quick
1114 brown fox
1115 ˇ"},
1116 );
1117 cx.assert(
1118 indoc! {"
1119 The qˇuick
1120 brown fox
1121 jumps over"},
1122 indoc! {"
1123 ˇ
1124 brown fox
1125 jumps over"},
1126 );
1127 cx.assert(
1128 indoc! {"
1129 The quick
1130 ˇ
1131 brown fox"},
1132 indoc! {"
1133 The quick
1134 ˇ
1135 brown fox"},
1136 );
1137 }
1138
1139 #[gpui::test]
1140 async fn test_p(cx: &mut gpui::TestAppContext) {
1141 let mut cx = VimTestContext::new(cx, true).await;
1142 cx.set_state(
1143 indoc! {"
1144 The quick brown
1145 fox juˇmps over
1146 the lazy dog"},
1147 Mode::Normal,
1148 );
1149
1150 cx.simulate_keystrokes(["d", "d"]);
1151 cx.assert_editor_state(indoc! {"
1152 The quick brown
1153 the laˇzy dog"});
1154
1155 cx.simulate_keystroke("p");
1156 cx.assert_state(
1157 indoc! {"
1158 The quick brown
1159 the lazy dog
1160 ˇfox jumps over"},
1161 Mode::Normal,
1162 );
1163
1164 cx.set_state(
1165 indoc! {"
1166 The quick brown
1167 fox «jumpˇ»s over
1168 the lazy dog"},
1169 Mode::Visual { line: false },
1170 );
1171 cx.simulate_keystroke("y");
1172 cx.set_state(
1173 indoc! {"
1174 The quick brown
1175 fox jumps oveˇr
1176 the lazy dog"},
1177 Mode::Normal,
1178 );
1179 cx.simulate_keystroke("p");
1180 cx.assert_state(
1181 indoc! {"
1182 The quick brown
1183 fox jumps overˇjumps
1184 the lazy dog"},
1185 Mode::Normal,
1186 );
1187 }
1188}