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