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