normal.rs

  1mod case;
  2mod change;
  3mod delete;
  4mod increment;
  5mod paste;
  6pub(crate) mod repeat;
  7mod scroll;
  8pub(crate) mod search;
  9pub mod substitute;
 10mod yank;
 11
 12use std::sync::Arc;
 13
 14use crate::{
 15    motion::{self, first_non_whitespace, next_line_end, right, Motion},
 16    object::Object,
 17    state::{Mode, Operator},
 18    Vim,
 19};
 20use collections::HashSet;
 21use editor::scroll::autoscroll::Autoscroll;
 22use editor::{Bias, DisplayPoint};
 23use gpui::{actions, AppContext, ViewContext, WindowContext};
 24use language::SelectionGoal;
 25use log::error;
 26use workspace::Workspace;
 27
 28use self::{
 29    case::change_case,
 30    change::{change_motion, change_object},
 31    delete::{delete_motion, delete_object},
 32    yank::{yank_motion, yank_object},
 33};
 34
 35actions!(
 36    InsertAfter,
 37    InsertBefore,
 38    InsertFirstNonWhitespace,
 39    InsertEndOfLine,
 40    InsertLineAbove,
 41    InsertLineBelow,
 42    DeleteLeft,
 43    DeleteRight,
 44    ChangeToEndOfLine,
 45    DeleteToEndOfLine,
 46    Yank,
 47    YankLine,
 48    ChangeCase,
 49    JoinLines,
 50);
 51
 52pub fn init(cx: &mut AppContext) {
 53    paste::init(cx);
 54    repeat::init(cx);
 55    scroll::init(cx);
 56    search::init(cx);
 57    substitute::init(cx);
 58    increment::init(cx);
 59
 60    // cx.add_action(insert_after);
 61    // cx.add_action(insert_before);
 62    // cx.add_action(insert_first_non_whitespace);
 63    // cx.add_action(insert_end_of_line);
 64    // cx.add_action(insert_line_above);
 65    // cx.add_action(insert_line_below);
 66    // cx.add_action(change_case);
 67    // cx.add_action(yank_line);
 68
 69    // cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
 70    //     Vim::update(cx, |vim, cx| {
 71    //         vim.record_current_action(cx);
 72    //         let times = vim.take_count(cx);
 73    //         delete_motion(vim, Motion::Left, times, cx);
 74    //     })
 75    // });
 76    // cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
 77    //     Vim::update(cx, |vim, cx| {
 78    //         vim.record_current_action(cx);
 79    //         let times = vim.take_count(cx);
 80    //         delete_motion(vim, Motion::Right, times, cx);
 81    //     })
 82    // });
 83    // cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
 84    //     Vim::update(cx, |vim, cx| {
 85    //         vim.start_recording(cx);
 86    //         let times = vim.take_count(cx);
 87    //         change_motion(
 88    //             vim,
 89    //             Motion::EndOfLine {
 90    //                 display_lines: false,
 91    //             },
 92    //             times,
 93    //             cx,
 94    //         );
 95    //     })
 96    // });
 97    // cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
 98    //     Vim::update(cx, |vim, cx| {
 99    //         vim.record_current_action(cx);
100    //         let times = vim.take_count(cx);
101    //         delete_motion(
102    //             vim,
103    //             Motion::EndOfLine {
104    //                 display_lines: false,
105    //             },
106    //             times,
107    //             cx,
108    //         );
109    //     })
110    // });
111    // cx.add_action(|_: &mut Workspace, _: &JoinLines, cx| {
112    //     Vim::update(cx, |vim, cx| {
113    //         vim.record_current_action(cx);
114    //         let mut times = vim.take_count(cx).unwrap_or(1);
115    //         if vim.state().mode.is_visual() {
116    //             times = 1;
117    //         } else if times > 1 {
118    //             // 2J joins two lines together (same as J or 1J)
119    //             times -= 1;
120    //         }
121
122    //         vim.update_active_editor(cx, |editor, cx| {
123    //             editor.transact(cx, |editor, cx| {
124    //                 for _ in 0..times {
125    //                     editor.join_lines(&Default::default(), cx)
126    //                 }
127    //             })
128    //         })
129    //     })
130    // })
131}
132
133pub fn normal_motion(
134    motion: Motion,
135    operator: Option<Operator>,
136    times: Option<usize>,
137    cx: &mut WindowContext,
138) {
139    Vim::update(cx, |vim, cx| {
140        match operator {
141            None => move_cursor(vim, motion, times, cx),
142            Some(Operator::Change) => change_motion(vim, motion, times, cx),
143            Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
144            Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
145            Some(operator) => {
146                // Can't do anything for text objects, Ignoring
147                error!("Unexpected normal mode motion operator: {:?}", operator)
148            }
149        }
150    });
151}
152
153pub fn normal_object(object: Object, cx: &mut WindowContext) {
154    Vim::update(cx, |vim, cx| {
155        match vim.maybe_pop_operator() {
156            Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
157                Some(Operator::Change) => change_object(vim, object, around, cx),
158                Some(Operator::Delete) => delete_object(vim, object, around, cx),
159                Some(Operator::Yank) => yank_object(vim, object, around, cx),
160                _ => {
161                    // Can't do anything for namespace operators. Ignoring
162                }
163            },
164            _ => {
165                // Can't do anything with change/delete/yank and text objects. Ignoring
166            }
167        }
168        vim.clear_operator(cx);
169    })
170}
171
172pub(crate) fn move_cursor(
173    vim: &mut Vim,
174    motion: Motion,
175    times: Option<usize>,
176    cx: &mut WindowContext,
177) {
178    vim.update_active_editor(cx, |editor, cx| {
179        let text_layout_details = editor.text_layout_details(cx);
180        editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
181            s.move_cursors_with(|map, cursor, goal| {
182                motion
183                    .move_point(map, cursor, goal, times, &text_layout_details)
184                    .unwrap_or((cursor, goal))
185            })
186        })
187    });
188}
189
190fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
191    Vim::update(cx, |vim, cx| {
192        vim.start_recording(cx);
193        vim.switch_mode(Mode::Insert, false, cx);
194        vim.update_active_editor(cx, |editor, cx| {
195            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
196                s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
197            });
198        });
199    });
200}
201
202fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
203    Vim::update(cx, |vim, cx| {
204        vim.start_recording(cx);
205        vim.switch_mode(Mode::Insert, false, cx);
206    });
207}
208
209fn insert_first_non_whitespace(
210    _: &mut Workspace,
211    _: &InsertFirstNonWhitespace,
212    cx: &mut ViewContext<Workspace>,
213) {
214    Vim::update(cx, |vim, cx| {
215        vim.start_recording(cx);
216        vim.switch_mode(Mode::Insert, false, cx);
217        vim.update_active_editor(cx, |editor, cx| {
218            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
219                s.move_cursors_with(|map, cursor, _| {
220                    (
221                        first_non_whitespace(map, false, cursor),
222                        SelectionGoal::None,
223                    )
224                });
225            });
226        });
227    });
228}
229
230fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
231    Vim::update(cx, |vim, cx| {
232        vim.start_recording(cx);
233        vim.switch_mode(Mode::Insert, false, cx);
234        vim.update_active_editor(cx, |editor, cx| {
235            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
236                s.move_cursors_with(|map, cursor, _| {
237                    (next_line_end(map, cursor, 1), SelectionGoal::None)
238                });
239            });
240        });
241    });
242}
243
244fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
245    Vim::update(cx, |vim, cx| {
246        vim.start_recording(cx);
247        vim.switch_mode(Mode::Insert, false, cx);
248        vim.update_active_editor(cx, |editor, cx| {
249            editor.transact(cx, |editor, cx| {
250                let (map, old_selections) = editor.selections.all_display(cx);
251                let selection_start_rows: HashSet<u32> = old_selections
252                    .into_iter()
253                    .map(|selection| selection.start.row())
254                    .collect();
255                let edits = selection_start_rows.into_iter().map(|row| {
256                    let (indent, _) = map.line_indent(row);
257                    let start_of_line =
258                        motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
259                            .to_point(&map);
260                    let mut new_text = " ".repeat(indent as usize);
261                    new_text.push('\n');
262                    (start_of_line..start_of_line, new_text)
263                });
264                editor.edit_with_autoindent(edits, cx);
265                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
266                    s.move_cursors_with(|map, cursor, _| {
267                        let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
268                        let insert_point = motion::end_of_line(map, false, previous_line);
269                        (insert_point, SelectionGoal::None)
270                    });
271                });
272            });
273        });
274    });
275}
276
277fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
278    Vim::update(cx, |vim, cx| {
279        vim.start_recording(cx);
280        vim.switch_mode(Mode::Insert, false, cx);
281        vim.update_active_editor(cx, |editor, cx| {
282            let text_layout_details = editor.text_layout_details(cx);
283            editor.transact(cx, |editor, cx| {
284                let (map, old_selections) = editor.selections.all_display(cx);
285
286                let selection_end_rows: HashSet<u32> = old_selections
287                    .into_iter()
288                    .map(|selection| selection.end.row())
289                    .collect();
290                let edits = selection_end_rows.into_iter().map(|row| {
291                    let (indent, _) = map.line_indent(row);
292                    let end_of_line =
293                        motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
294
295                    let mut new_text = "\n".to_string();
296                    new_text.push_str(&" ".repeat(indent as usize));
297                    (end_of_line..end_of_line, new_text)
298                });
299                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
300                    s.maybe_move_cursors_with(|map, cursor, goal| {
301                        Motion::CurrentLine.move_point(
302                            map,
303                            cursor,
304                            goal,
305                            None,
306                            &text_layout_details,
307                        )
308                    });
309                });
310                editor.edit_with_autoindent(edits, cx);
311            });
312        });
313    });
314}
315
316fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext<Workspace>) {
317    Vim::update(cx, |vim, cx| {
318        let count = vim.take_count(cx);
319        yank_motion(vim, motion::Motion::CurrentLine, count, cx)
320    })
321}
322
323pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
324    Vim::update(cx, |vim, cx| {
325        vim.stop_recording();
326        vim.update_active_editor(cx, |editor, cx| {
327            editor.transact(cx, |editor, cx| {
328                editor.set_clip_at_line_ends(false, cx);
329                let (map, display_selections) = editor.selections.all_display(cx);
330                // Selections are biased right at the start. So we need to store
331                // anchors that are biased left so that we can restore the selections
332                // after the change
333                let stable_anchors = editor
334                    .selections
335                    .disjoint_anchors()
336                    .into_iter()
337                    .map(|selection| {
338                        let start = selection.start.bias_left(&map.buffer_snapshot);
339                        start..start
340                    })
341                    .collect::<Vec<_>>();
342
343                let edits = display_selections
344                    .into_iter()
345                    .map(|selection| {
346                        let mut range = selection.range();
347                        *range.end.column_mut() += 1;
348                        range.end = map.clip_point(range.end, Bias::Right);
349
350                        (
351                            range.start.to_offset(&map, Bias::Left)
352                                ..range.end.to_offset(&map, Bias::Left),
353                            text.clone(),
354                        )
355                    })
356                    .collect::<Vec<_>>();
357
358                editor.buffer().update(cx, |buffer, cx| {
359                    buffer.edit(edits, None, cx);
360                });
361                editor.set_clip_at_line_ends(true, cx);
362                editor.change_selections(None, cx, |s| {
363                    s.select_anchor_ranges(stable_anchors);
364                });
365            });
366        });
367        vim.pop_operator(cx)
368    });
369}
370
371// #[cfg(test)]
372// mod test {
373//     use gpui::TestAppContext;
374//     use indoc::indoc;
375
376//     use crate::{
377//         state::Mode::{self},
378//         test::NeovimBackedTestContext,
379//     };
380
381//     #[gpui::test]
382//     async fn test_h(cx: &mut gpui::TestAppContext) {
383//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
384//         cx.assert_all(indoc! {"
385//             ˇThe qˇuick
386//             ˇbrown"
387//         })
388//         .await;
389//     }
390
391//     #[gpui::test]
392//     async fn test_backspace(cx: &mut gpui::TestAppContext) {
393//         let mut cx = NeovimBackedTestContext::new(cx)
394//             .await
395//             .binding(["backspace"]);
396//         cx.assert_all(indoc! {"
397//             ˇThe qˇuick
398//             ˇbrown"
399//         })
400//         .await;
401//     }
402
403//     #[gpui::test]
404//     async fn test_j(cx: &mut gpui::TestAppContext) {
405//         let mut cx = NeovimBackedTestContext::new(cx).await;
406
407//         cx.set_shared_state(indoc! {"
408//                     aaˇaa
409//                     😃😃"
410//         })
411//         .await;
412//         cx.simulate_shared_keystrokes(["j"]).await;
413//         cx.assert_shared_state(indoc! {"
414//                     aaaa
415//                     😃ˇ😃"
416//         })
417//         .await;
418
419//         for marked_position in cx.each_marked_position(indoc! {"
420//                     ˇThe qˇuick broˇwn
421//                     ˇfox jumps"
422//         }) {
423//             cx.assert_neovim_compatible(&marked_position, ["j"]).await;
424//         }
425//     }
426
427//     #[gpui::test]
428//     async fn test_enter(cx: &mut gpui::TestAppContext) {
429//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
430//         cx.assert_all(indoc! {"
431//             ˇThe qˇuick broˇwn
432//             ˇfox jumps"
433//         })
434//         .await;
435//     }
436
437//     #[gpui::test]
438//     async fn test_k(cx: &mut gpui::TestAppContext) {
439//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
440//         cx.assert_all(indoc! {"
441//             ˇThe qˇuick
442//             ˇbrown fˇox jumˇps"
443//         })
444//         .await;
445//     }
446
447//     #[gpui::test]
448//     async fn test_l(cx: &mut gpui::TestAppContext) {
449//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
450//         cx.assert_all(indoc! {"
451//             ˇThe qˇuicˇk
452//             ˇbrowˇn"})
453//             .await;
454//     }
455
456//     #[gpui::test]
457//     async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
458//         let mut cx = NeovimBackedTestContext::new(cx).await;
459//         cx.assert_binding_matches_all(
460//             ["$"],
461//             indoc! {"
462//             ˇThe qˇuicˇk
463//             ˇbrowˇn"},
464//         )
465//         .await;
466//         cx.assert_binding_matches_all(
467//             ["0"],
468//             indoc! {"
469//                 ˇThe qˇuicˇk
470//                 ˇbrowˇn"},
471//         )
472//         .await;
473//     }
474
475//     #[gpui::test]
476//     async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
477//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
478
479//         cx.assert_all(indoc! {"
480//                 The ˇquick
481
482//                 brown fox jumps
483//                 overˇ the lazy doˇg"})
484//             .await;
485//         cx.assert(indoc! {"
486//             The quiˇck
487
488//             brown"})
489//             .await;
490//         cx.assert(indoc! {"
491//             The quiˇck
492
493//             "})
494//             .await;
495//     }
496
497//     #[gpui::test]
498//     async fn test_w(cx: &mut gpui::TestAppContext) {
499//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
500//         cx.assert_all(indoc! {"
501//             The ˇquickˇ-ˇbrown
502//             ˇ
503//             ˇ
504//             ˇfox_jumps ˇover
505//             ˇthˇe"})
506//             .await;
507//         let mut cx = cx.binding(["shift-w"]);
508//         cx.assert_all(indoc! {"
509//             The ˇquickˇ-ˇbrown
510//             ˇ
511//             ˇ
512//             ˇfox_jumps ˇover
513//             ˇthˇe"})
514//             .await;
515//     }
516
517//     #[gpui::test]
518//     async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
519//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
520//         cx.assert_all(indoc! {"
521//             Thˇe quicˇkˇ-browˇn
522
523//             fox_jumpˇs oveˇr
524//             thˇe"})
525//             .await;
526//         let mut cx = cx.binding(["shift-e"]);
527//         cx.assert_all(indoc! {"
528//             Thˇe quicˇkˇ-browˇn
529
530//             fox_jumpˇs oveˇr
531//             thˇe"})
532//             .await;
533//     }
534
535//     #[gpui::test]
536//     async fn test_b(cx: &mut gpui::TestAppContext) {
537//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
538//         cx.assert_all(indoc! {"
539//             ˇThe ˇquickˇ-ˇbrown
540//             ˇ
541//             ˇ
542//             ˇfox_jumps ˇover
543//             ˇthe"})
544//             .await;
545//         let mut cx = cx.binding(["shift-b"]);
546//         cx.assert_all(indoc! {"
547//             ˇThe ˇquickˇ-ˇbrown
548//             ˇ
549//             ˇ
550//             ˇfox_jumps ˇover
551//             ˇthe"})
552//             .await;
553//     }
554
555//     #[gpui::test]
556//     async fn test_gg(cx: &mut gpui::TestAppContext) {
557//         let mut cx = NeovimBackedTestContext::new(cx).await;
558//         cx.assert_binding_matches_all(
559//             ["g", "g"],
560//             indoc! {"
561//                 The qˇuick
562
563//                 brown fox jumps
564//                 over ˇthe laˇzy dog"},
565//         )
566//         .await;
567//         cx.assert_binding_matches(
568//             ["g", "g"],
569//             indoc! {"
570
571//                 brown fox jumps
572//                 over the laˇzy dog"},
573//         )
574//         .await;
575//         cx.assert_binding_matches(
576//             ["2", "g", "g"],
577//             indoc! {"
578//                 ˇ
579
580//                 brown fox jumps
581//                 over the lazydog"},
582//         )
583//         .await;
584//     }
585
586//     #[gpui::test]
587//     async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
588//         let mut cx = NeovimBackedTestContext::new(cx).await;
589//         cx.assert_binding_matches_all(
590//             ["shift-g"],
591//             indoc! {"
592//                 The qˇuick
593
594//                 brown fox jumps
595//                 over ˇthe laˇzy dog"},
596//         )
597//         .await;
598//         cx.assert_binding_matches(
599//             ["shift-g"],
600//             indoc! {"
601
602//                 brown fox jumps
603//                 over the laˇzy dog"},
604//         )
605//         .await;
606//         cx.assert_binding_matches(
607//             ["2", "shift-g"],
608//             indoc! {"
609//                 ˇ
610
611//                 brown fox jumps
612//                 over the lazydog"},
613//         )
614//         .await;
615//     }
616
617//     #[gpui::test]
618//     async fn test_a(cx: &mut gpui::TestAppContext) {
619//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
620//         cx.assert_all("The qˇuicˇk").await;
621//     }
622
623//     #[gpui::test]
624//     async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
625//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
626//         cx.assert_all(indoc! {"
627//             ˇ
628//             The qˇuick
629//             brown ˇfox "})
630//             .await;
631//     }
632
633//     #[gpui::test]
634//     async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
635//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
636//         cx.assert("The qˇuick").await;
637//         cx.assert(" The qˇuick").await;
638//         cx.assert("ˇ").await;
639//         cx.assert(indoc! {"
640//                 The qˇuick
641//                 brown fox"})
642//             .await;
643//         cx.assert(indoc! {"
644//                 ˇ
645//                 The quick"})
646//             .await;
647//         // Indoc disallows trailing whitespace.
648//         cx.assert("   ˇ \nThe quick").await;
649//     }
650
651//     #[gpui::test]
652//     async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
653//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
654//         cx.assert("The qˇuick").await;
655//         cx.assert(" The qˇuick").await;
656//         cx.assert("ˇ").await;
657//         cx.assert(indoc! {"
658//                 The qˇuick
659//                 brown fox"})
660//             .await;
661//         cx.assert(indoc! {"
662//                 ˇ
663//                 The quick"})
664//             .await;
665//     }
666
667//     #[gpui::test]
668//     async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
669//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
670//         cx.assert(indoc! {"
671//                 The qˇuick
672//                 brown fox"})
673//             .await;
674//         cx.assert(indoc! {"
675//                 The quick
676//                 ˇ
677//                 brown fox"})
678//             .await;
679//     }
680
681//     #[gpui::test]
682//     async fn test_x(cx: &mut gpui::TestAppContext) {
683//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
684//         cx.assert_all("ˇTeˇsˇt").await;
685//         cx.assert(indoc! {"
686//                 Tesˇt
687//                 test"})
688//             .await;
689//     }
690
691//     #[gpui::test]
692//     async fn test_delete_left(cx: &mut gpui::TestAppContext) {
693//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
694//         cx.assert_all("ˇTˇeˇsˇt").await;
695//         cx.assert(indoc! {"
696//                 Test
697//                 ˇtest"})
698//             .await;
699//     }
700
701//     #[gpui::test]
702//     async fn test_o(cx: &mut gpui::TestAppContext) {
703//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
704//         cx.assert("ˇ").await;
705//         cx.assert("The ˇquick").await;
706//         cx.assert_all(indoc! {"
707//                 The qˇuick
708//                 brown ˇfox
709//                 jumps ˇover"})
710//             .await;
711//         cx.assert(indoc! {"
712//                 The quick
713//                 ˇ
714//                 brown fox"})
715//             .await;
716
717//         cx.assert_manual(
718//             indoc! {"
719//                 fn test() {
720//                     println!(ˇ);
721//                 }"},
722//             Mode::Normal,
723//             indoc! {"
724//                 fn test() {
725//                     println!();
726//                     ˇ
727//                 }"},
728//             Mode::Insert,
729//         );
730
731//         cx.assert_manual(
732//             indoc! {"
733//                 fn test(ˇ) {
734//                     println!();
735//                 }"},
736//             Mode::Normal,
737//             indoc! {"
738//                 fn test() {
739//                     ˇ
740//                     println!();
741//                 }"},
742//             Mode::Insert,
743//         );
744//     }
745
746//     #[gpui::test]
747//     async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
748//         let cx = NeovimBackedTestContext::new(cx).await;
749//         let mut cx = cx.binding(["shift-o"]);
750//         cx.assert("ˇ").await;
751//         cx.assert("The ˇquick").await;
752//         cx.assert_all(indoc! {"
753//             The qˇuick
754//             brown ˇfox
755//             jumps ˇover"})
756//             .await;
757//         cx.assert(indoc! {"
758//             The quick
759//             ˇ
760//             brown fox"})
761//             .await;
762
763//         // Our indentation is smarter than vims. So we don't match here
764//         cx.assert_manual(
765//             indoc! {"
766//                 fn test() {
767//                     println!(ˇ);
768//                 }"},
769//             Mode::Normal,
770//             indoc! {"
771//                 fn test() {
772//                     ˇ
773//                     println!();
774//                 }"},
775//             Mode::Insert,
776//         );
777//         cx.assert_manual(
778//             indoc! {"
779//                 fn test(ˇ) {
780//                     println!();
781//                 }"},
782//             Mode::Normal,
783//             indoc! {"
784//                 ˇ
785//                 fn test() {
786//                     println!();
787//                 }"},
788//             Mode::Insert,
789//         );
790//     }
791
792//     #[gpui::test]
793//     async fn test_dd(cx: &mut gpui::TestAppContext) {
794//         let mut cx = NeovimBackedTestContext::new(cx).await;
795//         cx.assert_neovim_compatible("ˇ", ["d", "d"]).await;
796//         cx.assert_neovim_compatible("The ˇquick", ["d", "d"]).await;
797//         for marked_text in cx.each_marked_position(indoc! {"
798//             The qˇuick
799//             brown ˇfox
800//             jumps ˇover"})
801//         {
802//             cx.assert_neovim_compatible(&marked_text, ["d", "d"]).await;
803//         }
804//         cx.assert_neovim_compatible(
805//             indoc! {"
806//                 The quick
807//                 ˇ
808//                 brown fox"},
809//             ["d", "d"],
810//         )
811//         .await;
812//     }
813
814//     #[gpui::test]
815//     async fn test_cc(cx: &mut gpui::TestAppContext) {
816//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
817//         cx.assert("ˇ").await;
818//         cx.assert("The ˇquick").await;
819//         cx.assert_all(indoc! {"
820//                 The quˇick
821//                 brown ˇfox
822//                 jumps ˇover"})
823//             .await;
824//         cx.assert(indoc! {"
825//                 The quick
826//                 ˇ
827//                 brown fox"})
828//             .await;
829//     }
830
831//     #[gpui::test]
832//     async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
833//         let mut cx = NeovimBackedTestContext::new(cx).await;
834
835//         for count in 1..=5 {
836//             cx.assert_binding_matches_all(
837//                 [&count.to_string(), "w"],
838//                 indoc! {"
839//                     ˇThe quˇickˇ browˇn
840//                     ˇ
841//                     ˇfox ˇjumpsˇ-ˇoˇver
842//                     ˇthe lazy dog
843//                 "},
844//             )
845//             .await;
846//         }
847//     }
848
849//     #[gpui::test]
850//     async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
851//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
852//         cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
853//     }
854
855//     #[gpui::test]
856//     async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
857//         let mut cx = NeovimBackedTestContext::new(cx).await;
858
859//         for count in 1..=3 {
860//             let test_case = indoc! {"
861//                 ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
862//                 ˇ    ˇbˇaaˇa ˇbˇbˇb
863//                 ˇ
864//                 ˇb
865//             "};
866
867//             cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
868//                 .await;
869
870//             cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
871//                 .await;
872//         }
873//     }
874
875//     #[gpui::test]
876//     async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
877//         let mut cx = NeovimBackedTestContext::new(cx).await;
878//         let test_case = indoc! {"
879//             ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
880//             ˇ    ˇbˇaaˇa ˇbˇbˇb
881//             ˇ•••
882//             ˇb
883//             "
884//         };
885
886//         for count in 1..=3 {
887//             cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
888//                 .await;
889
890//             cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
891//                 .await;
892//         }
893//     }
894
895//     #[gpui::test]
896//     async fn test_percent(cx: &mut TestAppContext) {
897//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
898//         cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
899//         cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
900//             .await;
901//         cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
902//     }
903// }