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