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