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// }