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