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