1use anyhow::Result;
2use std::sync::Arc;
3
4use collections::HashMap;
5use editor::{
6 display_map::{DisplaySnapshot, ToDisplayPoint},
7 movement,
8 scroll::autoscroll::Autoscroll,
9 Bias, DisplayPoint, Editor,
10};
11use gpui::{actions, AppContext, ViewContext, WindowContext};
12use language::{Selection, SelectionGoal};
13use workspace::Workspace;
14
15use crate::{
16 motion::{start_of_line, Motion},
17 object::Object,
18 state::{Mode, Operator},
19 utils::copy_selections_content,
20 Vim,
21};
22
23actions!(
24 ToggleVisual,
25 ToggleVisualLine,
26 ToggleVisualBlock,
27 VisualDelete,
28 VisualYank,
29 OtherEnd,
30 SelectNext,
31 SelectPrevious,
32);
33
34pub fn init(cx: &mut AppContext) {
35 // todo!()
36 // cx.add_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
37 // toggle_mode(Mode::Visual, cx)
38 // });
39 // cx.add_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
40 // toggle_mode(Mode::VisualLine, cx)
41 // });
42 // cx.add_action(
43 // |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
44 // toggle_mode(Mode::VisualBlock, cx)
45 // },
46 // );
47 // cx.add_action(other_end);
48 // cx.add_action(delete);
49 // cx.add_action(yank);
50
51 // cx.add_action(select_next);
52 // cx.add_action(select_previous);
53}
54
55pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
56 Vim::update(cx, |vim, cx| {
57 vim.update_active_editor(cx, |editor, cx| {
58 let text_layout_details = editor.text_layout_details(cx);
59 if vim.state().mode == Mode::VisualBlock
60 && !matches!(
61 motion,
62 Motion::EndOfLine {
63 display_lines: false
64 }
65 )
66 {
67 let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
68 visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
69 motion.move_point(map, point, goal, times, &text_layout_details)
70 })
71 } else {
72 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
73 s.move_with(|map, selection| {
74 let was_reversed = selection.reversed;
75 let mut current_head = selection.head();
76
77 // our motions assume the current character is after the cursor,
78 // but in (forward) visual mode the current character is just
79 // before the end of the selection.
80
81 // If the file ends with a newline (which is common) we don't do this.
82 // so that if you go to the end of such a file you can use "up" to go
83 // to the previous line and have it work somewhat as expected.
84 if !selection.reversed
85 && !selection.is_empty()
86 && !(selection.end.column() == 0 && selection.end == map.max_point())
87 {
88 current_head = movement::left(map, selection.end)
89 }
90
91 let Some((new_head, goal)) = motion.move_point(
92 map,
93 current_head,
94 selection.goal,
95 times,
96 &text_layout_details,
97 ) else {
98 return;
99 };
100
101 selection.set_head(new_head, goal);
102
103 // ensure the current character is included in the selection.
104 if !selection.reversed {
105 let next_point = if vim.state().mode == Mode::VisualBlock {
106 movement::saturating_right(map, selection.end)
107 } else {
108 movement::right(map, selection.end)
109 };
110
111 if !(next_point.column() == 0 && next_point == map.max_point()) {
112 selection.end = next_point;
113 }
114 }
115
116 // vim always ensures the anchor character stays selected.
117 // if our selection has reversed, we need to move the opposite end
118 // to ensure the anchor is still selected.
119 if was_reversed && !selection.reversed {
120 selection.start = movement::left(map, selection.start);
121 } else if !was_reversed && selection.reversed {
122 selection.end = movement::right(map, selection.end);
123 }
124 })
125 });
126 }
127 });
128 });
129}
130
131pub fn visual_block_motion(
132 preserve_goal: bool,
133 editor: &mut Editor,
134 cx: &mut ViewContext<Editor>,
135 mut move_selection: impl FnMut(
136 &DisplaySnapshot,
137 DisplayPoint,
138 SelectionGoal,
139 ) -> Option<(DisplayPoint, SelectionGoal)>,
140) {
141 let text_layout_details = editor.text_layout_details(cx);
142 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
143 let map = &s.display_map();
144 let mut head = s.newest_anchor().head().to_display_point(map);
145 let mut tail = s.oldest_anchor().tail().to_display_point(map);
146
147 let mut head_x = map.x_for_display_point(head, &text_layout_details);
148 let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
149
150 let (start, end) = match s.newest_anchor().goal {
151 SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
152 SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
153 _ => (tail_x.0, head_x.0),
154 };
155 let mut goal = SelectionGoal::HorizontalRange { start, end };
156
157 let was_reversed = tail_x > head_x;
158 if !was_reversed && !preserve_goal {
159 head = movement::saturating_left(map, head);
160 }
161
162 let Some((new_head, _)) = move_selection(&map, head, goal) else {
163 return;
164 };
165 head = new_head;
166 head_x = map.x_for_display_point(head, &text_layout_details);
167
168 let is_reversed = tail_x > head_x;
169 if was_reversed && !is_reversed {
170 tail = movement::saturating_left(map, tail);
171 tail_x = map.x_for_display_point(tail, &text_layout_details);
172 } else if !was_reversed && is_reversed {
173 tail = movement::saturating_right(map, tail);
174 tail_x = map.x_for_display_point(tail, &text_layout_details);
175 }
176 if !is_reversed && !preserve_goal {
177 head = movement::saturating_right(map, head);
178 head_x = map.x_for_display_point(head, &text_layout_details);
179 }
180
181 let positions = if is_reversed {
182 head_x..tail_x
183 } else {
184 tail_x..head_x
185 };
186
187 if !preserve_goal {
188 goal = SelectionGoal::HorizontalRange {
189 start: positions.start.0,
190 end: positions.end.0,
191 };
192 }
193
194 let mut selections = Vec::new();
195 let mut row = tail.row();
196
197 loop {
198 let layed_out_line = map.layout_row(row, &text_layout_details);
199 let start = DisplayPoint::new(
200 row,
201 layed_out_line.closest_index_for_x(positions.start) as u32,
202 );
203 let mut end = DisplayPoint::new(
204 row,
205 layed_out_line.closest_index_for_x(positions.end) as u32,
206 );
207 if end <= start {
208 if start.column() == map.line_len(start.row()) {
209 end = start;
210 } else {
211 end = movement::saturating_right(map, start);
212 }
213 }
214
215 if positions.start <= layed_out_line.width {
216 let selection = Selection {
217 id: s.new_selection_id(),
218 start: start.to_point(map),
219 end: end.to_point(map),
220 reversed: is_reversed,
221 goal: goal.clone(),
222 };
223
224 selections.push(selection);
225 }
226 if row == head.row() {
227 break;
228 }
229 if tail.row() > head.row() {
230 row -= 1
231 } else {
232 row += 1
233 }
234 }
235
236 s.select(selections);
237 })
238}
239
240pub fn visual_object(object: Object, cx: &mut WindowContext) {
241 Vim::update(cx, |vim, cx| {
242 if let Some(Operator::Object { around }) = vim.active_operator() {
243 vim.pop_operator(cx);
244 let current_mode = vim.state().mode;
245 let target_mode = object.target_visual_mode(current_mode);
246 if target_mode != current_mode {
247 vim.switch_mode(target_mode, true, cx);
248 }
249
250 vim.update_active_editor(cx, |editor, cx| {
251 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
252 s.move_with(|map, selection| {
253 let mut head = selection.head();
254
255 // all our motions assume that the current character is
256 // after the cursor; however in the case of a visual selection
257 // the current character is before the cursor.
258 if !selection.reversed {
259 head = movement::left(map, head);
260 }
261
262 if let Some(range) = object.range(map, head, around) {
263 if !range.is_empty() {
264 let expand_both_ways = object.always_expands_both_ways()
265 || selection.is_empty()
266 || movement::right(map, selection.start) == selection.end;
267
268 if expand_both_ways {
269 selection.start = range.start;
270 selection.end = range.end;
271 } else if selection.reversed {
272 selection.start = range.start;
273 } else {
274 selection.end = range.end;
275 }
276 }
277 }
278 });
279 });
280 });
281 }
282 });
283}
284
285fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
286 Vim::update(cx, |vim, cx| {
287 if vim.state().mode == mode {
288 vim.switch_mode(Mode::Normal, false, cx);
289 } else {
290 vim.switch_mode(mode, false, cx);
291 }
292 })
293}
294
295pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
296 Vim::update(cx, |vim, cx| {
297 vim.update_active_editor(cx, |editor, cx| {
298 editor.change_selections(None, cx, |s| {
299 s.move_with(|_, selection| {
300 selection.reversed = !selection.reversed;
301 })
302 })
303 })
304 });
305}
306
307pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
308 Vim::update(cx, |vim, cx| {
309 vim.record_current_action(cx);
310 vim.update_active_editor(cx, |editor, cx| {
311 let mut original_columns: HashMap<_, _> = Default::default();
312 let line_mode = editor.selections.line_mode;
313
314 editor.transact(cx, |editor, cx| {
315 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
316 s.move_with(|map, selection| {
317 if line_mode {
318 let mut position = selection.head();
319 if !selection.reversed {
320 position = movement::left(map, position);
321 }
322 original_columns.insert(selection.id, position.to_point(map).column);
323 }
324 selection.goal = SelectionGoal::None;
325 });
326 });
327 copy_selections_content(editor, line_mode, cx);
328 editor.insert("", cx);
329
330 // Fixup cursor position after the deletion
331 editor.set_clip_at_line_ends(true, cx);
332 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
333 s.move_with(|map, selection| {
334 let mut cursor = selection.head().to_point(map);
335
336 if let Some(column) = original_columns.get(&selection.id) {
337 cursor.column = *column
338 }
339 let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
340 selection.collapse_to(cursor, selection.goal)
341 });
342 if vim.state().mode == Mode::VisualBlock {
343 s.select_anchors(vec![s.first_anchor()])
344 }
345 });
346 })
347 });
348 vim.switch_mode(Mode::Normal, true, cx);
349 });
350}
351
352pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
353 Vim::update(cx, |vim, cx| {
354 vim.update_active_editor(cx, |editor, cx| {
355 let line_mode = editor.selections.line_mode;
356 copy_selections_content(editor, line_mode, cx);
357 editor.change_selections(None, cx, |s| {
358 s.move_with(|map, selection| {
359 if line_mode {
360 selection.start = start_of_line(map, false, selection.start);
361 };
362 selection.collapse_to(selection.start, SelectionGoal::None)
363 });
364 if vim.state().mode == Mode::VisualBlock {
365 s.select_anchors(vec![s.first_anchor()])
366 }
367 });
368 });
369 vim.switch_mode(Mode::Normal, true, cx);
370 });
371}
372
373pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
374 Vim::update(cx, |vim, cx| {
375 vim.stop_recording();
376 vim.update_active_editor(cx, |editor, cx| {
377 editor.transact(cx, |editor, cx| {
378 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
379
380 // Selections are biased right at the start. So we need to store
381 // anchors that are biased left so that we can restore the selections
382 // after the change
383 let stable_anchors = editor
384 .selections
385 .disjoint_anchors()
386 .into_iter()
387 .map(|selection| {
388 let start = selection.start.bias_left(&display_map.buffer_snapshot);
389 start..start
390 })
391 .collect::<Vec<_>>();
392
393 let mut edits = Vec::new();
394 for selection in selections.iter() {
395 let selection = selection.clone();
396 for row_range in
397 movement::split_display_range_by_lines(&display_map, selection.range())
398 {
399 let range = row_range.start.to_offset(&display_map, Bias::Right)
400 ..row_range.end.to_offset(&display_map, Bias::Right);
401 let text = text.repeat(range.len());
402 edits.push((range, text));
403 }
404 }
405
406 editor.buffer().update(cx, |buffer, cx| {
407 buffer.edit(edits, None, cx);
408 });
409 editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
410 });
411 });
412 vim.switch_mode(Mode::Normal, false, cx);
413 });
414}
415
416pub fn select_next(
417 _: &mut Workspace,
418 _: &SelectNext,
419 cx: &mut ViewContext<Workspace>,
420) -> Result<()> {
421 Vim::update(cx, |vim, cx| {
422 let count =
423 vim.take_count(cx)
424 .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
425 vim.update_active_editor(cx, |editor, cx| {
426 for _ in 0..count {
427 match editor.select_next(&Default::default(), cx) {
428 Err(a) => return Err(a),
429 _ => {}
430 }
431 }
432 Ok(())
433 })
434 })
435 .unwrap_or(Ok(()))
436}
437
438pub fn select_previous(
439 _: &mut Workspace,
440 _: &SelectPrevious,
441 cx: &mut ViewContext<Workspace>,
442) -> Result<()> {
443 Vim::update(cx, |vim, cx| {
444 let count =
445 vim.take_count(cx)
446 .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
447 vim.update_active_editor(cx, |editor, cx| {
448 for _ in 0..count {
449 match editor.select_previous(&Default::default(), cx) {
450 Err(a) => return Err(a),
451 _ => {}
452 }
453 }
454 Ok(())
455 })
456 })
457 .unwrap_or(Ok(()))
458}
459
460// #[cfg(test)]
461// mod test {
462// use indoc::indoc;
463// use workspace::item::Item;
464
465// use crate::{
466// state::Mode,
467// test::{NeovimBackedTestContext, VimTestContext},
468// };
469
470// #[gpui::test]
471// async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
472// let mut cx = NeovimBackedTestContext::new(cx).await;
473
474// cx.set_shared_state(indoc! {
475// "The ˇquick brown
476// fox jumps over
477// the lazy dog"
478// })
479// .await;
480// let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
481
482// // entering visual mode should select the character
483// // under cursor
484// cx.simulate_shared_keystrokes(["v"]).await;
485// cx.assert_shared_state(indoc! { "The «qˇ»uick brown
486// fox jumps over
487// the lazy dog"})
488// .await;
489// cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
490
491// // forwards motions should extend the selection
492// cx.simulate_shared_keystrokes(["w", "j"]).await;
493// cx.assert_shared_state(indoc! { "The «quick brown
494// fox jumps oˇ»ver
495// the lazy dog"})
496// .await;
497
498// cx.simulate_shared_keystrokes(["escape"]).await;
499// assert_eq!(Mode::Normal, cx.neovim_mode().await);
500// cx.assert_shared_state(indoc! { "The quick brown
501// fox jumps ˇover
502// the lazy dog"})
503// .await;
504
505// // motions work backwards
506// cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
507// cx.assert_shared_state(indoc! { "The «ˇquick brown
508// fox jumps o»ver
509// the lazy dog"})
510// .await;
511
512// // works on empty lines
513// cx.set_shared_state(indoc! {"
514// a
515// ˇ
516// b
517// "})
518// .await;
519// let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
520// cx.simulate_shared_keystrokes(["v"]).await;
521// cx.assert_shared_state(indoc! {"
522// a
523// «
524// ˇ»b
525// "})
526// .await;
527// cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
528
529// // toggles off again
530// cx.simulate_shared_keystrokes(["v"]).await;
531// cx.assert_shared_state(indoc! {"
532// a
533// ˇ
534// b
535// "})
536// .await;
537
538// // works at the end of a document
539// cx.set_shared_state(indoc! {"
540// a
541// b
542// ˇ"})
543// .await;
544
545// cx.simulate_shared_keystrokes(["v"]).await;
546// cx.assert_shared_state(indoc! {"
547// a
548// b
549// ˇ"})
550// .await;
551// assert_eq!(cx.mode(), cx.neovim_mode().await);
552// }
553
554// #[gpui::test]
555// async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
556// let mut cx = NeovimBackedTestContext::new(cx).await;
557
558// cx.set_shared_state(indoc! {
559// "The ˇquick brown
560// fox jumps over
561// the lazy dog"
562// })
563// .await;
564// cx.simulate_shared_keystrokes(["shift-v"]).await;
565// cx.assert_shared_state(indoc! { "The «qˇ»uick brown
566// fox jumps over
567// the lazy dog"})
568// .await;
569// assert_eq!(cx.mode(), cx.neovim_mode().await);
570// cx.simulate_shared_keystrokes(["x"]).await;
571// cx.assert_shared_state(indoc! { "fox ˇjumps over
572// the lazy dog"})
573// .await;
574
575// // it should work on empty lines
576// cx.set_shared_state(indoc! {"
577// a
578// ˇ
579// b"})
580// .await;
581// cx.simulate_shared_keystrokes(["shift-v"]).await;
582// cx.assert_shared_state(indoc! { "
583// a
584// «
585// ˇ»b"})
586// .await;
587// cx.simulate_shared_keystrokes(["x"]).await;
588// cx.assert_shared_state(indoc! { "
589// a
590// ˇb"})
591// .await;
592
593// // it should work at the end of the document
594// cx.set_shared_state(indoc! {"
595// a
596// b
597// ˇ"})
598// .await;
599// let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
600// cx.simulate_shared_keystrokes(["shift-v"]).await;
601// cx.assert_shared_state(indoc! {"
602// a
603// b
604// ˇ"})
605// .await;
606// assert_eq!(cx.mode(), cx.neovim_mode().await);
607// cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
608// cx.simulate_shared_keystrokes(["x"]).await;
609// cx.assert_shared_state(indoc! {"
610// a
611// ˇb"})
612// .await;
613// }
614
615// #[gpui::test]
616// async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
617// let mut cx = NeovimBackedTestContext::new(cx).await;
618
619// cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
620// .await;
621
622// cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
623// .await;
624// cx.assert_binding_matches(
625// ["v", "w", "j", "x"],
626// indoc! {"
627// The ˇquick brown
628// fox jumps over
629// the lazy dog"},
630// )
631// .await;
632// // Test pasting code copied on delete
633// cx.simulate_shared_keystrokes(["j", "p"]).await;
634// cx.assert_state_matches().await;
635
636// let mut cx = cx.binding(["v", "w", "j", "x"]);
637// cx.assert_all(indoc! {"
638// The ˇquick brown
639// fox jumps over
640// the ˇlazy dog"})
641// .await;
642// let mut cx = cx.binding(["v", "b", "k", "x"]);
643// cx.assert_all(indoc! {"
644// The ˇquick brown
645// fox jumps ˇover
646// the ˇlazy dog"})
647// .await;
648// }
649
650// #[gpui::test]
651// async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
652// let mut cx = NeovimBackedTestContext::new(cx).await;
653
654// cx.set_shared_state(indoc! {"
655// The quˇick brown
656// fox jumps over
657// the lazy dog"})
658// .await;
659// cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
660// cx.assert_state_matches().await;
661
662// // Test pasting code copied on delete
663// cx.simulate_shared_keystroke("p").await;
664// cx.assert_state_matches().await;
665
666// cx.set_shared_state(indoc! {"
667// The quick brown
668// fox jumps over
669// the laˇzy dog"})
670// .await;
671// cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
672// cx.assert_state_matches().await;
673// cx.assert_shared_clipboard("the lazy dog\n").await;
674
675// for marked_text in cx.each_marked_position(indoc! {"
676// The quˇick brown
677// fox jumps over
678// the lazy dog"})
679// {
680// cx.set_shared_state(&marked_text).await;
681// cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
682// cx.assert_state_matches().await;
683// // Test pasting code copied on delete
684// cx.simulate_shared_keystroke("p").await;
685// cx.assert_state_matches().await;
686// }
687
688// cx.set_shared_state(indoc! {"
689// The ˇlong line
690// should not
691// crash
692// "})
693// .await;
694// cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
695// cx.assert_state_matches().await;
696// }
697
698// #[gpui::test]
699// async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
700// let mut cx = NeovimBackedTestContext::new(cx).await;
701
702// cx.set_shared_state("The quick ˇbrown").await;
703// cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
704// cx.assert_shared_state("The quick ˇbrown").await;
705// cx.assert_shared_clipboard("brown").await;
706
707// cx.set_shared_state(indoc! {"
708// The ˇquick brown
709// fox jumps over
710// the lazy dog"})
711// .await;
712// cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
713// cx.assert_shared_state(indoc! {"
714// The ˇquick brown
715// fox jumps over
716// the lazy dog"})
717// .await;
718// cx.assert_shared_clipboard(indoc! {"
719// quick brown
720// fox jumps o"})
721// .await;
722
723// cx.set_shared_state(indoc! {"
724// The quick brown
725// fox jumps over
726// the ˇlazy dog"})
727// .await;
728// cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
729// cx.assert_shared_state(indoc! {"
730// The quick brown
731// fox jumps over
732// the ˇlazy dog"})
733// .await;
734// cx.assert_shared_clipboard("lazy d").await;
735// cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
736// cx.assert_shared_clipboard("the lazy dog\n").await;
737
738// let mut cx = cx.binding(["v", "b", "k", "y"]);
739// cx.set_shared_state(indoc! {"
740// The ˇquick brown
741// fox jumps over
742// the lazy dog"})
743// .await;
744// cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
745// cx.assert_shared_state(indoc! {"
746// ˇThe quick brown
747// fox jumps over
748// the lazy dog"})
749// .await;
750// cx.assert_clipboard_content(Some("The q"));
751
752// cx.set_shared_state(indoc! {"
753// The quick brown
754// fox ˇjumps over
755// the lazy dog"})
756// .await;
757// cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
758// .await;
759// cx.assert_shared_state(indoc! {"
760// The quick brown
761// ˇfox jumps over
762// the lazy dog"})
763// .await;
764// cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
765// .await;
766// }
767
768// #[gpui::test]
769// async fn test_visual_block_mode(cx: &mut gpui::TestAppContext) {
770// let mut cx = NeovimBackedTestContext::new(cx).await;
771
772// cx.set_shared_state(indoc! {
773// "The ˇquick brown
774// fox jumps over
775// the lazy dog"
776// })
777// .await;
778// cx.simulate_shared_keystrokes(["ctrl-v"]).await;
779// cx.assert_shared_state(indoc! {
780// "The «qˇ»uick brown
781// fox jumps over
782// the lazy dog"
783// })
784// .await;
785// cx.simulate_shared_keystrokes(["2", "down"]).await;
786// cx.assert_shared_state(indoc! {
787// "The «qˇ»uick brown
788// fox «jˇ»umps over
789// the «lˇ»azy dog"
790// })
791// .await;
792// cx.simulate_shared_keystrokes(["e"]).await;
793// cx.assert_shared_state(indoc! {
794// "The «quicˇ»k brown
795// fox «jumpˇ»s over
796// the «lazyˇ» dog"
797// })
798// .await;
799// cx.simulate_shared_keystrokes(["^"]).await;
800// cx.assert_shared_state(indoc! {
801// "«ˇThe q»uick brown
802// «ˇfox j»umps over
803// «ˇthe l»azy dog"
804// })
805// .await;
806// cx.simulate_shared_keystrokes(["$"]).await;
807// cx.assert_shared_state(indoc! {
808// "The «quick brownˇ»
809// fox «jumps overˇ»
810// the «lazy dogˇ»"
811// })
812// .await;
813// cx.simulate_shared_keystrokes(["shift-f", " "]).await;
814// cx.assert_shared_state(indoc! {
815// "The «quickˇ» brown
816// fox «jumpsˇ» over
817// the «lazy ˇ»dog"
818// })
819// .await;
820
821// // toggling through visual mode works as expected
822// cx.simulate_shared_keystrokes(["v"]).await;
823// cx.assert_shared_state(indoc! {
824// "The «quick brown
825// fox jumps over
826// the lazy ˇ»dog"
827// })
828// .await;
829// cx.simulate_shared_keystrokes(["ctrl-v"]).await;
830// cx.assert_shared_state(indoc! {
831// "The «quickˇ» brown
832// fox «jumpsˇ» over
833// the «lazy ˇ»dog"
834// })
835// .await;
836
837// cx.set_shared_state(indoc! {
838// "The ˇquick
839// brown
840// fox
841// jumps over the
842
843// lazy dog
844// "
845// })
846// .await;
847// cx.simulate_shared_keystrokes(["ctrl-v", "down", "down"])
848// .await;
849// cx.assert_shared_state(indoc! {
850// "The«ˇ q»uick
851// bro«ˇwn»
852// foxˇ
853// jumps over the
854
855// lazy dog
856// "
857// })
858// .await;
859// cx.simulate_shared_keystrokes(["down"]).await;
860// cx.assert_shared_state(indoc! {
861// "The «qˇ»uick
862// brow«nˇ»
863// fox
864// jump«sˇ» over the
865
866// lazy dog
867// "
868// })
869// .await;
870// cx.simulate_shared_keystroke("left").await;
871// cx.assert_shared_state(indoc! {
872// "The«ˇ q»uick
873// bro«ˇwn»
874// foxˇ
875// jum«ˇps» over the
876
877// lazy dog
878// "
879// })
880// .await;
881// cx.simulate_shared_keystrokes(["s", "o", "escape"]).await;
882// cx.assert_shared_state(indoc! {
883// "Theˇouick
884// broo
885// foxo
886// jumo over the
887
888// lazy dog
889// "
890// })
891// .await;
892
893// //https://github.com/zed-industries/community/issues/1950
894// cx.set_shared_state(indoc! {
895// "Theˇ quick brown
896
897// fox jumps over
898// the lazy dog
899// "
900// })
901// .await;
902// cx.simulate_shared_keystrokes(["l", "ctrl-v", "j", "j"])
903// .await;
904// cx.assert_shared_state(indoc! {
905// "The «qˇ»uick brown
906
907// fox «jˇ»umps over
908// the lazy dog
909// "
910// })
911// .await;
912// }
913
914// #[gpui::test]
915// async fn test_visual_block_issue_2123(cx: &mut gpui::TestAppContext) {
916// let mut cx = NeovimBackedTestContext::new(cx).await;
917
918// cx.set_shared_state(indoc! {
919// "The ˇquick brown
920// fox jumps over
921// the lazy dog
922// "
923// })
924// .await;
925// cx.simulate_shared_keystrokes(["ctrl-v", "right", "down"])
926// .await;
927// cx.assert_shared_state(indoc! {
928// "The «quˇ»ick brown
929// fox «juˇ»mps over
930// the lazy dog
931// "
932// })
933// .await;
934// }
935
936// #[gpui::test]
937// async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
938// let mut cx = NeovimBackedTestContext::new(cx).await;
939
940// cx.set_shared_state(indoc! {
941// "ˇThe quick brown
942// fox jumps over
943// the lazy dog
944// "
945// })
946// .await;
947// cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
948// cx.assert_shared_state(indoc! {
949// "«Tˇ»he quick brown
950// «fˇ»ox jumps over
951// «tˇ»he lazy dog
952// ˇ"
953// })
954// .await;
955
956// cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
957// .await;
958// cx.assert_shared_state(indoc! {
959// "ˇkThe quick brown
960// kfox jumps over
961// kthe lazy dog
962// k"
963// })
964// .await;
965
966// cx.set_shared_state(indoc! {
967// "ˇThe quick brown
968// fox jumps over
969// the lazy dog
970// "
971// })
972// .await;
973// cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
974// cx.assert_shared_state(indoc! {
975// "«Tˇ»he quick brown
976// «fˇ»ox jumps over
977// «tˇ»he lazy dog
978// ˇ"
979// })
980// .await;
981// cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
982// cx.assert_shared_state(indoc! {
983// "ˇkhe quick brown
984// kox jumps over
985// khe lazy dog
986// k"
987// })
988// .await;
989// }
990
991// #[gpui::test]
992// async fn test_visual_object(cx: &mut gpui::TestAppContext) {
993// let mut cx = NeovimBackedTestContext::new(cx).await;
994
995// cx.set_shared_state("hello (in [parˇens] o)").await;
996// cx.simulate_shared_keystrokes(["ctrl-v", "l"]).await;
997// cx.simulate_shared_keystrokes(["a", "]"]).await;
998// cx.assert_shared_state("hello (in «[parens]ˇ» o)").await;
999// assert_eq!(cx.mode(), Mode::Visual);
1000// cx.simulate_shared_keystrokes(["i", "("]).await;
1001// cx.assert_shared_state("hello («in [parens] oˇ»)").await;
1002
1003// cx.set_shared_state("hello in a wˇord again.").await;
1004// cx.simulate_shared_keystrokes(["ctrl-v", "l", "i", "w"])
1005// .await;
1006// cx.assert_shared_state("hello in a w«ordˇ» again.").await;
1007// assert_eq!(cx.mode(), Mode::VisualBlock);
1008// cx.simulate_shared_keystrokes(["o", "a", "s"]).await;
1009// cx.assert_shared_state("«ˇhello in a word» again.").await;
1010// assert_eq!(cx.mode(), Mode::Visual);
1011// }
1012
1013// #[gpui::test]
1014// async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
1015// let mut cx = VimTestContext::new(cx, true).await;
1016
1017// cx.set_state("aˇbc", Mode::Normal);
1018// cx.simulate_keystrokes(["ctrl-v"]);
1019// assert_eq!(cx.mode(), Mode::VisualBlock);
1020// cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
1021// assert_eq!(cx.mode(), Mode::VisualBlock);
1022// }
1023// }