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, bool),
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, around) => {
63 object.range(&display_map, selection.clone(), *around)
64 }
65 SurroundsType::Motion(motion) => {
66 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 }
94 SurroundsType::Selection => Some(selection.range()),
95 };
96
97 if let Some(range) = range {
98 let start = range.start.to_offset(&display_map, Bias::Right);
99 let end = range.end.to_offset(&display_map, Bias::Left);
100 let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
101 (format!("{}\n", pair.start), format!("{}\n", pair.end))
102 } else {
103 let maybe_space = if surround { " " } else { "" };
104 (
105 format!("{}{}", pair.start, maybe_space),
106 format!("{}{}", maybe_space, pair.end),
107 )
108 };
109 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
110
111 edits.push((start..start, start_cursor_str));
112 edits.push((end..end, end_cursor_str));
113 anchors.push(start_anchor..start_anchor);
114 } else {
115 let start_anchor = display_map
116 .buffer_snapshot
117 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
118 anchors.push(start_anchor..start_anchor);
119 }
120 }
121
122 editor.buffer().update(cx, |buffer, cx| {
123 buffer.edit(edits, None, cx);
124 });
125 editor.set_clip_at_line_ends(true, cx);
126 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
127 if mode == Mode::VisualBlock {
128 s.select_anchor_ranges(anchors.into_iter().take(1))
129 } else {
130 s.select_anchor_ranges(anchors)
131 }
132 });
133 });
134 });
135 self.switch_mode(Mode::Normal, false, cx);
136 }
137
138 pub fn delete_surrounds(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
139 self.stop_recording(cx);
140
141 // only legitimate surrounds can be removed
142 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
143 Some(pair) => pair.clone(),
144 None => return,
145 };
146 let pair_object = match pair_to_object(&pair) {
147 Some(pair_object) => pair_object,
148 None => return,
149 };
150 let surround = pair.end != *text;
151
152 self.update_editor(cx, |_, editor, cx| {
153 editor.transact(cx, |editor, cx| {
154 editor.set_clip_at_line_ends(false, cx);
155
156 let (display_map, display_selections) = editor.selections.all_display(cx);
157 let mut edits = Vec::new();
158 let mut anchors = Vec::new();
159
160 for selection in &display_selections {
161 let start = selection.start.to_offset(&display_map, Bias::Left);
162 if let Some(range) = pair_object.range(&display_map, selection.clone(), true) {
163 // If the current parenthesis object is single-line,
164 // then we need to filter whether it is the current line or not
165 if !pair_object.is_multiline() {
166 let is_same_row = selection.start.row() == range.start.row()
167 && selection.end.row() == range.end.row();
168 if !is_same_row {
169 anchors.push(start..start);
170 continue;
171 }
172 }
173 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
174 // hello«ˇ "hello in a word" »again.
175 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
176 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
177 let mut chars_and_offset = display_map
178 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
179 .peekable();
180 while let Some((ch, offset)) = chars_and_offset.next() {
181 if ch.to_string() == pair.start {
182 let start = offset;
183 let mut end = start + 1;
184 if surround {
185 if let Some((next_ch, _)) = chars_and_offset.peek() {
186 if next_ch.eq(&' ') {
187 end += 1;
188 }
189 }
190 }
191 edits.push((start..end, ""));
192 anchors.push(start..start);
193 break;
194 }
195 }
196 let mut reverse_chars_and_offsets = display_map
197 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
198 .peekable();
199 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
200 if ch.to_string() == pair.end {
201 let mut start = offset;
202 let end = start + 1;
203 if surround {
204 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
205 if next_ch.eq(&' ') {
206 start -= 1;
207 }
208 }
209 }
210 edits.push((start..end, ""));
211 break;
212 }
213 }
214 } else {
215 anchors.push(start..start);
216 }
217 }
218
219 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
220 s.select_ranges(anchors);
221 });
222 edits.sort_by_key(|(range, _)| range.start);
223 editor.buffer().update(cx, |buffer, cx| {
224 buffer.edit(edits, None, cx);
225 });
226 editor.set_clip_at_line_ends(true, cx);
227 });
228 });
229 }
230
231 pub fn change_surrounds(&mut self, text: Arc<str>, target: Object, cx: &mut ViewContext<Self>) {
232 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
233 self.stop_recording(cx);
234 self.update_editor(cx, |_, editor, cx| {
235 editor.transact(cx, |editor, cx| {
236 editor.set_clip_at_line_ends(false, cx);
237
238 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
239 Some(pair) => pair.clone(),
240 None => BracketPair {
241 start: text.to_string(),
242 end: text.to_string(),
243 close: true,
244 surround: true,
245 newline: false,
246 },
247 };
248 let surround = pair.end != *text;
249 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
250 let mut edits = Vec::new();
251 let mut anchors = Vec::new();
252
253 for selection in &selections {
254 let start = selection.start.to_offset(&display_map, Bias::Left);
255 if let Some(range) = target.range(&display_map, selection.clone(), true) {
256 if !target.is_multiline() {
257 let is_same_row = selection.start.row() == range.start.row()
258 && selection.end.row() == range.end.row();
259 if !is_same_row {
260 anchors.push(start..start);
261 continue;
262 }
263 }
264 let mut chars_and_offset = display_map
265 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
266 .peekable();
267 while let Some((ch, offset)) = chars_and_offset.next() {
268 if ch.to_string() == will_replace_pair.start {
269 let mut open_str = pair.start.clone();
270 let start = offset;
271 let mut end = start + 1;
272 if let Some((next_ch, _)) = chars_and_offset.peek() {
273 // If the next position is already a space or line break,
274 // we don't need to splice another space even under around
275 if surround && !next_ch.is_whitespace() {
276 open_str.push(' ');
277 } else if !surround && next_ch.to_string() == " " {
278 end += 1;
279 }
280 }
281 edits.push((start..end, open_str));
282 anchors.push(start..start);
283 break;
284 }
285 }
286
287 let mut reverse_chars_and_offsets = display_map
288 .reverse_buffer_chars_at(
289 range.end.to_offset(&display_map, Bias::Left),
290 )
291 .peekable();
292 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
293 if ch.to_string() == will_replace_pair.end {
294 let mut close_str = pair.end.clone();
295 let mut start = offset;
296 let end = start + 1;
297 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
298 if surround && !next_ch.is_whitespace() {
299 close_str.insert(0, ' ')
300 } else if !surround && next_ch.to_string() == " " {
301 start -= 1;
302 }
303 }
304 edits.push((start..end, close_str));
305 break;
306 }
307 }
308 } else {
309 anchors.push(start..start);
310 }
311 }
312
313 let stable_anchors = editor
314 .selections
315 .disjoint_anchors()
316 .iter()
317 .map(|selection| {
318 let start = selection.start.bias_left(&display_map.buffer_snapshot);
319 start..start
320 })
321 .collect::<Vec<_>>();
322 edits.sort_by_key(|(range, _)| range.start);
323 editor.buffer().update(cx, |buffer, cx| {
324 buffer.edit(edits, None, cx);
325 });
326 editor.set_clip_at_line_ends(true, cx);
327 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
328 s.select_anchor_ranges(stable_anchors);
329 });
330 });
331 });
332 }
333 }
334
335 /// Checks if any of the current cursors are surrounded by a valid pair of brackets.
336 ///
337 /// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
338 /// A pair of brackets is considered valid if it is well-formed and properly closed.
339 ///
340 /// 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.
341 /// If no valid pair of brackets is found for any cursor, the method returns `false`.
342 pub fn check_and_move_to_valid_bracket_pair(
343 &mut self,
344 object: Object,
345 cx: &mut ViewContext<Self>,
346 ) -> bool {
347 let mut valid = false;
348 if let Some(pair) = object_to_bracket_pair(object) {
349 self.update_editor(cx, |_, editor, cx| {
350 editor.transact(cx, |editor, cx| {
351 editor.set_clip_at_line_ends(false, cx);
352 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
353 let mut anchors = Vec::new();
354
355 for selection in &selections {
356 let start = selection.start.to_offset(&display_map, Bias::Left);
357 if let Some(range) = object.range(&display_map, selection.clone(), true) {
358 // If the current parenthesis object is single-line,
359 // then we need to filter whether it is the current line or not
360 if object.is_multiline()
361 || (!object.is_multiline()
362 && selection.start.row() == range.start.row()
363 && selection.end.row() == range.end.row())
364 {
365 valid = true;
366 let chars_and_offset = display_map
367 .buffer_chars_at(
368 range.start.to_offset(&display_map, Bias::Left),
369 )
370 .peekable();
371 for (ch, offset) in chars_and_offset {
372 if ch.to_string() == pair.start {
373 anchors.push(offset..offset);
374 break;
375 }
376 }
377 } else {
378 anchors.push(start..start)
379 }
380 } else {
381 anchors.push(start..start)
382 }
383 }
384 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
385 s.select_ranges(anchors);
386 });
387 editor.set_clip_at_line_ends(true, cx);
388 });
389 });
390 }
391 valid
392 }
393}
394
395fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
396 pairs.iter().find(|pair| pair.start == ch || pair.end == ch)
397}
398
399fn all_support_surround_pair() -> Vec<BracketPair> {
400 vec![
401 BracketPair {
402 start: "{".into(),
403 end: "}".into(),
404 close: true,
405 surround: true,
406 newline: false,
407 },
408 BracketPair {
409 start: "'".into(),
410 end: "'".into(),
411 close: true,
412 surround: true,
413 newline: false,
414 },
415 BracketPair {
416 start: "`".into(),
417 end: "`".into(),
418 close: true,
419 surround: true,
420 newline: false,
421 },
422 BracketPair {
423 start: "\"".into(),
424 end: "\"".into(),
425 close: true,
426 surround: true,
427 newline: false,
428 },
429 BracketPair {
430 start: "(".into(),
431 end: ")".into(),
432 close: true,
433 surround: true,
434 newline: false,
435 },
436 BracketPair {
437 start: "|".into(),
438 end: "|".into(),
439 close: true,
440 surround: true,
441 newline: false,
442 },
443 BracketPair {
444 start: "[".into(),
445 end: "]".into(),
446 close: true,
447 surround: true,
448 newline: false,
449 },
450 BracketPair {
451 start: "{".into(),
452 end: "}".into(),
453 close: true,
454 surround: true,
455 newline: false,
456 },
457 BracketPair {
458 start: "<".into(),
459 end: ">".into(),
460 close: true,
461 surround: true,
462 newline: false,
463 },
464 ]
465}
466
467fn pair_to_object(pair: &BracketPair) -> Option<Object> {
468 match pair.start.as_str() {
469 "'" => Some(Object::Quotes),
470 "`" => Some(Object::BackQuotes),
471 "\"" => Some(Object::DoubleQuotes),
472 "|" => Some(Object::VerticalBars),
473 "(" => Some(Object::Parentheses),
474 "[" => Some(Object::SquareBrackets),
475 "{" => Some(Object::CurlyBrackets),
476 "<" => Some(Object::AngleBrackets),
477 _ => None,
478 }
479}
480
481fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
482 match object {
483 Object::Quotes => Some(BracketPair {
484 start: "'".to_string(),
485 end: "'".to_string(),
486 close: true,
487 surround: true,
488 newline: false,
489 }),
490 Object::BackQuotes => Some(BracketPair {
491 start: "`".to_string(),
492 end: "`".to_string(),
493 close: true,
494 surround: true,
495 newline: false,
496 }),
497 Object::DoubleQuotes => Some(BracketPair {
498 start: "\"".to_string(),
499 end: "\"".to_string(),
500 close: true,
501 surround: true,
502 newline: false,
503 }),
504 Object::VerticalBars => Some(BracketPair {
505 start: "|".to_string(),
506 end: "|".to_string(),
507 close: true,
508 surround: true,
509 newline: false,
510 }),
511 Object::Parentheses => Some(BracketPair {
512 start: "(".to_string(),
513 end: ")".to_string(),
514 close: true,
515 surround: true,
516 newline: false,
517 }),
518 Object::SquareBrackets => Some(BracketPair {
519 start: "[".to_string(),
520 end: "]".to_string(),
521 close: true,
522 surround: true,
523 newline: false,
524 }),
525 Object::CurlyBrackets => Some(BracketPair {
526 start: "{".to_string(),
527 end: "}".to_string(),
528 close: true,
529 surround: true,
530 newline: false,
531 }),
532 Object::AngleBrackets => Some(BracketPair {
533 start: "<".to_string(),
534 end: ">".to_string(),
535 close: true,
536 surround: true,
537 newline: false,
538 }),
539 _ => None,
540 }
541}
542
543#[cfg(test)]
544mod test {
545 use gpui::KeyBinding;
546 use indoc::indoc;
547
548 use crate::{
549 state::{Mode, Operator},
550 test::VimTestContext,
551 PushOperator,
552 };
553
554 #[gpui::test]
555 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
556 let mut cx = VimTestContext::new(cx, true).await;
557
558 // test add surrounds with around
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 not with around
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 i w }");
584 cx.assert_state(
585 indoc! {"
586 The ˇ{quick} brown
587 fox jumps over
588 the lazy dog."},
589 Mode::Normal,
590 );
591
592 // test add surrounds with motion
593 cx.set_state(
594 indoc! {"
595 The quˇick brown
596 fox jumps over
597 the lazy dog."},
598 Mode::Normal,
599 );
600 cx.simulate_keystrokes("y s $ }");
601 cx.assert_state(
602 indoc! {"
603 The quˇ{ick brown}
604 fox jumps over
605 the lazy dog."},
606 Mode::Normal,
607 );
608
609 // test add surrounds with multi cursor
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 i w '");
618 cx.assert_state(
619 indoc! {"
620 The ˇ'quick' brown
621 fox jumps over
622 the ˇ'lazy' dog."},
623 Mode::Normal,
624 );
625
626 // test multi cursor add surrounds with motion
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 $ '");
635 cx.assert_state(
636 indoc! {"
637 The quˇ'ick brown'
638 fox jumps over
639 the laˇ'zy dog.'"},
640 Mode::Normal,
641 );
642
643 // test multi cursor add surrounds with motion and custom string
644 cx.set_state(
645 indoc! {"
646 The quˇick brown
647 fox jumps over
648 the laˇzy dog."},
649 Mode::Normal,
650 );
651 cx.simulate_keystrokes("y s $ 1");
652 cx.assert_state(
653 indoc! {"
654 The quˇ1ick brown1
655 fox jumps over
656 the laˇ1zy dog.1"},
657 Mode::Normal,
658 );
659
660 // test add surrounds with motion current line
661 cx.set_state(
662 indoc! {"
663 The quˇick brown
664 fox jumps over
665 the lazy dog."},
666 Mode::Normal,
667 );
668 cx.simulate_keystrokes("y s s {");
669 cx.assert_state(
670 indoc! {"
671 ˇ{ The quick brown }
672 fox jumps over
673 the lazy dog."},
674 Mode::Normal,
675 );
676
677 cx.set_state(
678 indoc! {"
679 The quˇick brown•
680 fox jumps over
681 the lazy dog."},
682 Mode::Normal,
683 );
684 cx.simulate_keystrokes("y s s {");
685 cx.assert_state(
686 indoc! {"
687 ˇ{ The quick brown }•
688 fox jumps over
689 the lazy dog."},
690 Mode::Normal,
691 );
692 cx.simulate_keystrokes("2 y s s )");
693 cx.assert_state(
694 indoc! {"
695 ˇ({ The quick brown }•
696 fox jumps over)
697 the lazy dog."},
698 Mode::Normal,
699 );
700
701 // test add surrounds around object
702 cx.set_state(
703 indoc! {"
704 The [quˇick] brown
705 fox jumps over
706 the lazy dog."},
707 Mode::Normal,
708 );
709 cx.simulate_keystrokes("y s a ] )");
710 cx.assert_state(
711 indoc! {"
712 The ˇ([quick]) brown
713 fox jumps over
714 the lazy dog."},
715 Mode::Normal,
716 );
717
718 // test add surrounds inside object
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("y s i ] )");
727 cx.assert_state(
728 indoc! {"
729 The [ˇ(quick)] brown
730 fox jumps over
731 the lazy dog."},
732 Mode::Normal,
733 );
734 }
735
736 #[gpui::test]
737 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
738 let mut cx = VimTestContext::new(cx, true).await;
739
740 cx.update(|cx| {
741 cx.bind_keys([KeyBinding::new(
742 "shift-s",
743 PushOperator(Operator::AddSurrounds { target: None }),
744 Some("vim_mode == visual"),
745 )])
746 });
747
748 // test add surrounds with around
749 cx.set_state(
750 indoc! {"
751 The quˇick brown
752 fox jumps over
753 the lazy dog."},
754 Mode::Normal,
755 );
756 cx.simulate_keystrokes("v i w shift-s {");
757 cx.assert_state(
758 indoc! {"
759 The ˇ{ quick } brown
760 fox jumps over
761 the lazy dog."},
762 Mode::Normal,
763 );
764
765 // test add surrounds not with around
766 cx.set_state(
767 indoc! {"
768 The quˇick brown
769 fox jumps over
770 the lazy dog."},
771 Mode::Normal,
772 );
773 cx.simulate_keystrokes("v i w shift-s }");
774 cx.assert_state(
775 indoc! {"
776 The ˇ{quick} brown
777 fox jumps over
778 the lazy dog."},
779 Mode::Normal,
780 );
781
782 // test add surrounds with motion
783 cx.set_state(
784 indoc! {"
785 The quˇick brown
786 fox jumps over
787 the lazy dog."},
788 Mode::Normal,
789 );
790 cx.simulate_keystrokes("v e shift-s }");
791 cx.assert_state(
792 indoc! {"
793 The quˇ{ick} brown
794 fox jumps over
795 the lazy dog."},
796 Mode::Normal,
797 );
798
799 // test add surrounds with multi cursor
800 cx.set_state(
801 indoc! {"
802 The quˇick brown
803 fox jumps over
804 the laˇzy dog."},
805 Mode::Normal,
806 );
807 cx.simulate_keystrokes("v i w shift-s '");
808 cx.assert_state(
809 indoc! {"
810 The ˇ'quick' brown
811 fox jumps over
812 the ˇ'lazy' dog."},
813 Mode::Normal,
814 );
815
816 // test add surrounds with visual block
817 cx.set_state(
818 indoc! {"
819 The quˇick brown
820 fox jumps over
821 the lazy dog."},
822 Mode::Normal,
823 );
824 cx.simulate_keystrokes("ctrl-v i w j j shift-s '");
825 cx.assert_state(
826 indoc! {"
827 The ˇ'quick' brown
828 fox 'jumps' over
829 the 'lazy 'dog."},
830 Mode::Normal,
831 );
832
833 // test add surrounds with visual line
834 cx.set_state(
835 indoc! {"
836 The quˇick brown
837 fox jumps over
838 the lazy dog."},
839 Mode::Normal,
840 );
841 cx.simulate_keystrokes("j shift-v shift-s '");
842 cx.assert_state(
843 indoc! {"
844 The quick brown
845 ˇ'
846 fox jumps over
847 '
848 the lazy dog."},
849 Mode::Normal,
850 );
851 }
852
853 #[gpui::test]
854 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
855 let mut cx = VimTestContext::new(cx, true).await;
856
857 // test delete surround
858 cx.set_state(
859 indoc! {"
860 The {quˇick} brown
861 fox jumps over
862 the lazy dog."},
863 Mode::Normal,
864 );
865 cx.simulate_keystrokes("d s {");
866 cx.assert_state(
867 indoc! {"
868 The ˇquick brown
869 fox jumps over
870 the lazy dog."},
871 Mode::Normal,
872 );
873
874 // test delete not exist surrounds
875 cx.set_state(
876 indoc! {"
877 The {quˇick} brown
878 fox jumps over
879 the lazy dog."},
880 Mode::Normal,
881 );
882 cx.simulate_keystrokes("d s [");
883 cx.assert_state(
884 indoc! {"
885 The {quˇick} brown
886 fox jumps over
887 the lazy dog."},
888 Mode::Normal,
889 );
890
891 // test delete surround forward exist, in the surrounds plugin of other editors,
892 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
893 cx.set_state(
894 indoc! {"
895 The {quick} brˇown
896 fox jumps over
897 the lazy dog."},
898 Mode::Normal,
899 );
900 cx.simulate_keystrokes("d s {");
901 cx.assert_state(
902 indoc! {"
903 The {quick} brˇown
904 fox jumps over
905 the lazy dog."},
906 Mode::Normal,
907 );
908
909 // test cursor delete inner surrounds
910 cx.set_state(
911 indoc! {"
912 The { quick brown
913 fox jumˇps over }
914 the lazy dog."},
915 Mode::Normal,
916 );
917 cx.simulate_keystrokes("d s {");
918 cx.assert_state(
919 indoc! {"
920 The ˇquick brown
921 fox jumps over
922 the lazy dog."},
923 Mode::Normal,
924 );
925
926 // test multi cursor delete surrounds
927 cx.set_state(
928 indoc! {"
929 The [quˇick] brown
930 fox jumps over
931 the [laˇzy] dog."},
932 Mode::Normal,
933 );
934 cx.simulate_keystrokes("d s ]");
935 cx.assert_state(
936 indoc! {"
937 The ˇquick brown
938 fox jumps over
939 the ˇlazy dog."},
940 Mode::Normal,
941 );
942
943 // test multi cursor delete surrounds with around
944 cx.set_state(
945 indoc! {"
946 Tˇhe [ quick ] brown
947 fox jumps over
948 the [laˇzy] dog."},
949 Mode::Normal,
950 );
951 cx.simulate_keystrokes("d s [");
952 cx.assert_state(
953 indoc! {"
954 The ˇquick brown
955 fox jumps over
956 the ˇlazy dog."},
957 Mode::Normal,
958 );
959
960 cx.set_state(
961 indoc! {"
962 Tˇhe [ quick ] brown
963 fox jumps over
964 the [laˇzy ] dog."},
965 Mode::Normal,
966 );
967 cx.simulate_keystrokes("d s [");
968 cx.assert_state(
969 indoc! {"
970 The ˇquick brown
971 fox jumps over
972 the ˇlazy dog."},
973 Mode::Normal,
974 );
975
976 // test multi cursor delete different surrounds
977 // the pair corresponding to the two cursors is the same,
978 // so they are combined into one cursor
979 cx.set_state(
980 indoc! {"
981 The [quˇick] brown
982 fox jumps over
983 the {laˇzy} dog."},
984 Mode::Normal,
985 );
986 cx.simulate_keystrokes("d s {");
987 cx.assert_state(
988 indoc! {"
989 The [quick] brown
990 fox jumps over
991 the ˇlazy dog."},
992 Mode::Normal,
993 );
994
995 // test delete surround with multi cursor and nest surrounds
996 cx.set_state(
997 indoc! {"
998 fn test_surround() {
999 ifˇ 2 > 1 {
1000 ˇprintln!(\"it is fine\");
1001 };
1002 }"},
1003 Mode::Normal,
1004 );
1005 cx.simulate_keystrokes("d s }");
1006 cx.assert_state(
1007 indoc! {"
1008 fn test_surround() ˇ
1009 if 2 > 1 ˇ
1010 println!(\"it is fine\");
1011 ;
1012 "},
1013 Mode::Normal,
1014 );
1015 }
1016
1017 #[gpui::test]
1018 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
1019 let mut cx = VimTestContext::new(cx, true).await;
1020
1021 cx.set_state(
1022 indoc! {"
1023 The {quˇick} brown
1024 fox jumps over
1025 the lazy dog."},
1026 Mode::Normal,
1027 );
1028 cx.simulate_keystrokes("c s { [");
1029 cx.assert_state(
1030 indoc! {"
1031 The ˇ[ quick ] brown
1032 fox jumps over
1033 the lazy dog."},
1034 Mode::Normal,
1035 );
1036
1037 // test multi cursor change surrounds
1038 cx.set_state(
1039 indoc! {"
1040 The {quˇick} brown
1041 fox jumps over
1042 the {laˇzy} dog."},
1043 Mode::Normal,
1044 );
1045 cx.simulate_keystrokes("c s { [");
1046 cx.assert_state(
1047 indoc! {"
1048 The ˇ[ quick ] brown
1049 fox jumps over
1050 the ˇ[ lazy ] dog."},
1051 Mode::Normal,
1052 );
1053
1054 // test multi cursor delete different surrounds with after cursor
1055 cx.set_state(
1056 indoc! {"
1057 Thˇe {quick} brown
1058 fox jumps over
1059 the {laˇzy} dog."},
1060 Mode::Normal,
1061 );
1062 cx.simulate_keystrokes("c s { [");
1063 cx.assert_state(
1064 indoc! {"
1065 The ˇ[ quick ] brown
1066 fox jumps over
1067 the ˇ[ lazy ] dog."},
1068 Mode::Normal,
1069 );
1070
1071 // test multi cursor change surrount with not around
1072 cx.set_state(
1073 indoc! {"
1074 Thˇe { quick } brown
1075 fox jumps over
1076 the {laˇzy} dog."},
1077 Mode::Normal,
1078 );
1079 cx.simulate_keystrokes("c s { ]");
1080 cx.assert_state(
1081 indoc! {"
1082 The ˇ[quick] brown
1083 fox jumps over
1084 the ˇ[lazy] dog."},
1085 Mode::Normal,
1086 );
1087
1088 // test multi cursor change with not exist surround
1089 cx.set_state(
1090 indoc! {"
1091 The {quˇick} brown
1092 fox jumps over
1093 the [laˇzy] dog."},
1094 Mode::Normal,
1095 );
1096 cx.simulate_keystrokes("c s [ '");
1097 cx.assert_state(
1098 indoc! {"
1099 The {quick} brown
1100 fox jumps over
1101 the ˇ'lazy' dog."},
1102 Mode::Normal,
1103 );
1104
1105 // test change nesting surrounds
1106 cx.set_state(
1107 indoc! {"
1108 fn test_surround() {
1109 ifˇ 2 > 1 {
1110 ˇprintln!(\"it is fine\");
1111 }
1112 };"},
1113 Mode::Normal,
1114 );
1115 cx.simulate_keystrokes("c s { [");
1116 cx.assert_state(
1117 indoc! {"
1118 fn test_surround() ˇ[
1119 if 2 > 1 ˇ[
1120 println!(\"it is fine\");
1121 ]
1122 ];"},
1123 Mode::Normal,
1124 );
1125 }
1126
1127 #[gpui::test]
1128 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1129 let mut cx = VimTestContext::new(cx, true).await;
1130
1131 cx.set_state(
1132 indoc! {"
1133 The quˇick brown
1134 fox jumps over
1135 the lazy dog."},
1136 Mode::Normal,
1137 );
1138 cx.simulate_keystrokes("y s i w [");
1139 cx.assert_state(
1140 indoc! {"
1141 The ˇ[ quick ] brown
1142 fox jumps over
1143 the lazy dog."},
1144 Mode::Normal,
1145 );
1146
1147 cx.simulate_keystrokes("c s [ }");
1148 cx.assert_state(
1149 indoc! {"
1150 The ˇ{quick} brown
1151 fox jumps over
1152 the lazy dog."},
1153 Mode::Normal,
1154 );
1155
1156 cx.simulate_keystrokes("d s {");
1157 cx.assert_state(
1158 indoc! {"
1159 The ˇquick brown
1160 fox jumps over
1161 the lazy dog."},
1162 Mode::Normal,
1163 );
1164
1165 cx.simulate_keystrokes("u");
1166 cx.assert_state(
1167 indoc! {"
1168 The ˇ{quick} brown
1169 fox jumps over
1170 the lazy dog."},
1171 Mode::Normal,
1172 );
1173 }
1174}