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