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