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