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