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