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