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