1use crate::{
2 Vim,
3 motion::{self, Motion},
4 object::Object,
5 state::Mode,
6};
7use editor::{Bias, movement};
8use gpui::{Context, Window};
9use language::BracketPair;
10
11use std::sync::Arc;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum SurroundsType {
15 Motion(Motion),
16 Object(Object, bool),
17 Selection,
18}
19
20impl Vim {
21 pub fn add_surrounds(
22 &mut self,
23 text: Arc<str>,
24 target: SurroundsType,
25 window: &mut Window,
26 cx: &mut Context<Self>,
27 ) {
28 self.stop_recording(cx);
29 let count = Vim::take_count(cx);
30 let forced_motion = Vim::take_forced_motion(cx);
31 let mode = self.mode;
32 self.update_editor(cx, |_, editor, cx| {
33 let text_layout_details = editor.text_layout_details(window);
34 editor.transact(window, cx, |editor, window, cx| {
35 editor.set_clip_at_line_ends(false, cx);
36
37 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
38 Some(pair) => pair.clone(),
39 None => BracketPair {
40 start: text.to_string(),
41 end: text.to_string(),
42 close: true,
43 surround: true,
44 newline: false,
45 },
46 };
47 let surround = pair.end != surround_alias((*text).as_ref());
48 let (display_map, display_selections) = editor.selections.all_adjusted_display(cx);
49 let mut edits = Vec::new();
50 let mut anchors = Vec::new();
51
52 for selection in &display_selections {
53 let range = match &target {
54 SurroundsType::Object(object, around) => {
55 object.range(&display_map, selection.clone(), *around, None)
56 }
57 SurroundsType::Motion(motion) => {
58 motion
59 .range(
60 &display_map,
61 selection.clone(),
62 count,
63 &text_layout_details,
64 forced_motion,
65 )
66 .map(|(mut range, _)| {
67 // The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
68 if let Motion::CurrentLine = motion {
69 range.start = motion::first_non_whitespace(
70 &display_map,
71 false,
72 range.start,
73 );
74 range.end = movement::saturating_right(
75 &display_map,
76 motion::last_non_whitespace(&display_map, range.end, 1),
77 );
78 }
79 range
80 })
81 }
82 SurroundsType::Selection => Some(selection.range()),
83 };
84
85 if let Some(range) = range {
86 let start = range.start.to_offset(&display_map, Bias::Right);
87 let end = range.end.to_offset(&display_map, Bias::Left);
88 let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
89 (format!("{}\n", pair.start), format!("\n{}", pair.end))
90 } else {
91 let maybe_space = if surround { " " } else { "" };
92 (
93 format!("{}{}", pair.start, maybe_space),
94 format!("{}{}", maybe_space, pair.end),
95 )
96 };
97 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
98
99 edits.push((start..start, start_cursor_str));
100 edits.push((end..end, end_cursor_str));
101 anchors.push(start_anchor..start_anchor);
102 } else {
103 let start_anchor = display_map
104 .buffer_snapshot
105 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
106 anchors.push(start_anchor..start_anchor);
107 }
108 }
109
110 editor.edit(edits, cx);
111 editor.set_clip_at_line_ends(true, cx);
112 editor.change_selections(Default::default(), window, cx, |s| {
113 if mode == Mode::VisualBlock {
114 s.select_anchor_ranges(anchors.into_iter().take(1))
115 } else {
116 s.select_anchor_ranges(anchors)
117 }
118 });
119 });
120 });
121 self.switch_mode(Mode::Normal, false, window, cx);
122 }
123
124 pub fn delete_surrounds(
125 &mut self,
126 text: Arc<str>,
127 window: &mut Window,
128 cx: &mut Context<Self>,
129 ) {
130 self.stop_recording(cx);
131
132 // only legitimate surrounds can be removed
133 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
134 Some(pair) => pair.clone(),
135 None => return,
136 };
137 let pair_object = match pair_to_object(&pair) {
138 Some(pair_object) => pair_object,
139 None => return,
140 };
141 let surround = pair.end != *text;
142
143 self.update_editor(cx, |_, editor, cx| {
144 editor.transact(window, cx, |editor, window, cx| {
145 editor.set_clip_at_line_ends(false, cx);
146
147 let (display_map, display_selections) = editor.selections.all_display(cx);
148 let mut edits = Vec::new();
149 let mut anchors = Vec::new();
150
151 for selection in &display_selections {
152 let start = selection.start.to_offset(&display_map, Bias::Left);
153 if let Some(range) =
154 pair_object.range(&display_map, selection.clone(), true, None)
155 {
156 // If the current parenthesis object is single-line,
157 // then we need to filter whether it is the current line or not
158 if !pair_object.is_multiline() {
159 let is_same_row = selection.start.row() == range.start.row()
160 && selection.end.row() == range.end.row();
161 if !is_same_row {
162 anchors.push(start..start);
163 continue;
164 }
165 }
166 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
167 // hello«ˇ "hello in a word" »again.
168 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
169 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
170 let mut chars_and_offset = display_map
171 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
172 .peekable();
173 while let Some((ch, offset)) = chars_and_offset.next() {
174 if ch.to_string() == pair.start {
175 let start = offset;
176 let mut end = start + 1;
177 if surround
178 && let Some((next_ch, _)) = chars_and_offset.peek()
179 && next_ch.eq(&' ')
180 {
181 end += 1;
182 }
183 edits.push((start..end, ""));
184 anchors.push(start..start);
185 break;
186 }
187 }
188 let mut reverse_chars_and_offsets = display_map
189 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
190 .peekable();
191 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
192 if ch.to_string() == pair.end {
193 let mut start = offset;
194 let end = start + 1;
195 if surround
196 && let Some((next_ch, _)) = reverse_chars_and_offsets.peek()
197 && next_ch.eq(&' ')
198 {
199 start -= 1;
200 }
201 edits.push((start..end, ""));
202 break;
203 }
204 }
205 } else {
206 anchors.push(start..start);
207 }
208 }
209
210 editor.change_selections(Default::default(), window, cx, |s| {
211 s.select_ranges(anchors);
212 });
213 edits.sort_by_key(|(range, _)| range.start);
214 editor.edit(edits, cx);
215 editor.set_clip_at_line_ends(true, cx);
216 });
217 });
218 }
219
220 pub fn change_surrounds(
221 &mut self,
222 text: Arc<str>,
223 target: Object,
224 window: &mut Window,
225 cx: &mut Context<Self>,
226 ) {
227 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
228 self.stop_recording(cx);
229 self.update_editor(cx, |_, editor, cx| {
230 editor.transact(window, cx, |editor, window, cx| {
231 editor.set_clip_at_line_ends(false, cx);
232
233 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
234 Some(pair) => pair.clone(),
235 None => BracketPair {
236 start: text.to_string(),
237 end: text.to_string(),
238 close: true,
239 surround: true,
240 newline: false,
241 },
242 };
243
244 // Determines whether space should be added/removed after
245 // and before the surround pairs.
246 // For example, using `cs{[` will add a space before and
247 // after the pair, while using `cs{]` will not, notice the
248 // use of the closing bracket instead of the opening bracket
249 // on the target object.
250 // In the case of quotes, the opening and closing is the
251 // same, so no space will ever be added or removed.
252 let surround = match target {
253 Object::Quotes
254 | Object::BackQuotes
255 | Object::AnyQuotes
256 | Object::MiniQuotes
257 | Object::DoubleQuotes => true,
258 _ => pair.end != surround_alias((*text).as_ref()),
259 };
260
261 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
262 let mut edits = Vec::new();
263 let mut anchors = Vec::new();
264
265 for selection in &selections {
266 let start = selection.start.to_offset(&display_map, Bias::Left);
267 if let Some(range) =
268 target.range(&display_map, selection.clone(), true, None)
269 {
270 if !target.is_multiline() {
271 let is_same_row = selection.start.row() == range.start.row()
272 && selection.end.row() == range.end.row();
273 if !is_same_row {
274 anchors.push(start..start);
275 continue;
276 }
277 }
278 let mut chars_and_offset = display_map
279 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
280 .peekable();
281 while let Some((ch, offset)) = chars_and_offset.next() {
282 if ch.to_string() == will_replace_pair.start {
283 let mut open_str = pair.start.clone();
284 let start = offset;
285 let mut end = start + 1;
286 if let Some((next_ch, _)) = chars_and_offset.peek() {
287 // If the next position is already a space or line break,
288 // we don't need to splice another space even under around
289 if surround && !next_ch.is_whitespace() {
290 open_str.push(' ');
291 } else if !surround && next_ch.to_string() == " " {
292 end += 1;
293 }
294 }
295 edits.push((start..end, open_str));
296 anchors.push(start..start);
297 break;
298 }
299 }
300
301 let mut reverse_chars_and_offsets = display_map
302 .reverse_buffer_chars_at(
303 range.end.to_offset(&display_map, Bias::Left),
304 )
305 .peekable();
306 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
307 if ch.to_string() == will_replace_pair.end {
308 let mut close_str = pair.end.clone();
309 let mut start = offset;
310 let end = start + 1;
311 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
312 if surround && !next_ch.is_whitespace() {
313 close_str.insert(0, ' ')
314 } else if !surround && next_ch.to_string() == " " {
315 start -= 1;
316 }
317 }
318 edits.push((start..end, close_str));
319 break;
320 }
321 }
322 } else {
323 anchors.push(start..start);
324 }
325 }
326
327 let stable_anchors = editor
328 .selections
329 .disjoint_anchors()
330 .iter()
331 .map(|selection| {
332 let start = selection.start.bias_left(&display_map.buffer_snapshot);
333 start..start
334 })
335 .collect::<Vec<_>>();
336 edits.sort_by_key(|(range, _)| range.start);
337 editor.edit(edits, cx);
338 editor.set_clip_at_line_ends(true, cx);
339 editor.change_selections(Default::default(), window, cx, |s| {
340 s.select_anchor_ranges(stable_anchors);
341 });
342 });
343 });
344 }
345 }
346
347 /// Checks if any of the current cursors are surrounded by a valid pair of brackets.
348 ///
349 /// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
350 /// A pair of brackets is considered valid if it is well-formed and properly closed.
351 ///
352 /// If a valid pair of brackets is found, the method returns `true` and the cursor is automatically moved to the start of the bracket pair.
353 /// If no valid pair of brackets is found for any cursor, the method returns `false`.
354 pub fn check_and_move_to_valid_bracket_pair(
355 &mut self,
356 object: Object,
357 window: &mut Window,
358 cx: &mut Context<Self>,
359 ) -> bool {
360 let mut valid = false;
361 if let Some(pair) = object_to_bracket_pair(object) {
362 self.update_editor(cx, |_, editor, cx| {
363 editor.transact(window, cx, |editor, window, cx| {
364 editor.set_clip_at_line_ends(false, cx);
365 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
366 let mut anchors = Vec::new();
367
368 for selection in &selections {
369 let start = selection.start.to_offset(&display_map, Bias::Left);
370 if let Some(range) =
371 object.range(&display_map, selection.clone(), true, None)
372 {
373 // If the current parenthesis object is single-line,
374 // then we need to filter whether it is the current line or not
375 if object.is_multiline()
376 || (!object.is_multiline()
377 && selection.start.row() == range.start.row()
378 && selection.end.row() == range.end.row())
379 {
380 valid = true;
381 let chars_and_offset = display_map
382 .buffer_chars_at(
383 range.start.to_offset(&display_map, Bias::Left),
384 )
385 .peekable();
386 for (ch, offset) in chars_and_offset {
387 if ch.to_string() == pair.start {
388 anchors.push(offset..offset);
389 break;
390 }
391 }
392 } else {
393 anchors.push(start..start)
394 }
395 } else {
396 anchors.push(start..start)
397 }
398 }
399 editor.change_selections(Default::default(), window, cx, |s| {
400 s.select_ranges(anchors);
401 });
402 editor.set_clip_at_line_ends(true, cx);
403 });
404 });
405 }
406 valid
407 }
408}
409
410fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
411 pairs
412 .iter()
413 .find(|pair| pair.start == surround_alias(ch) || pair.end == surround_alias(ch))
414}
415
416fn surround_alias(ch: &str) -> &str {
417 match ch {
418 "b" => ")",
419 "B" => "}",
420 "a" => ">",
421 "r" => "]",
422 _ => ch,
423 }
424}
425
426fn all_support_surround_pair() -> Vec<BracketPair> {
427 vec![
428 BracketPair {
429 start: "{".into(),
430 end: "}".into(),
431 close: true,
432 surround: true,
433 newline: false,
434 },
435 BracketPair {
436 start: "'".into(),
437 end: "'".into(),
438 close: true,
439 surround: true,
440 newline: false,
441 },
442 BracketPair {
443 start: "`".into(),
444 end: "`".into(),
445 close: true,
446 surround: true,
447 newline: false,
448 },
449 BracketPair {
450 start: "\"".into(),
451 end: "\"".into(),
452 close: true,
453 surround: true,
454 newline: false,
455 },
456 BracketPair {
457 start: "(".into(),
458 end: ")".into(),
459 close: true,
460 surround: true,
461 newline: false,
462 },
463 BracketPair {
464 start: "|".into(),
465 end: "|".into(),
466 close: true,
467 surround: true,
468 newline: false,
469 },
470 BracketPair {
471 start: "[".into(),
472 end: "]".into(),
473 close: true,
474 surround: true,
475 newline: false,
476 },
477 BracketPair {
478 start: "{".into(),
479 end: "}".into(),
480 close: true,
481 surround: true,
482 newline: false,
483 },
484 BracketPair {
485 start: "<".into(),
486 end: ">".into(),
487 close: true,
488 surround: true,
489 newline: false,
490 },
491 ]
492}
493
494fn pair_to_object(pair: &BracketPair) -> Option<Object> {
495 match pair.start.as_str() {
496 "'" => Some(Object::Quotes),
497 "`" => Some(Object::BackQuotes),
498 "\"" => Some(Object::DoubleQuotes),
499 "|" => Some(Object::VerticalBars),
500 "(" => Some(Object::Parentheses),
501 "[" => Some(Object::SquareBrackets),
502 "{" => Some(Object::CurlyBrackets),
503 "<" => Some(Object::AngleBrackets),
504 _ => None,
505 }
506}
507
508fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
509 match object {
510 Object::Quotes => Some(BracketPair {
511 start: "'".to_string(),
512 end: "'".to_string(),
513 close: true,
514 surround: true,
515 newline: false,
516 }),
517 Object::BackQuotes => Some(BracketPair {
518 start: "`".to_string(),
519 end: "`".to_string(),
520 close: true,
521 surround: true,
522 newline: false,
523 }),
524 Object::DoubleQuotes => Some(BracketPair {
525 start: "\"".to_string(),
526 end: "\"".to_string(),
527 close: true,
528 surround: true,
529 newline: false,
530 }),
531 Object::VerticalBars => Some(BracketPair {
532 start: "|".to_string(),
533 end: "|".to_string(),
534 close: true,
535 surround: true,
536 newline: false,
537 }),
538 Object::Parentheses => Some(BracketPair {
539 start: "(".to_string(),
540 end: ")".to_string(),
541 close: true,
542 surround: true,
543 newline: false,
544 }),
545 Object::SquareBrackets => Some(BracketPair {
546 start: "[".to_string(),
547 end: "]".to_string(),
548 close: true,
549 surround: true,
550 newline: false,
551 }),
552 Object::CurlyBrackets => Some(BracketPair {
553 start: "{".to_string(),
554 end: "}".to_string(),
555 close: true,
556 surround: true,
557 newline: false,
558 }),
559 Object::AngleBrackets => Some(BracketPair {
560 start: "<".to_string(),
561 end: ">".to_string(),
562 close: true,
563 surround: true,
564 newline: false,
565 }),
566 _ => None,
567 }
568}
569
570#[cfg(test)]
571mod test {
572 use gpui::KeyBinding;
573 use indoc::indoc;
574
575 use crate::{PushAddSurrounds, state::Mode, test::VimTestContext};
576
577 #[gpui::test]
578 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
579 let mut cx = VimTestContext::new(cx, true).await;
580
581 // test add surrounds with around
582 cx.set_state(
583 indoc! {"
584 The quˇick brown
585 fox jumps over
586 the lazy dog."},
587 Mode::Normal,
588 );
589 cx.simulate_keystrokes("y s i w {");
590 cx.assert_state(
591 indoc! {"
592 The ˇ{ quick } brown
593 fox jumps over
594 the lazy dog."},
595 Mode::Normal,
596 );
597
598 // test add surrounds not with around
599 cx.set_state(
600 indoc! {"
601 The quˇick brown
602 fox jumps over
603 the lazy dog."},
604 Mode::Normal,
605 );
606 cx.simulate_keystrokes("y s i w }");
607 cx.assert_state(
608 indoc! {"
609 The ˇ{quick} brown
610 fox jumps over
611 the lazy dog."},
612 Mode::Normal,
613 );
614
615 // test add surrounds with motion
616 cx.set_state(
617 indoc! {"
618 The quˇick brown
619 fox jumps over
620 the lazy dog."},
621 Mode::Normal,
622 );
623 cx.simulate_keystrokes("y s $ }");
624 cx.assert_state(
625 indoc! {"
626 The quˇ{ick brown}
627 fox jumps over
628 the lazy dog."},
629 Mode::Normal,
630 );
631
632 // test add surrounds with multi cursor
633 cx.set_state(
634 indoc! {"
635 The quˇick brown
636 fox jumps over
637 the laˇzy dog."},
638 Mode::Normal,
639 );
640 cx.simulate_keystrokes("y s i w '");
641 cx.assert_state(
642 indoc! {"
643 The ˇ'quick' brown
644 fox jumps over
645 the ˇ'lazy' dog."},
646 Mode::Normal,
647 );
648
649 // test multi cursor add surrounds with motion
650 cx.set_state(
651 indoc! {"
652 The quˇick brown
653 fox jumps over
654 the laˇzy dog."},
655 Mode::Normal,
656 );
657 cx.simulate_keystrokes("y s $ '");
658 cx.assert_state(
659 indoc! {"
660 The quˇ'ick brown'
661 fox jumps over
662 the laˇ'zy dog.'"},
663 Mode::Normal,
664 );
665
666 // test multi cursor add surrounds with motion and custom string
667 cx.set_state(
668 indoc! {"
669 The quˇick brown
670 fox jumps over
671 the laˇzy dog."},
672 Mode::Normal,
673 );
674 cx.simulate_keystrokes("y s $ 1");
675 cx.assert_state(
676 indoc! {"
677 The quˇ1ick brown1
678 fox jumps over
679 the laˇ1zy dog.1"},
680 Mode::Normal,
681 );
682
683 // test add surrounds with motion current line
684 cx.set_state(
685 indoc! {"
686 The quˇick brown
687 fox jumps over
688 the lazy dog."},
689 Mode::Normal,
690 );
691 cx.simulate_keystrokes("y s s {");
692 cx.assert_state(
693 indoc! {"
694 ˇ{ The quick brown }
695 fox jumps over
696 the lazy dog."},
697 Mode::Normal,
698 );
699
700 cx.set_state(
701 indoc! {"
702 The quˇick brown•
703 fox jumps over
704 the lazy dog."},
705 Mode::Normal,
706 );
707 cx.simulate_keystrokes("y s s {");
708 cx.assert_state(
709 indoc! {"
710 ˇ{ The quick brown }•
711 fox jumps over
712 the lazy dog."},
713 Mode::Normal,
714 );
715 cx.simulate_keystrokes("2 y s s )");
716 cx.assert_state(
717 indoc! {"
718 ˇ({ The quick brown }•
719 fox jumps over)
720 the lazy dog."},
721 Mode::Normal,
722 );
723
724 // test add surrounds around object
725 cx.set_state(
726 indoc! {"
727 The [quˇick] brown
728 fox jumps over
729 the lazy dog."},
730 Mode::Normal,
731 );
732 cx.simulate_keystrokes("y s a ] )");
733 cx.assert_state(
734 indoc! {"
735 The ˇ([quick]) brown
736 fox jumps over
737 the lazy dog."},
738 Mode::Normal,
739 );
740
741 // test add surrounds inside object
742 cx.set_state(
743 indoc! {"
744 The [quˇick] brown
745 fox jumps over
746 the lazy dog."},
747 Mode::Normal,
748 );
749 cx.simulate_keystrokes("y s i ] )");
750 cx.assert_state(
751 indoc! {"
752 The [ˇ(quick)] brown
753 fox jumps over
754 the lazy dog."},
755 Mode::Normal,
756 );
757 }
758
759 #[gpui::test]
760 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
761 let mut cx = VimTestContext::new(cx, true).await;
762
763 cx.update(|_, cx| {
764 cx.bind_keys([KeyBinding::new(
765 "shift-s",
766 PushAddSurrounds {},
767 Some("vim_mode == visual"),
768 )])
769 });
770
771 // test add surrounds with around
772 cx.set_state(
773 indoc! {"
774 The quˇick brown
775 fox jumps over
776 the lazy dog."},
777 Mode::Normal,
778 );
779 cx.simulate_keystrokes("v i w shift-s {");
780 cx.assert_state(
781 indoc! {"
782 The ˇ{ quick } brown
783 fox jumps over
784 the lazy dog."},
785 Mode::Normal,
786 );
787
788 // test add surrounds not with around
789 cx.set_state(
790 indoc! {"
791 The quˇick brown
792 fox jumps over
793 the lazy dog."},
794 Mode::Normal,
795 );
796 cx.simulate_keystrokes("v i w shift-s }");
797 cx.assert_state(
798 indoc! {"
799 The ˇ{quick} brown
800 fox jumps over
801 the lazy dog."},
802 Mode::Normal,
803 );
804
805 // test add surrounds with motion
806 cx.set_state(
807 indoc! {"
808 The quˇick brown
809 fox jumps over
810 the lazy dog."},
811 Mode::Normal,
812 );
813 cx.simulate_keystrokes("v e shift-s }");
814 cx.assert_state(
815 indoc! {"
816 The quˇ{ick} brown
817 fox jumps over
818 the lazy dog."},
819 Mode::Normal,
820 );
821
822 // test add surrounds with multi cursor
823 cx.set_state(
824 indoc! {"
825 The quˇick brown
826 fox jumps over
827 the laˇzy dog."},
828 Mode::Normal,
829 );
830 cx.simulate_keystrokes("v i w shift-s '");
831 cx.assert_state(
832 indoc! {"
833 The ˇ'quick' brown
834 fox jumps over
835 the ˇ'lazy' dog."},
836 Mode::Normal,
837 );
838
839 // test add surrounds with visual block
840 cx.set_state(
841 indoc! {"
842 The quˇick brown
843 fox jumps over
844 the lazy dog."},
845 Mode::Normal,
846 );
847 cx.simulate_keystrokes("ctrl-v i w j j shift-s '");
848 cx.assert_state(
849 indoc! {"
850 The ˇ'quick' brown
851 fox 'jumps' over
852 the 'lazy 'dog."},
853 Mode::Normal,
854 );
855
856 // test add surrounds with visual line
857 cx.set_state(
858 indoc! {"
859 The quˇick brown
860 fox jumps over
861 the lazy dog."},
862 Mode::Normal,
863 );
864 cx.simulate_keystrokes("j shift-v shift-s '");
865 cx.assert_state(
866 indoc! {"
867 The quick brown
868 ˇ'
869 fox jumps over
870 '
871 the lazy dog."},
872 Mode::Normal,
873 );
874 }
875
876 #[gpui::test]
877 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
878 let mut cx = VimTestContext::new(cx, true).await;
879
880 // test delete surround
881 cx.set_state(
882 indoc! {"
883 The {quˇick} brown
884 fox jumps over
885 the lazy dog."},
886 Mode::Normal,
887 );
888 cx.simulate_keystrokes("d s {");
889 cx.assert_state(
890 indoc! {"
891 The ˇquick brown
892 fox jumps over
893 the lazy dog."},
894 Mode::Normal,
895 );
896
897 // test delete not exist surrounds
898 cx.set_state(
899 indoc! {"
900 The {quˇick} brown
901 fox jumps over
902 the lazy dog."},
903 Mode::Normal,
904 );
905 cx.simulate_keystrokes("d s [");
906 cx.assert_state(
907 indoc! {"
908 The {quˇick} brown
909 fox jumps over
910 the lazy dog."},
911 Mode::Normal,
912 );
913
914 // test delete surround forward exist, in the surrounds plugin of other editors,
915 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
916 cx.set_state(
917 indoc! {"
918 The {quick} brˇown
919 fox jumps over
920 the lazy dog."},
921 Mode::Normal,
922 );
923 cx.simulate_keystrokes("d s {");
924 cx.assert_state(
925 indoc! {"
926 The {quick} brˇown
927 fox jumps over
928 the lazy dog."},
929 Mode::Normal,
930 );
931
932 // test cursor delete inner surrounds
933 cx.set_state(
934 indoc! {"
935 The { quick brown
936 fox jumˇps over }
937 the lazy dog."},
938 Mode::Normal,
939 );
940 cx.simulate_keystrokes("d s {");
941 cx.assert_state(
942 indoc! {"
943 The ˇquick brown
944 fox jumps over
945 the lazy dog."},
946 Mode::Normal,
947 );
948
949 // test multi cursor delete surrounds
950 cx.set_state(
951 indoc! {"
952 The [quˇick] brown
953 fox jumps over
954 the [laˇzy] dog."},
955 Mode::Normal,
956 );
957 cx.simulate_keystrokes("d s ]");
958 cx.assert_state(
959 indoc! {"
960 The ˇquick brown
961 fox jumps over
962 the ˇlazy dog."},
963 Mode::Normal,
964 );
965
966 // test multi cursor delete surrounds with around
967 cx.set_state(
968 indoc! {"
969 Tˇhe [ quick ] brown
970 fox jumps over
971 the [laˇzy] dog."},
972 Mode::Normal,
973 );
974 cx.simulate_keystrokes("d s [");
975 cx.assert_state(
976 indoc! {"
977 The ˇquick brown
978 fox jumps over
979 the ˇlazy dog."},
980 Mode::Normal,
981 );
982
983 cx.set_state(
984 indoc! {"
985 Tˇhe [ quick ] brown
986 fox jumps over
987 the [laˇzy ] dog."},
988 Mode::Normal,
989 );
990 cx.simulate_keystrokes("d s [");
991 cx.assert_state(
992 indoc! {"
993 The ˇquick brown
994 fox jumps over
995 the ˇlazy dog."},
996 Mode::Normal,
997 );
998
999 // test multi cursor delete different surrounds
1000 // the pair corresponding to the two cursors is the same,
1001 // so they are combined into one cursor
1002 cx.set_state(
1003 indoc! {"
1004 The [quˇick] brown
1005 fox jumps over
1006 the {laˇzy} dog."},
1007 Mode::Normal,
1008 );
1009 cx.simulate_keystrokes("d s {");
1010 cx.assert_state(
1011 indoc! {"
1012 The [quick] brown
1013 fox jumps over
1014 the ˇlazy dog."},
1015 Mode::Normal,
1016 );
1017
1018 // test delete surround with multi cursor and nest surrounds
1019 cx.set_state(
1020 indoc! {"
1021 fn test_surround() {
1022 ifˇ 2 > 1 {
1023 ˇprintln!(\"it is fine\");
1024 };
1025 }"},
1026 Mode::Normal,
1027 );
1028 cx.simulate_keystrokes("d s }");
1029 cx.assert_state(
1030 indoc! {"
1031 fn test_surround() ˇ
1032 if 2 > 1 ˇ
1033 println!(\"it is fine\");
1034 ;
1035 "},
1036 Mode::Normal,
1037 );
1038 }
1039
1040 #[gpui::test]
1041 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
1042 let mut cx = VimTestContext::new(cx, true).await;
1043
1044 cx.set_state(
1045 indoc! {"
1046 The {quˇick} brown
1047 fox jumps over
1048 the lazy dog."},
1049 Mode::Normal,
1050 );
1051 cx.simulate_keystrokes("c s { [");
1052 cx.assert_state(
1053 indoc! {"
1054 The ˇ[ quick ] brown
1055 fox jumps over
1056 the lazy dog."},
1057 Mode::Normal,
1058 );
1059
1060 // test multi cursor change surrounds
1061 cx.set_state(
1062 indoc! {"
1063 The {quˇick} brown
1064 fox jumps over
1065 the {laˇzy} dog."},
1066 Mode::Normal,
1067 );
1068 cx.simulate_keystrokes("c s { [");
1069 cx.assert_state(
1070 indoc! {"
1071 The ˇ[ quick ] brown
1072 fox jumps over
1073 the ˇ[ lazy ] dog."},
1074 Mode::Normal,
1075 );
1076
1077 // test multi cursor delete different surrounds with after cursor
1078 cx.set_state(
1079 indoc! {"
1080 Thˇe {quick} brown
1081 fox jumps over
1082 the {laˇzy} dog."},
1083 Mode::Normal,
1084 );
1085 cx.simulate_keystrokes("c s { [");
1086 cx.assert_state(
1087 indoc! {"
1088 The ˇ[ quick ] brown
1089 fox jumps over
1090 the ˇ[ lazy ] dog."},
1091 Mode::Normal,
1092 );
1093
1094 // test multi cursor change surrount with not around
1095 cx.set_state(
1096 indoc! {"
1097 Thˇe { quick } brown
1098 fox jumps over
1099 the {laˇzy} dog."},
1100 Mode::Normal,
1101 );
1102 cx.simulate_keystrokes("c s { ]");
1103 cx.assert_state(
1104 indoc! {"
1105 The ˇ[quick] brown
1106 fox jumps over
1107 the ˇ[lazy] dog."},
1108 Mode::Normal,
1109 );
1110
1111 // test multi cursor change with not exist surround
1112 cx.set_state(
1113 indoc! {"
1114 The {quˇick} brown
1115 fox jumps over
1116 the [laˇzy] dog."},
1117 Mode::Normal,
1118 );
1119 cx.simulate_keystrokes("c s [ '");
1120 cx.assert_state(
1121 indoc! {"
1122 The {quick} brown
1123 fox jumps over
1124 the ˇ'lazy' dog."},
1125 Mode::Normal,
1126 );
1127
1128 // test change nesting surrounds
1129 cx.set_state(
1130 indoc! {"
1131 fn test_surround() {
1132 ifˇ 2 > 1 {
1133 ˇprintln!(\"it is fine\");
1134 }
1135 };"},
1136 Mode::Normal,
1137 );
1138 cx.simulate_keystrokes("c s { [");
1139 cx.assert_state(
1140 indoc! {"
1141 fn test_surround() ˇ[
1142 if 2 > 1 ˇ[
1143 println!(\"it is fine\");
1144 ]
1145 ];"},
1146 Mode::Normal,
1147 );
1148
1149 // test change quotes.
1150 cx.set_state(indoc! {"' ˇstr '"}, Mode::Normal);
1151 cx.simulate_keystrokes("c s ' \"");
1152 cx.assert_state(indoc! {"ˇ\" str \""}, Mode::Normal);
1153
1154 // test multi cursor change quotes
1155 cx.set_state(
1156 indoc! {"
1157 ' ˇstr '
1158 some example text here
1159 ˇ' str '
1160 "},
1161 Mode::Normal,
1162 );
1163 cx.simulate_keystrokes("c s ' \"");
1164 cx.assert_state(
1165 indoc! {"
1166 ˇ\" str \"
1167 some example text here
1168 ˇ\" str \"
1169 "},
1170 Mode::Normal,
1171 );
1172 }
1173
1174 #[gpui::test]
1175 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1176 let mut cx = VimTestContext::new(cx, true).await;
1177
1178 cx.set_state(
1179 indoc! {"
1180 The quˇick brown
1181 fox jumps over
1182 the lazy dog."},
1183 Mode::Normal,
1184 );
1185 cx.simulate_keystrokes("y s i w [");
1186 cx.assert_state(
1187 indoc! {"
1188 The ˇ[ quick ] brown
1189 fox jumps over
1190 the lazy dog."},
1191 Mode::Normal,
1192 );
1193
1194 cx.simulate_keystrokes("c s [ }");
1195 cx.assert_state(
1196 indoc! {"
1197 The ˇ{quick} brown
1198 fox jumps over
1199 the lazy dog."},
1200 Mode::Normal,
1201 );
1202
1203 cx.simulate_keystrokes("d s {");
1204 cx.assert_state(
1205 indoc! {"
1206 The ˇquick brown
1207 fox jumps over
1208 the lazy dog."},
1209 Mode::Normal,
1210 );
1211
1212 cx.simulate_keystrokes("u");
1213 cx.assert_state(
1214 indoc! {"
1215 The ˇ{quick} brown
1216 fox jumps over
1217 the lazy dog."},
1218 Mode::Normal,
1219 );
1220 }
1221
1222 #[gpui::test]
1223 async fn test_surround_aliases(cx: &mut gpui::TestAppContext) {
1224 let mut cx = VimTestContext::new(cx, true).await;
1225
1226 // add aliases
1227 cx.set_state(
1228 indoc! {"
1229 The quˇick brown
1230 fox jumps over
1231 the lazy dog."},
1232 Mode::Normal,
1233 );
1234 cx.simulate_keystrokes("y s i w b");
1235 cx.assert_state(
1236 indoc! {"
1237 The ˇ(quick) brown
1238 fox jumps over
1239 the lazy dog."},
1240 Mode::Normal,
1241 );
1242
1243 cx.set_state(
1244 indoc! {"
1245 The quˇick brown
1246 fox jumps over
1247 the lazy dog."},
1248 Mode::Normal,
1249 );
1250 cx.simulate_keystrokes("y s i w B");
1251 cx.assert_state(
1252 indoc! {"
1253 The ˇ{quick} brown
1254 fox jumps over
1255 the lazy dog."},
1256 Mode::Normal,
1257 );
1258
1259 cx.set_state(
1260 indoc! {"
1261 The quˇick brown
1262 fox jumps over
1263 the lazy dog."},
1264 Mode::Normal,
1265 );
1266 cx.simulate_keystrokes("y s i w a");
1267 cx.assert_state(
1268 indoc! {"
1269 The ˇ<quick> brown
1270 fox jumps over
1271 the lazy dog."},
1272 Mode::Normal,
1273 );
1274
1275 cx.set_state(
1276 indoc! {"
1277 The quˇick brown
1278 fox jumps over
1279 the lazy dog."},
1280 Mode::Normal,
1281 );
1282 cx.simulate_keystrokes("y s i w r");
1283 cx.assert_state(
1284 indoc! {"
1285 The ˇ[quick] brown
1286 fox jumps over
1287 the lazy dog."},
1288 Mode::Normal,
1289 );
1290
1291 // change aliases
1292 cx.set_state(
1293 indoc! {"
1294 The {quˇick} brown
1295 fox jumps over
1296 the lazy dog."},
1297 Mode::Normal,
1298 );
1299 cx.simulate_keystrokes("c s { b");
1300 cx.assert_state(
1301 indoc! {"
1302 The ˇ(quick) brown
1303 fox jumps over
1304 the lazy dog."},
1305 Mode::Normal,
1306 );
1307
1308 cx.set_state(
1309 indoc! {"
1310 The (quˇick) brown
1311 fox jumps over
1312 the lazy dog."},
1313 Mode::Normal,
1314 );
1315 cx.simulate_keystrokes("c s ( B");
1316 cx.assert_state(
1317 indoc! {"
1318 The ˇ{quick} brown
1319 fox jumps over
1320 the lazy dog."},
1321 Mode::Normal,
1322 );
1323
1324 cx.set_state(
1325 indoc! {"
1326 The (quˇick) brown
1327 fox jumps over
1328 the lazy dog."},
1329 Mode::Normal,
1330 );
1331 cx.simulate_keystrokes("c s ( a");
1332 cx.assert_state(
1333 indoc! {"
1334 The ˇ<quick> brown
1335 fox jumps over
1336 the lazy dog."},
1337 Mode::Normal,
1338 );
1339
1340 cx.set_state(
1341 indoc! {"
1342 The <quˇick> brown
1343 fox jumps over
1344 the lazy dog."},
1345 Mode::Normal,
1346 );
1347 cx.simulate_keystrokes("c s < b");
1348 cx.assert_state(
1349 indoc! {"
1350 The ˇ(quick) brown
1351 fox jumps over
1352 the lazy dog."},
1353 Mode::Normal,
1354 );
1355
1356 cx.set_state(
1357 indoc! {"
1358 The (quˇick) brown
1359 fox jumps over
1360 the lazy dog."},
1361 Mode::Normal,
1362 );
1363 cx.simulate_keystrokes("c s ( r");
1364 cx.assert_state(
1365 indoc! {"
1366 The ˇ[quick] brown
1367 fox jumps over
1368 the lazy dog."},
1369 Mode::Normal,
1370 );
1371
1372 cx.set_state(
1373 indoc! {"
1374 The [quˇick] brown
1375 fox jumps over
1376 the lazy dog."},
1377 Mode::Normal,
1378 );
1379 cx.simulate_keystrokes("c s [ b");
1380 cx.assert_state(
1381 indoc! {"
1382 The ˇ(quick) brown
1383 fox jumps over
1384 the lazy dog."},
1385 Mode::Normal,
1386 );
1387
1388 // delete alias
1389 cx.set_state(
1390 indoc! {"
1391 The {quˇick} brown
1392 fox jumps over
1393 the lazy dog."},
1394 Mode::Normal,
1395 );
1396 cx.simulate_keystrokes("d s B");
1397 cx.assert_state(
1398 indoc! {"
1399 The ˇquick brown
1400 fox jumps over
1401 the lazy dog."},
1402 Mode::Normal,
1403 );
1404
1405 cx.set_state(
1406 indoc! {"
1407 The (quˇick) brown
1408 fox jumps over
1409 the lazy dog."},
1410 Mode::Normal,
1411 );
1412 cx.simulate_keystrokes("d s b");
1413 cx.assert_state(
1414 indoc! {"
1415 The ˇquick brown
1416 fox jumps over
1417 the lazy dog."},
1418 Mode::Normal,
1419 );
1420
1421 cx.set_state(
1422 indoc! {"
1423 The [quˇick] brown
1424 fox jumps over
1425 the lazy dog."},
1426 Mode::Normal,
1427 );
1428 cx.simulate_keystrokes("d s r");
1429 cx.assert_state(
1430 indoc! {"
1431 The ˇquick brown
1432 fox jumps over
1433 the lazy dog."},
1434 Mode::Normal,
1435 );
1436
1437 cx.set_state(
1438 indoc! {"
1439 The <quˇick> brown
1440 fox jumps over
1441 the lazy dog."},
1442 Mode::Normal,
1443 );
1444 cx.simulate_keystrokes("d s a");
1445 cx.assert_state(
1446 indoc! {"
1447 The ˇquick brown
1448 fox jumps over
1449 the lazy dog."},
1450 Mode::Normal,
1451 );
1452 }
1453}