1use crate::{
2 motion::{self, Motion},
3 object::Object,
4 state::Mode,
5 Vim,
6};
7use editor::{movement, scroll::Autoscroll, Bias};
8use gpui::WindowContext;
9use language::BracketPair;
10use serde::Deserialize;
11use std::sync::Arc;
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum SurroundsType {
14 Motion(Motion),
15 Object(Object),
16}
17
18// This exists so that we can have Deserialize on Operators, but not on Motions.
19impl<'de> Deserialize<'de> for SurroundsType {
20 fn deserialize<D>(_: D) -> Result<Self, D::Error>
21 where
22 D: serde::Deserializer<'de>,
23 {
24 Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
25 }
26}
27
28pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
29 Vim::update(cx, |vim, cx| {
30 vim.stop_recording();
31 let count = vim.take_count(cx);
32 vim.update_active_editor(cx, |_, editor, cx| {
33 let text_layout_details = editor.text_layout_details(cx);
34 editor.transact(cx, |editor, 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 != *text;
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) => {
55 object.range(&display_map, selection.clone(), false)
56 }
57 SurroundsType::Motion(motion) => {
58 let range = motion
59 .range(
60 &display_map,
61 selection.clone(),
62 count,
63 true,
64 &text_layout_details,
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(
77 &display_map,
78 movement::left(&display_map, range.end),
79 1,
80 ),
81 );
82 }
83 range
84 });
85 range
86 }
87 };
88
89 if let Some(range) = range {
90 let start = range.start.to_offset(&display_map, Bias::Right);
91 let end = range.end.to_offset(&display_map, Bias::Left);
92 let start_cursor_str =
93 format!("{}{}", pair.start, if surround { " " } else { "" });
94 let close_cursor_str =
95 format!("{}{}", if surround { " " } else { "" }, pair.end);
96 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
97
98 edits.push((start..start, start_cursor_str));
99 edits.push((end..end, close_cursor_str));
100 anchors.push(start_anchor..start_anchor);
101 } else {
102 let start_anchor = display_map
103 .buffer_snapshot
104 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
105 anchors.push(start_anchor..start_anchor);
106 }
107 }
108
109 editor.buffer().update(cx, |buffer, cx| {
110 buffer.edit(edits, None, cx);
111 });
112 editor.set_clip_at_line_ends(true, cx);
113 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
114 s.select_anchor_ranges(anchors)
115 });
116 });
117 });
118 vim.switch_mode(Mode::Normal, false, cx);
119 });
120}
121
122pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
123 Vim::update(cx, |vim, cx| {
124 vim.stop_recording();
125
126 // only legitimate surrounds can be removed
127 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
128 Some(pair) => pair.clone(),
129 None => return,
130 };
131 let pair_object = match pair_to_object(&pair) {
132 Some(pair_object) => pair_object,
133 None => return,
134 };
135 let surround = pair.end != *text;
136
137 vim.update_active_editor(cx, |_, editor, cx| {
138 editor.transact(cx, |editor, cx| {
139 editor.set_clip_at_line_ends(false, cx);
140
141 let (display_map, display_selections) = editor.selections.all_display(cx);
142 let mut edits = Vec::new();
143 let mut anchors = Vec::new();
144
145 for selection in &display_selections {
146 let start = selection.start.to_offset(&display_map, Bias::Left);
147 if let Some(range) = pair_object.range(&display_map, selection.clone(), true) {
148 // If the current parenthesis object is single-line,
149 // then we need to filter whether it is the current line or not
150 if !pair_object.is_multiline() {
151 let is_same_row = selection.start.row() == range.start.row()
152 && selection.end.row() == range.end.row();
153 if !is_same_row {
154 anchors.push(start..start);
155 continue;
156 }
157 }
158 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
159 // hello«ˇ "hello in a word" »again.
160 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
161 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
162 let mut chars_and_offset = display_map
163 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
164 .peekable();
165 while let Some((ch, offset)) = chars_and_offset.next() {
166 if ch.to_string() == pair.start {
167 let start = offset;
168 let mut end = start + 1;
169 if surround {
170 if let Some((next_ch, _)) = chars_and_offset.peek() {
171 if next_ch.eq(&' ') {
172 end += 1;
173 }
174 }
175 }
176 edits.push((start..end, ""));
177 anchors.push(start..start);
178 break;
179 }
180 }
181 let mut reverse_chars_and_offsets = display_map
182 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
183 .peekable();
184 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
185 if ch.to_string() == pair.end {
186 let mut start = offset;
187 let end = start + 1;
188 if surround {
189 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
190 if next_ch.eq(&' ') {
191 start -= 1;
192 }
193 }
194 }
195 edits.push((start..end, ""));
196 break;
197 }
198 }
199 } else {
200 anchors.push(start..start);
201 }
202 }
203
204 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
205 s.select_ranges(anchors);
206 });
207 edits.sort_by_key(|(range, _)| range.start);
208 editor.buffer().update(cx, |buffer, cx| {
209 buffer.edit(edits, None, cx);
210 });
211 editor.set_clip_at_line_ends(true, cx);
212 });
213 });
214 });
215}
216
217pub fn change_surrounds(text: Arc<str>, target: Object, cx: &mut WindowContext) {
218 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
219 Vim::update(cx, |vim, cx| {
220 vim.stop_recording();
221 vim.update_active_editor(cx, |_, editor, cx| {
222 editor.transact(cx, |editor, cx| {
223 editor.set_clip_at_line_ends(false, cx);
224
225 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
226 Some(pair) => pair.clone(),
227 None => BracketPair {
228 start: text.to_string(),
229 end: text.to_string(),
230 close: true,
231 surround: true,
232 newline: false,
233 },
234 };
235 let surround = pair.end != *text;
236 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
237 let mut edits = Vec::new();
238 let mut anchors = Vec::new();
239
240 for selection in &selections {
241 let start = selection.start.to_offset(&display_map, Bias::Left);
242 if let Some(range) = target.range(&display_map, selection.clone(), true) {
243 if !target.is_multiline() {
244 let is_same_row = selection.start.row() == range.start.row()
245 && selection.end.row() == range.end.row();
246 if !is_same_row {
247 anchors.push(start..start);
248 continue;
249 }
250 }
251 let mut chars_and_offset = display_map
252 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
253 .peekable();
254 while let Some((ch, offset)) = chars_and_offset.next() {
255 if ch.to_string() == will_replace_pair.start {
256 let mut open_str = pair.start.clone();
257 let start = offset;
258 let mut end = start + 1;
259 match chars_and_offset.peek() {
260 Some((next_ch, _)) => {
261 // If the next position is already a space or line break,
262 // we don't need to splice another space even under arround
263 if surround && !next_ch.is_whitespace() {
264 open_str.push_str(" ");
265 } else if !surround && next_ch.to_string() == " " {
266 end += 1;
267 }
268 }
269 None => {}
270 }
271 edits.push((start..end, open_str));
272 anchors.push(start..start);
273 break;
274 }
275 }
276
277 let mut reverse_chars_and_offsets = display_map
278 .reverse_buffer_chars_at(
279 range.end.to_offset(&display_map, Bias::Left),
280 )
281 .peekable();
282 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
283 if ch.to_string() == will_replace_pair.end {
284 let mut close_str = pair.end.clone();
285 let mut start = offset;
286 let end = start + 1;
287 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
288 if surround && !next_ch.is_whitespace() {
289 close_str.insert_str(0, " ")
290 } else if !surround && next_ch.to_string() == " " {
291 start -= 1;
292 }
293 }
294 edits.push((start..end, close_str));
295 break;
296 }
297 }
298 } else {
299 anchors.push(start..start);
300 }
301 }
302
303 let stable_anchors = editor
304 .selections
305 .disjoint_anchors()
306 .into_iter()
307 .map(|selection| {
308 let start = selection.start.bias_left(&display_map.buffer_snapshot);
309 start..start
310 })
311 .collect::<Vec<_>>();
312 edits.sort_by_key(|(range, _)| range.start);
313 editor.buffer().update(cx, |buffer, cx| {
314 buffer.edit(edits, None, cx);
315 });
316 editor.set_clip_at_line_ends(true, cx);
317 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
318 s.select_anchor_ranges(stable_anchors);
319 });
320 });
321 });
322 });
323 }
324}
325
326/// Checks if any of the current cursors are surrounded by a valid pair of brackets.
327///
328/// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
329/// A pair of brackets is considered valid if it is well-formed and properly closed.
330///
331/// 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.
332/// If no valid pair of brackets is found for any cursor, the method returns `false`.
333pub fn check_and_move_to_valid_bracket_pair(
334 vim: &mut Vim,
335 object: Object,
336 cx: &mut WindowContext,
337) -> bool {
338 let mut valid = false;
339 if let Some(pair) = object_to_bracket_pair(object) {
340 vim.update_active_editor(cx, |_, editor, cx| {
341 editor.transact(cx, |editor, cx| {
342 editor.set_clip_at_line_ends(false, cx);
343 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
344 let mut anchors = Vec::new();
345
346 for selection in &selections {
347 let start = selection.start.to_offset(&display_map, Bias::Left);
348 if let Some(range) = object.range(&display_map, selection.clone(), true) {
349 // If the current parenthesis object is single-line,
350 // then we need to filter whether it is the current line or not
351 if object.is_multiline()
352 || (!object.is_multiline()
353 && selection.start.row() == range.start.row()
354 && selection.end.row() == range.end.row())
355 {
356 valid = true;
357 let mut chars_and_offset = display_map
358 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
359 .peekable();
360 while let Some((ch, offset)) = chars_and_offset.next() {
361 if ch.to_string() == pair.start {
362 anchors.push(offset..offset);
363 break;
364 }
365 }
366 } else {
367 anchors.push(start..start)
368 }
369 } else {
370 anchors.push(start..start)
371 }
372 }
373 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
374 s.select_ranges(anchors);
375 });
376 editor.set_clip_at_line_ends(true, cx);
377 });
378 });
379 }
380 return valid;
381}
382
383fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
384 pairs.iter().find(|pair| pair.start == ch || pair.end == ch)
385}
386
387fn all_support_surround_pair() -> Vec<BracketPair> {
388 return vec![
389 BracketPair {
390 start: "{".into(),
391 end: "}".into(),
392 close: true,
393 surround: true,
394 newline: false,
395 },
396 BracketPair {
397 start: "'".into(),
398 end: "'".into(),
399 close: true,
400 surround: true,
401 newline: false,
402 },
403 BracketPair {
404 start: "`".into(),
405 end: "`".into(),
406 close: true,
407 surround: true,
408 newline: false,
409 },
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 ];
453}
454
455fn pair_to_object(pair: &BracketPair) -> Option<Object> {
456 match pair.start.as_str() {
457 "'" => Some(Object::Quotes),
458 "`" => Some(Object::BackQuotes),
459 "\"" => Some(Object::DoubleQuotes),
460 "|" => Some(Object::VerticalBars),
461 "(" => Some(Object::Parentheses),
462 "[" => Some(Object::SquareBrackets),
463 "{" => Some(Object::CurlyBrackets),
464 "<" => Some(Object::AngleBrackets),
465 _ => None,
466 }
467}
468
469fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
470 match object {
471 Object::Quotes => Some(BracketPair {
472 start: "'".to_string(),
473 end: "'".to_string(),
474 close: true,
475 surround: true,
476 newline: false,
477 }),
478 Object::BackQuotes => Some(BracketPair {
479 start: "`".to_string(),
480 end: "`".to_string(),
481 close: true,
482 surround: true,
483 newline: false,
484 }),
485 Object::DoubleQuotes => Some(BracketPair {
486 start: "\"".to_string(),
487 end: "\"".to_string(),
488 close: true,
489 surround: true,
490 newline: false,
491 }),
492 Object::VerticalBars => Some(BracketPair {
493 start: "|".to_string(),
494 end: "|".to_string(),
495 close: true,
496 surround: true,
497 newline: false,
498 }),
499 Object::Parentheses => Some(BracketPair {
500 start: "(".to_string(),
501 end: ")".to_string(),
502 close: true,
503 surround: true,
504 newline: false,
505 }),
506 Object::SquareBrackets => Some(BracketPair {
507 start: "[".to_string(),
508 end: "]".to_string(),
509 close: true,
510 surround: true,
511 newline: false,
512 }),
513 Object::CurlyBrackets => Some(BracketPair {
514 start: "{".to_string(),
515 end: "}".to_string(),
516 close: true,
517 surround: true,
518 newline: false,
519 }),
520 Object::AngleBrackets => Some(BracketPair {
521 start: "<".to_string(),
522 end: ">".to_string(),
523 close: true,
524 surround: true,
525 newline: false,
526 }),
527 _ => None,
528 }
529}
530
531#[cfg(test)]
532mod test {
533 use indoc::indoc;
534
535 use crate::{state::Mode, test::VimTestContext};
536
537 #[gpui::test]
538 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
539 let mut cx = VimTestContext::new(cx, true).await;
540
541 // test add surrounds with arround
542 cx.set_state(
543 indoc! {"
544 The quˇick brown
545 fox jumps over
546 the lazy dog."},
547 Mode::Normal,
548 );
549 cx.simulate_keystrokes("y s i w {");
550 cx.assert_state(
551 indoc! {"
552 The ˇ{ quick } brown
553 fox jumps over
554 the lazy dog."},
555 Mode::Normal,
556 );
557
558 // test add surrounds not with arround
559 cx.set_state(
560 indoc! {"
561 The quˇick brown
562 fox jumps over
563 the lazy dog."},
564 Mode::Normal,
565 );
566 cx.simulate_keystrokes("y s i w }");
567 cx.assert_state(
568 indoc! {"
569 The ˇ{quick} brown
570 fox jumps over
571 the lazy dog."},
572 Mode::Normal,
573 );
574
575 // test add surrounds with motion
576 cx.set_state(
577 indoc! {"
578 The quˇick brown
579 fox jumps over
580 the lazy dog."},
581 Mode::Normal,
582 );
583 cx.simulate_keystrokes("y s $ }");
584 cx.assert_state(
585 indoc! {"
586 The quˇ{ick brown}
587 fox jumps over
588 the lazy dog."},
589 Mode::Normal,
590 );
591
592 // test add surrounds with multi cursor
593 cx.set_state(
594 indoc! {"
595 The quˇick brown
596 fox jumps over
597 the laˇzy dog."},
598 Mode::Normal,
599 );
600 cx.simulate_keystrokes("y s i w '");
601 cx.assert_state(
602 indoc! {"
603 The ˇ'quick' brown
604 fox jumps over
605 the ˇ'lazy' dog."},
606 Mode::Normal,
607 );
608
609 // test multi cursor add surrounds with motion
610 cx.set_state(
611 indoc! {"
612 The quˇick brown
613 fox jumps over
614 the laˇzy dog."},
615 Mode::Normal,
616 );
617 cx.simulate_keystrokes("y s $ '");
618 cx.assert_state(
619 indoc! {"
620 The quˇ'ick brown'
621 fox jumps over
622 the laˇ'zy dog.'"},
623 Mode::Normal,
624 );
625
626 // test multi cursor add surrounds with motion and custom string
627 cx.set_state(
628 indoc! {"
629 The quˇick brown
630 fox jumps over
631 the laˇzy dog."},
632 Mode::Normal,
633 );
634 cx.simulate_keystrokes("y s $ 1");
635 cx.assert_state(
636 indoc! {"
637 The quˇ1ick brown1
638 fox jumps over
639 the laˇ1zy dog.1"},
640 Mode::Normal,
641 );
642
643 // test add surrounds with motion current line
644 cx.set_state(
645 indoc! {"
646 The quˇick brown
647 fox jumps over
648 the lazy dog."},
649 Mode::Normal,
650 );
651 cx.simulate_keystrokes("y s s {");
652 cx.assert_state(
653 indoc! {"
654 ˇ{ The quick brown }
655 fox jumps over
656 the lazy dog."},
657 Mode::Normal,
658 );
659
660 cx.set_state(
661 indoc! {"
662 The quˇick brown•
663 fox jumps over
664 the lazy dog."},
665 Mode::Normal,
666 );
667 cx.simulate_keystrokes("y s s {");
668 cx.assert_state(
669 indoc! {"
670 ˇ{ The quick brown }•
671 fox jumps over
672 the lazy dog."},
673 Mode::Normal,
674 );
675 cx.simulate_keystrokes("2 y s s )");
676 cx.assert_state(
677 indoc! {"
678 ˇ({ The quick brown }•
679 fox jumps over)
680 the lazy dog."},
681 Mode::Normal,
682 );
683 }
684
685 #[gpui::test]
686 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
687 let mut cx = VimTestContext::new(cx, true).await;
688
689 // test delete surround
690 cx.set_state(
691 indoc! {"
692 The {quˇick} brown
693 fox jumps over
694 the lazy dog."},
695 Mode::Normal,
696 );
697 cx.simulate_keystrokes("d 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 delete not exist surrounds
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("d s [");
715 cx.assert_state(
716 indoc! {"
717 The {quˇick} brown
718 fox jumps over
719 the lazy dog."},
720 Mode::Normal,
721 );
722
723 // test delete surround forward exist, in the surrounds plugin of other editors,
724 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
725 cx.set_state(
726 indoc! {"
727 The {quick} brˇown
728 fox jumps over
729 the lazy dog."},
730 Mode::Normal,
731 );
732 cx.simulate_keystrokes("d s {");
733 cx.assert_state(
734 indoc! {"
735 The {quick} brˇown
736 fox jumps over
737 the lazy dog."},
738 Mode::Normal,
739 );
740
741 // test cursor delete inner surrounds
742 cx.set_state(
743 indoc! {"
744 The { quick brown
745 fox jumˇps over }
746 the lazy dog."},
747 Mode::Normal,
748 );
749 cx.simulate_keystrokes("d s {");
750 cx.assert_state(
751 indoc! {"
752 The ˇquick brown
753 fox jumps over
754 the lazy dog."},
755 Mode::Normal,
756 );
757
758 // test multi cursor delete surrounds
759 cx.set_state(
760 indoc! {"
761 The [quˇick] brown
762 fox jumps over
763 the [laˇzy] dog."},
764 Mode::Normal,
765 );
766 cx.simulate_keystrokes("d s ]");
767 cx.assert_state(
768 indoc! {"
769 The ˇquick brown
770 fox jumps over
771 the ˇlazy dog."},
772 Mode::Normal,
773 );
774
775 // test multi cursor delete surrounds with arround
776 cx.set_state(
777 indoc! {"
778 Tˇhe [ quick ] brown
779 fox jumps over
780 the [laˇzy] dog."},
781 Mode::Normal,
782 );
783 cx.simulate_keystrokes("d s [");
784 cx.assert_state(
785 indoc! {"
786 The ˇquick brown
787 fox jumps over
788 the ˇlazy dog."},
789 Mode::Normal,
790 );
791
792 cx.set_state(
793 indoc! {"
794 Tˇhe [ quick ] brown
795 fox jumps over
796 the [laˇzy ] dog."},
797 Mode::Normal,
798 );
799 cx.simulate_keystrokes("d s [");
800 cx.assert_state(
801 indoc! {"
802 The ˇquick brown
803 fox jumps over
804 the ˇlazy dog."},
805 Mode::Normal,
806 );
807
808 // test multi cursor delete different surrounds
809 // the pair corresponding to the two cursors is the same,
810 // so they are combined into one cursor
811 cx.set_state(
812 indoc! {"
813 The [quˇick] brown
814 fox jumps over
815 the {laˇzy} dog."},
816 Mode::Normal,
817 );
818 cx.simulate_keystrokes("d s {");
819 cx.assert_state(
820 indoc! {"
821 The [quick] brown
822 fox jumps over
823 the ˇlazy dog."},
824 Mode::Normal,
825 );
826
827 // test delete surround with multi cursor and nest surrounds
828 cx.set_state(
829 indoc! {"
830 fn test_surround() {
831 ifˇ 2 > 1 {
832 ˇprintln!(\"it is fine\");
833 };
834 }"},
835 Mode::Normal,
836 );
837 cx.simulate_keystrokes("d s }");
838 cx.assert_state(
839 indoc! {"
840 fn test_surround() ˇ
841 if 2 > 1 ˇ
842 println!(\"it is fine\");
843 ;
844 "},
845 Mode::Normal,
846 );
847 }
848
849 #[gpui::test]
850 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
851 let mut cx = VimTestContext::new(cx, true).await;
852
853 cx.set_state(
854 indoc! {"
855 The {quˇick} brown
856 fox jumps over
857 the lazy dog."},
858 Mode::Normal,
859 );
860 cx.simulate_keystrokes("c s { [");
861 cx.assert_state(
862 indoc! {"
863 The ˇ[ quick ] brown
864 fox jumps over
865 the lazy dog."},
866 Mode::Normal,
867 );
868
869 // test multi cursor change surrounds
870 cx.set_state(
871 indoc! {"
872 The {quˇick} brown
873 fox jumps over
874 the {laˇzy} dog."},
875 Mode::Normal,
876 );
877 cx.simulate_keystrokes("c s { [");
878 cx.assert_state(
879 indoc! {"
880 The ˇ[ quick ] brown
881 fox jumps over
882 the ˇ[ lazy ] dog."},
883 Mode::Normal,
884 );
885
886 // test multi cursor delete different surrounds with after cursor
887 cx.set_state(
888 indoc! {"
889 Thˇe {quick} brown
890 fox jumps over
891 the {laˇzy} dog."},
892 Mode::Normal,
893 );
894 cx.simulate_keystrokes("c s { [");
895 cx.assert_state(
896 indoc! {"
897 The ˇ[ quick ] brown
898 fox jumps over
899 the ˇ[ lazy ] dog."},
900 Mode::Normal,
901 );
902
903 // test multi cursor change surrount with not arround
904 cx.set_state(
905 indoc! {"
906 Thˇe { quick } brown
907 fox jumps over
908 the {laˇzy} dog."},
909 Mode::Normal,
910 );
911 cx.simulate_keystrokes("c s { ]");
912 cx.assert_state(
913 indoc! {"
914 The ˇ[quick] brown
915 fox jumps over
916 the ˇ[lazy] dog."},
917 Mode::Normal,
918 );
919
920 // test multi cursor change with not exist surround
921 cx.set_state(
922 indoc! {"
923 The {quˇick} brown
924 fox jumps over
925 the [laˇzy] dog."},
926 Mode::Normal,
927 );
928 cx.simulate_keystrokes("c s [ '");
929 cx.assert_state(
930 indoc! {"
931 The {quick} brown
932 fox jumps over
933 the ˇ'lazy' dog."},
934 Mode::Normal,
935 );
936
937 // test change nesting surrounds
938 cx.set_state(
939 indoc! {"
940 fn test_surround() {
941 ifˇ 2 > 1 {
942 ˇprintln!(\"it is fine\");
943 }
944 };"},
945 Mode::Normal,
946 );
947 cx.simulate_keystrokes("c s { [");
948 cx.assert_state(
949 indoc! {"
950 fn test_surround() ˇ[
951 if 2 > 1 ˇ[
952 println!(\"it is fine\");
953 ]
954 ];"},
955 Mode::Normal,
956 );
957 }
958
959 #[gpui::test]
960 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
961 let mut cx = VimTestContext::new(cx, true).await;
962
963 cx.set_state(
964 indoc! {"
965 The quˇick brown
966 fox jumps over
967 the lazy dog."},
968 Mode::Normal,
969 );
970 cx.simulate_keystrokes("y s i w [");
971 cx.assert_state(
972 indoc! {"
973 The ˇ[ quick ] brown
974 fox jumps over
975 the lazy dog."},
976 Mode::Normal,
977 );
978
979 cx.simulate_keystrokes("c s [ }");
980 cx.assert_state(
981 indoc! {"
982 The ˇ{quick} brown
983 fox jumps over
984 the lazy dog."},
985 Mode::Normal,
986 );
987
988 cx.simulate_keystrokes("d s {");
989 cx.assert_state(
990 indoc! {"
991 The ˇquick brown
992 fox jumps over
993 the lazy dog."},
994 Mode::Normal,
995 );
996
997 cx.simulate_keystrokes("u");
998 cx.assert_state(
999 indoc! {"
1000 The ˇ{quick} brown
1001 fox jumps over
1002 the lazy dog."},
1003 Mode::Normal,
1004 );
1005 }
1006}