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