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