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