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