1use crate::{
2 motion::{self, Motion},
3 object::Object,
4 state::Mode,
5 Vim,
6};
7use editor::{movement, scroll::Autoscroll, Bias};
8use language::BracketPair;
9use serde::Deserialize;
10use std::sync::Arc;
11use ui::ViewContext;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum SurroundsType {
15 Motion(Motion),
16 Object(Object),
17 Selection,
18}
19
20// This exists so that we can have Deserialize on Operators, but not on Motions.
21impl<'de> Deserialize<'de> for SurroundsType {
22 fn deserialize<D>(_: D) -> Result<Self, D::Error>
23 where
24 D: serde::Deserializer<'de>,
25 {
26 Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
27 }
28}
29
30impl Vim {
31 pub fn add_surrounds(
32 &mut self,
33 text: Arc<str>,
34 target: SurroundsType,
35 cx: &mut ViewContext<Self>,
36 ) {
37 self.stop_recording(cx);
38 let count = self.take_count(cx);
39 let mode = self.mode;
40 self.update_editor(cx, |_, editor, cx| {
41 let text_layout_details = editor.text_layout_details(cx);
42 editor.transact(cx, |editor, cx| {
43 editor.set_clip_at_line_ends(false, cx);
44
45 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
46 Some(pair) => pair.clone(),
47 None => BracketPair {
48 start: text.to_string(),
49 end: text.to_string(),
50 close: true,
51 surround: true,
52 newline: false,
53 },
54 };
55 let surround = pair.end != *text;
56 let (display_map, display_selections) = editor.selections.all_adjusted_display(cx);
57 let mut edits = Vec::new();
58 let mut anchors = Vec::new();
59
60 for selection in &display_selections {
61 let range = match &target {
62 SurroundsType::Object(object) => {
63 object.range(&display_map, selection.clone(), false)
64 }
65 SurroundsType::Motion(motion) => {
66 let range = motion
67 .range(
68 &display_map,
69 selection.clone(),
70 count,
71 true,
72 &text_layout_details,
73 )
74 .map(|mut range| {
75 // The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
76 if let Motion::CurrentLine = motion {
77 range.start = motion::first_non_whitespace(
78 &display_map,
79 false,
80 range.start,
81 );
82 range.end = movement::saturating_right(
83 &display_map,
84 motion::last_non_whitespace(
85 &display_map,
86 movement::left(&display_map, range.end),
87 1,
88 ),
89 );
90 }
91 range
92 });
93 range
94 }
95 SurroundsType::Selection => Some(selection.range()),
96 };
97
98 if let Some(range) = range {
99 let start = range.start.to_offset(&display_map, Bias::Right);
100 let end = range.end.to_offset(&display_map, Bias::Left);
101 let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
102 (format!("{}\n", pair.start), format!("{}\n", pair.end))
103 } else {
104 let maybe_space = if surround { " " } else { "" };
105 (
106 format!("{}{}", pair.start, maybe_space),
107 format!("{}{}", maybe_space, pair.end),
108 )
109 };
110 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
111
112 edits.push((start..start, start_cursor_str));
113 edits.push((end..end, end_cursor_str));
114 anchors.push(start_anchor..start_anchor);
115 } else {
116 let start_anchor = display_map
117 .buffer_snapshot
118 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
119 anchors.push(start_anchor..start_anchor);
120 }
121 }
122
123 editor.buffer().update(cx, |buffer, cx| {
124 buffer.edit(edits, None, cx);
125 });
126 editor.set_clip_at_line_ends(true, cx);
127 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
128 if mode == Mode::VisualBlock {
129 s.select_anchor_ranges(anchors.into_iter().take(1))
130 } else {
131 s.select_anchor_ranges(anchors)
132 }
133 });
134 });
135 });
136 self.switch_mode(Mode::Normal, false, cx);
137 }
138
139 pub fn delete_surrounds(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
140 self.stop_recording(cx);
141
142 // only legitimate surrounds can be removed
143 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
144 Some(pair) => pair.clone(),
145 None => return,
146 };
147 let pair_object = match pair_to_object(&pair) {
148 Some(pair_object) => pair_object,
149 None => return,
150 };
151 let surround = pair.end != *text;
152
153 self.update_editor(cx, |_, editor, cx| {
154 editor.transact(cx, |editor, cx| {
155 editor.set_clip_at_line_ends(false, cx);
156
157 let (display_map, display_selections) = editor.selections.all_display(cx);
158 let mut edits = Vec::new();
159 let mut anchors = Vec::new();
160
161 for selection in &display_selections {
162 let start = selection.start.to_offset(&display_map, Bias::Left);
163 if let Some(range) = pair_object.range(&display_map, selection.clone(), true) {
164 // If the current parenthesis object is single-line,
165 // then we need to filter whether it is the current line or not
166 if !pair_object.is_multiline() {
167 let is_same_row = selection.start.row() == range.start.row()
168 && selection.end.row() == range.end.row();
169 if !is_same_row {
170 anchors.push(start..start);
171 continue;
172 }
173 }
174 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
175 // hello«ˇ "hello in a word" »again.
176 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
177 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
178 let mut chars_and_offset = display_map
179 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
180 .peekable();
181 while let Some((ch, offset)) = chars_and_offset.next() {
182 if ch.to_string() == pair.start {
183 let start = offset;
184 let mut end = start + 1;
185 if surround {
186 if let Some((next_ch, _)) = chars_and_offset.peek() {
187 if next_ch.eq(&' ') {
188 end += 1;
189 }
190 }
191 }
192 edits.push((start..end, ""));
193 anchors.push(start..start);
194 break;
195 }
196 }
197 let mut reverse_chars_and_offsets = display_map
198 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
199 .peekable();
200 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
201 if ch.to_string() == pair.end {
202 let mut start = offset;
203 let end = start + 1;
204 if surround {
205 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
206 if next_ch.eq(&' ') {
207 start -= 1;
208 }
209 }
210 }
211 edits.push((start..end, ""));
212 break;
213 }
214 }
215 } else {
216 anchors.push(start..start);
217 }
218 }
219
220 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
221 s.select_ranges(anchors);
222 });
223 edits.sort_by_key(|(range, _)| range.start);
224 editor.buffer().update(cx, |buffer, cx| {
225 buffer.edit(edits, None, cx);
226 });
227 editor.set_clip_at_line_ends(true, cx);
228 });
229 });
230 }
231
232 pub fn change_surrounds(&mut self, text: Arc<str>, target: Object, cx: &mut ViewContext<Self>) {
233 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
234 self.stop_recording(cx);
235 self.update_editor(cx, |_, editor, cx| {
236 editor.transact(cx, |editor, cx| {
237 editor.set_clip_at_line_ends(false, cx);
238
239 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
240 Some(pair) => pair.clone(),
241 None => BracketPair {
242 start: text.to_string(),
243 end: text.to_string(),
244 close: true,
245 surround: true,
246 newline: false,
247 },
248 };
249 let surround = pair.end != *text;
250 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
251 let mut edits = Vec::new();
252 let mut anchors = Vec::new();
253
254 for selection in &selections {
255 let start = selection.start.to_offset(&display_map, Bias::Left);
256 if let Some(range) = target.range(&display_map, selection.clone(), true) {
257 if !target.is_multiline() {
258 let is_same_row = selection.start.row() == range.start.row()
259 && selection.end.row() == range.end.row();
260 if !is_same_row {
261 anchors.push(start..start);
262 continue;
263 }
264 }
265 let mut chars_and_offset = display_map
266 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
267 .peekable();
268 while let Some((ch, offset)) = chars_and_offset.next() {
269 if ch.to_string() == will_replace_pair.start {
270 let mut open_str = pair.start.clone();
271 let start = offset;
272 let mut end = start + 1;
273 match chars_and_offset.peek() {
274 Some((next_ch, _)) => {
275 // If the next position is already a space or line break,
276 // we don't need to splice another space even under around
277 if surround && !next_ch.is_whitespace() {
278 open_str.push_str(" ");
279 } else if !surround && next_ch.to_string() == " " {
280 end += 1;
281 }
282 }
283 None => {}
284 }
285 edits.push((start..end, open_str));
286 anchors.push(start..start);
287 break;
288 }
289 }
290
291 let mut reverse_chars_and_offsets = display_map
292 .reverse_buffer_chars_at(
293 range.end.to_offset(&display_map, Bias::Left),
294 )
295 .peekable();
296 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
297 if ch.to_string() == will_replace_pair.end {
298 let mut close_str = pair.end.clone();
299 let mut start = offset;
300 let end = start + 1;
301 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
302 if surround && !next_ch.is_whitespace() {
303 close_str.insert_str(0, " ")
304 } else if !surround && next_ch.to_string() == " " {
305 start -= 1;
306 }
307 }
308 edits.push((start..end, close_str));
309 break;
310 }
311 }
312 } else {
313 anchors.push(start..start);
314 }
315 }
316
317 let stable_anchors = editor
318 .selections
319 .disjoint_anchors()
320 .into_iter()
321 .map(|selection| {
322 let start = selection.start.bias_left(&display_map.buffer_snapshot);
323 start..start
324 })
325 .collect::<Vec<_>>();
326 edits.sort_by_key(|(range, _)| range.start);
327 editor.buffer().update(cx, |buffer, cx| {
328 buffer.edit(edits, None, cx);
329 });
330 editor.set_clip_at_line_ends(true, cx);
331 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
332 s.select_anchor_ranges(stable_anchors);
333 });
334 });
335 });
336 }
337 }
338
339 /// Checks if any of the current cursors are surrounded by a valid pair of brackets.
340 ///
341 /// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
342 /// A pair of brackets is considered valid if it is well-formed and properly closed.
343 ///
344 /// 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.
345 /// If no valid pair of brackets is found for any cursor, the method returns `false`.
346 pub fn check_and_move_to_valid_bracket_pair(
347 &mut self,
348 object: Object,
349 cx: &mut ViewContext<Self>,
350 ) -> bool {
351 let mut valid = false;
352 if let Some(pair) = object_to_bracket_pair(object) {
353 self.update_editor(cx, |_, editor, cx| {
354 editor.transact(cx, |editor, cx| {
355 editor.set_clip_at_line_ends(false, cx);
356 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
357 let mut anchors = Vec::new();
358
359 for selection in &selections {
360 let start = selection.start.to_offset(&display_map, Bias::Left);
361 if let Some(range) = object.range(&display_map, selection.clone(), true) {
362 // If the current parenthesis object is single-line,
363 // then we need to filter whether it is the current line or not
364 if object.is_multiline()
365 || (!object.is_multiline()
366 && selection.start.row() == range.start.row()
367 && selection.end.row() == range.end.row())
368 {
369 valid = true;
370 let mut chars_and_offset = display_map
371 .buffer_chars_at(
372 range.start.to_offset(&display_map, Bias::Left),
373 )
374 .peekable();
375 while let Some((ch, offset)) = chars_and_offset.next() {
376 if ch.to_string() == pair.start {
377 anchors.push(offset..offset);
378 break;
379 }
380 }
381 } else {
382 anchors.push(start..start)
383 }
384 } else {
385 anchors.push(start..start)
386 }
387 }
388 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
389 s.select_ranges(anchors);
390 });
391 editor.set_clip_at_line_ends(true, cx);
392 });
393 });
394 }
395 return valid;
396 }
397}
398
399fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
400 pairs.iter().find(|pair| pair.start == ch || pair.end == ch)
401}
402
403fn all_support_surround_pair() -> Vec<BracketPair> {
404 return vec![
405 BracketPair {
406 start: "{".into(),
407 end: "}".into(),
408 close: true,
409 surround: true,
410 newline: false,
411 },
412 BracketPair {
413 start: "'".into(),
414 end: "'".into(),
415 close: true,
416 surround: true,
417 newline: false,
418 },
419 BracketPair {
420 start: "`".into(),
421 end: "`".into(),
422 close: true,
423 surround: true,
424 newline: false,
425 },
426 BracketPair {
427 start: "\"".into(),
428 end: "\"".into(),
429 close: true,
430 surround: true,
431 newline: false,
432 },
433 BracketPair {
434 start: "(".into(),
435 end: ")".into(),
436 close: true,
437 surround: true,
438 newline: false,
439 },
440 BracketPair {
441 start: "|".into(),
442 end: "|".into(),
443 close: true,
444 surround: true,
445 newline: false,
446 },
447 BracketPair {
448 start: "[".into(),
449 end: "]".into(),
450 close: true,
451 surround: true,
452 newline: false,
453 },
454 BracketPair {
455 start: "{".into(),
456 end: "}".into(),
457 close: true,
458 surround: true,
459 newline: false,
460 },
461 BracketPair {
462 start: "<".into(),
463 end: ">".into(),
464 close: true,
465 surround: true,
466 newline: false,
467 },
468 ];
469}
470
471fn pair_to_object(pair: &BracketPair) -> Option<Object> {
472 match pair.start.as_str() {
473 "'" => Some(Object::Quotes),
474 "`" => Some(Object::BackQuotes),
475 "\"" => Some(Object::DoubleQuotes),
476 "|" => Some(Object::VerticalBars),
477 "(" => Some(Object::Parentheses),
478 "[" => Some(Object::SquareBrackets),
479 "{" => Some(Object::CurlyBrackets),
480 "<" => Some(Object::AngleBrackets),
481 _ => None,
482 }
483}
484
485fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
486 match object {
487 Object::Quotes => Some(BracketPair {
488 start: "'".to_string(),
489 end: "'".to_string(),
490 close: true,
491 surround: true,
492 newline: false,
493 }),
494 Object::BackQuotes => Some(BracketPair {
495 start: "`".to_string(),
496 end: "`".to_string(),
497 close: true,
498 surround: true,
499 newline: false,
500 }),
501 Object::DoubleQuotes => Some(BracketPair {
502 start: "\"".to_string(),
503 end: "\"".to_string(),
504 close: true,
505 surround: true,
506 newline: false,
507 }),
508 Object::VerticalBars => Some(BracketPair {
509 start: "|".to_string(),
510 end: "|".to_string(),
511 close: true,
512 surround: true,
513 newline: false,
514 }),
515 Object::Parentheses => Some(BracketPair {
516 start: "(".to_string(),
517 end: ")".to_string(),
518 close: true,
519 surround: true,
520 newline: false,
521 }),
522 Object::SquareBrackets => Some(BracketPair {
523 start: "[".to_string(),
524 end: "]".to_string(),
525 close: true,
526 surround: true,
527 newline: false,
528 }),
529 Object::CurlyBrackets => Some(BracketPair {
530 start: "{".to_string(),
531 end: "}".to_string(),
532 close: true,
533 surround: true,
534 newline: false,
535 }),
536 Object::AngleBrackets => Some(BracketPair {
537 start: "<".to_string(),
538 end: ">".to_string(),
539 close: true,
540 surround: true,
541 newline: false,
542 }),
543 _ => None,
544 }
545}
546
547#[cfg(test)]
548mod test {
549 use gpui::KeyBinding;
550 use indoc::indoc;
551
552 use crate::{
553 state::{Mode, Operator},
554 test::VimTestContext,
555 PushOperator,
556 };
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
706 #[gpui::test]
707 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
708 let mut cx = VimTestContext::new(cx, true).await;
709
710 cx.update(|cx| {
711 cx.bind_keys([KeyBinding::new(
712 "shift-s",
713 PushOperator(Operator::AddSurrounds { target: None }),
714 Some("vim_mode == visual"),
715 )])
716 });
717
718 // test add surrounds with around
719 cx.set_state(
720 indoc! {"
721 The quˇick brown
722 fox jumps over
723 the lazy dog."},
724 Mode::Normal,
725 );
726 cx.simulate_keystrokes("v i w shift-s {");
727 cx.assert_state(
728 indoc! {"
729 The ˇ{ quick } brown
730 fox jumps over
731 the lazy dog."},
732 Mode::Normal,
733 );
734
735 // test add surrounds not with around
736 cx.set_state(
737 indoc! {"
738 The quˇick brown
739 fox jumps over
740 the lazy dog."},
741 Mode::Normal,
742 );
743 cx.simulate_keystrokes("v i w shift-s }");
744 cx.assert_state(
745 indoc! {"
746 The ˇ{quick} brown
747 fox jumps over
748 the lazy dog."},
749 Mode::Normal,
750 );
751
752 // test add surrounds with motion
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 e shift-s }");
761 cx.assert_state(
762 indoc! {"
763 The quˇ{ick} brown
764 fox jumps over
765 the lazy dog."},
766 Mode::Normal,
767 );
768
769 // test add surrounds with multi cursor
770 cx.set_state(
771 indoc! {"
772 The quˇick brown
773 fox jumps over
774 the laˇzy 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 visual block
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("ctrl-v i w j j shift-s '");
795 cx.assert_state(
796 indoc! {"
797 The ˇ'quick' brown
798 fox 'jumps' over
799 the 'lazy 'dog."},
800 Mode::Normal,
801 );
802
803 // test add surrounds with visual line
804 cx.set_state(
805 indoc! {"
806 The quˇick brown
807 fox jumps over
808 the lazy dog."},
809 Mode::Normal,
810 );
811 cx.simulate_keystrokes("j shift-v shift-s '");
812 cx.assert_state(
813 indoc! {"
814 The quick brown
815 ˇ'
816 fox jumps over
817 '
818 the lazy dog."},
819 Mode::Normal,
820 );
821 }
822
823 #[gpui::test]
824 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
825 let mut cx = VimTestContext::new(cx, true).await;
826
827 // test delete surround
828 cx.set_state(
829 indoc! {"
830 The {quˇick} brown
831 fox jumps over
832 the lazy dog."},
833 Mode::Normal,
834 );
835 cx.simulate_keystrokes("d s {");
836 cx.assert_state(
837 indoc! {"
838 The ˇquick brown
839 fox jumps over
840 the lazy dog."},
841 Mode::Normal,
842 );
843
844 // test delete not exist surrounds
845 cx.set_state(
846 indoc! {"
847 The {quˇick} brown
848 fox jumps over
849 the lazy dog."},
850 Mode::Normal,
851 );
852 cx.simulate_keystrokes("d s [");
853 cx.assert_state(
854 indoc! {"
855 The {quˇick} brown
856 fox jumps over
857 the lazy dog."},
858 Mode::Normal,
859 );
860
861 // test delete surround forward exist, in the surrounds plugin of other editors,
862 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
863 cx.set_state(
864 indoc! {"
865 The {quick} brˇown
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} brˇown
874 fox jumps over
875 the lazy dog."},
876 Mode::Normal,
877 );
878
879 // test cursor delete inner surrounds
880 cx.set_state(
881 indoc! {"
882 The { quick brown
883 fox jumˇps over }
884 the lazy dog."},
885 Mode::Normal,
886 );
887 cx.simulate_keystrokes("d s {");
888 cx.assert_state(
889 indoc! {"
890 The ˇquick brown
891 fox jumps over
892 the lazy dog."},
893 Mode::Normal,
894 );
895
896 // test multi cursor delete surrounds
897 cx.set_state(
898 indoc! {"
899 The [quˇick] brown
900 fox jumps over
901 the [laˇzy] dog."},
902 Mode::Normal,
903 );
904 cx.simulate_keystrokes("d s ]");
905 cx.assert_state(
906 indoc! {"
907 The ˇquick brown
908 fox jumps over
909 the ˇlazy dog."},
910 Mode::Normal,
911 );
912
913 // test multi cursor delete surrounds with around
914 cx.set_state(
915 indoc! {"
916 Tˇhe [ quick ] brown
917 fox jumps over
918 the [laˇzy] 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 cx.set_state(
931 indoc! {"
932 Tˇhe [ quick ] brown
933 fox jumps over
934 the [laˇzy ] dog."},
935 Mode::Normal,
936 );
937 cx.simulate_keystrokes("d s [");
938 cx.assert_state(
939 indoc! {"
940 The ˇquick brown
941 fox jumps over
942 the ˇlazy dog."},
943 Mode::Normal,
944 );
945
946 // test multi cursor delete different surrounds
947 // the pair corresponding to the two cursors is the same,
948 // so they are combined into one cursor
949 cx.set_state(
950 indoc! {"
951 The [quˇick] 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 // test delete surround with multi cursor and nest surrounds
966 cx.set_state(
967 indoc! {"
968 fn test_surround() {
969 ifˇ 2 > 1 {
970 ˇprintln!(\"it is fine\");
971 };
972 }"},
973 Mode::Normal,
974 );
975 cx.simulate_keystrokes("d s }");
976 cx.assert_state(
977 indoc! {"
978 fn test_surround() ˇ
979 if 2 > 1 ˇ
980 println!(\"it is fine\");
981 ;
982 "},
983 Mode::Normal,
984 );
985 }
986
987 #[gpui::test]
988 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
989 let mut cx = VimTestContext::new(cx, true).await;
990
991 cx.set_state(
992 indoc! {"
993 The {quˇick} brown
994 fox jumps over
995 the lazy dog."},
996 Mode::Normal,
997 );
998 cx.simulate_keystrokes("c s { [");
999 cx.assert_state(
1000 indoc! {"
1001 The ˇ[ quick ] brown
1002 fox jumps over
1003 the lazy dog."},
1004 Mode::Normal,
1005 );
1006
1007 // test multi cursor change surrounds
1008 cx.set_state(
1009 indoc! {"
1010 The {quˇick} brown
1011 fox jumps over
1012 the {laˇzy} dog."},
1013 Mode::Normal,
1014 );
1015 cx.simulate_keystrokes("c s { [");
1016 cx.assert_state(
1017 indoc! {"
1018 The ˇ[ quick ] brown
1019 fox jumps over
1020 the ˇ[ lazy ] dog."},
1021 Mode::Normal,
1022 );
1023
1024 // test multi cursor delete different surrounds with after cursor
1025 cx.set_state(
1026 indoc! {"
1027 Thˇe {quick} brown
1028 fox jumps over
1029 the {laˇzy} 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 surrount with not around
1042 cx.set_state(
1043 indoc! {"
1044 Thˇe { quick } 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 change with not exist surround
1059 cx.set_state(
1060 indoc! {"
1061 The {quˇick} 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 change nesting surrounds
1076 cx.set_state(
1077 indoc! {"
1078 fn test_surround() {
1079 ifˇ 2 > 1 {
1080 ˇprintln!(\"it is fine\");
1081 }
1082 };"},
1083 Mode::Normal,
1084 );
1085 cx.simulate_keystrokes("c s { [");
1086 cx.assert_state(
1087 indoc! {"
1088 fn test_surround() ˇ[
1089 if 2 > 1 ˇ[
1090 println!(\"it is fine\");
1091 ]
1092 ];"},
1093 Mode::Normal,
1094 );
1095 }
1096
1097 #[gpui::test]
1098 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1099 let mut cx = VimTestContext::new(cx, true).await;
1100
1101 cx.set_state(
1102 indoc! {"
1103 The quˇick brown
1104 fox jumps over
1105 the lazy dog."},
1106 Mode::Normal,
1107 );
1108 cx.simulate_keystrokes("y s i w [");
1109 cx.assert_state(
1110 indoc! {"
1111 The ˇ[ quick ] brown
1112 fox jumps over
1113 the lazy dog."},
1114 Mode::Normal,
1115 );
1116
1117 cx.simulate_keystrokes("c s [ }");
1118 cx.assert_state(
1119 indoc! {"
1120 The ˇ{quick} brown
1121 fox jumps over
1122 the lazy dog."},
1123 Mode::Normal,
1124 );
1125
1126 cx.simulate_keystrokes("d s {");
1127 cx.assert_state(
1128 indoc! {"
1129 The ˇquick brown
1130 fox jumps over
1131 the lazy dog."},
1132 Mode::Normal,
1133 );
1134
1135 cx.simulate_keystrokes("u");
1136 cx.assert_state(
1137 indoc! {"
1138 The ˇ{quick} brown
1139 fox jumps over
1140 the lazy dog."},
1141 Mode::Normal,
1142 );
1143 }
1144}