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 = self.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.buffer().update(cx, |buffer, cx| {
123 buffer.edit(edits, None, cx);
124 });
125 editor.set_clip_at_line_ends(true, cx);
126 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
127 if mode == Mode::VisualBlock {
128 s.select_anchor_ranges(anchors.into_iter().take(1))
129 } else {
130 s.select_anchor_ranges(anchors)
131 }
132 });
133 });
134 });
135 self.switch_mode(Mode::Normal, false, cx);
136 }
137
138 pub fn delete_surrounds(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
139 self.stop_recording(cx);
140
141 // only legitimate surrounds can be removed
142 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
143 Some(pair) => pair.clone(),
144 None => return,
145 };
146 let pair_object = match pair_to_object(&pair) {
147 Some(pair_object) => pair_object,
148 None => return,
149 };
150 let surround = pair.end != *text;
151
152 self.update_editor(cx, |_, editor, cx| {
153 editor.transact(cx, |editor, cx| {
154 editor.set_clip_at_line_ends(false, cx);
155
156 let (display_map, display_selections) = editor.selections.all_display(cx);
157 let mut edits = Vec::new();
158 let mut anchors = Vec::new();
159
160 for selection in &display_selections {
161 let start = selection.start.to_offset(&display_map, Bias::Left);
162 if let Some(range) = pair_object.range(&display_map, selection.clone(), true) {
163 // If the current parenthesis object is single-line,
164 // then we need to filter whether it is the current line or not
165 if !pair_object.is_multiline() {
166 let is_same_row = selection.start.row() == range.start.row()
167 && selection.end.row() == range.end.row();
168 if !is_same_row {
169 anchors.push(start..start);
170 continue;
171 }
172 }
173 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
174 // hello«ˇ "hello in a word" »again.
175 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
176 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
177 let mut chars_and_offset = display_map
178 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
179 .peekable();
180 while let Some((ch, offset)) = chars_and_offset.next() {
181 if ch.to_string() == pair.start {
182 let start = offset;
183 let mut end = start + 1;
184 if surround {
185 if let Some((next_ch, _)) = chars_and_offset.peek() {
186 if next_ch.eq(&' ') {
187 end += 1;
188 }
189 }
190 }
191 edits.push((start..end, ""));
192 anchors.push(start..start);
193 break;
194 }
195 }
196 let mut reverse_chars_and_offsets = display_map
197 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
198 .peekable();
199 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
200 if ch.to_string() == pair.end {
201 let mut start = offset;
202 let end = start + 1;
203 if surround {
204 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
205 if next_ch.eq(&' ') {
206 start -= 1;
207 }
208 }
209 }
210 edits.push((start..end, ""));
211 break;
212 }
213 }
214 } else {
215 anchors.push(start..start);
216 }
217 }
218
219 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
220 s.select_ranges(anchors);
221 });
222 edits.sort_by_key(|(range, _)| range.start);
223 editor.buffer().update(cx, |buffer, cx| {
224 buffer.edit(edits, None, cx);
225 });
226 editor.set_clip_at_line_ends(true, cx);
227 });
228 });
229 }
230
231 pub fn change_surrounds(&mut self, text: Arc<str>, target: Object, cx: &mut ViewContext<Self>) {
232 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
233 self.stop_recording(cx);
234 self.update_editor(cx, |_, editor, cx| {
235 editor.transact(cx, |editor, cx| {
236 editor.set_clip_at_line_ends(false, cx);
237
238 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
239 Some(pair) => pair.clone(),
240 None => BracketPair {
241 start: text.to_string(),
242 end: text.to_string(),
243 close: true,
244 surround: true,
245 newline: false,
246 },
247 };
248 let surround = pair.end != surround_alias((*text).as_ref());
249 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
250 let mut edits = Vec::new();
251 let mut anchors = Vec::new();
252
253 for selection in &selections {
254 let start = selection.start.to_offset(&display_map, Bias::Left);
255 if let Some(range) = target.range(&display_map, selection.clone(), true) {
256 if !target.is_multiline() {
257 let is_same_row = selection.start.row() == range.start.row()
258 && selection.end.row() == range.end.row();
259 if !is_same_row {
260 anchors.push(start..start);
261 continue;
262 }
263 }
264 let mut chars_and_offset = display_map
265 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
266 .peekable();
267 while let Some((ch, offset)) = chars_and_offset.next() {
268 if ch.to_string() == will_replace_pair.start {
269 let mut open_str = pair.start.clone();
270 let start = offset;
271 let mut end = start + 1;
272 if let Some((next_ch, _)) = chars_and_offset.peek() {
273 // If the next position is already a space or line break,
274 // we don't need to splice another space even under around
275 if surround && !next_ch.is_whitespace() {
276 open_str.push(' ');
277 } else if !surround && next_ch.to_string() == " " {
278 end += 1;
279 }
280 }
281 edits.push((start..end, open_str));
282 anchors.push(start..start);
283 break;
284 }
285 }
286
287 let mut reverse_chars_and_offsets = display_map
288 .reverse_buffer_chars_at(
289 range.end.to_offset(&display_map, Bias::Left),
290 )
291 .peekable();
292 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
293 if ch.to_string() == will_replace_pair.end {
294 let mut close_str = pair.end.clone();
295 let mut start = offset;
296 let end = start + 1;
297 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
298 if surround && !next_ch.is_whitespace() {
299 close_str.insert(0, ' ')
300 } else if !surround && next_ch.to_string() == " " {
301 start -= 1;
302 }
303 }
304 edits.push((start..end, close_str));
305 break;
306 }
307 }
308 } else {
309 anchors.push(start..start);
310 }
311 }
312
313 let stable_anchors = editor
314 .selections
315 .disjoint_anchors()
316 .iter()
317 .map(|selection| {
318 let start = selection.start.bias_left(&display_map.buffer_snapshot);
319 start..start
320 })
321 .collect::<Vec<_>>();
322 edits.sort_by_key(|(range, _)| range.start);
323 editor.buffer().update(cx, |buffer, cx| {
324 buffer.edit(edits, None, cx);
325 });
326 editor.set_clip_at_line_ends(true, cx);
327 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
328 s.select_anchor_ranges(stable_anchors);
329 });
330 });
331 });
332 }
333 }
334
335 /// Checks if any of the current cursors are surrounded by a valid pair of brackets.
336 ///
337 /// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
338 /// A pair of brackets is considered valid if it is well-formed and properly closed.
339 ///
340 /// 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.
341 /// If no valid pair of brackets is found for any cursor, the method returns `false`.
342 pub fn check_and_move_to_valid_bracket_pair(
343 &mut self,
344 object: Object,
345 cx: &mut ViewContext<Self>,
346 ) -> bool {
347 let mut valid = false;
348 if let Some(pair) = object_to_bracket_pair(object) {
349 self.update_editor(cx, |_, editor, cx| {
350 editor.transact(cx, |editor, cx| {
351 editor.set_clip_at_line_ends(false, cx);
352 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
353 let mut anchors = Vec::new();
354
355 for selection in &selections {
356 let start = selection.start.to_offset(&display_map, Bias::Left);
357 if let Some(range) = object.range(&display_map, selection.clone(), true) {
358 // If the current parenthesis object is single-line,
359 // then we need to filter whether it is the current line or not
360 if object.is_multiline()
361 || (!object.is_multiline()
362 && selection.start.row() == range.start.row()
363 && selection.end.row() == range.end.row())
364 {
365 valid = true;
366 let chars_and_offset = display_map
367 .buffer_chars_at(
368 range.start.to_offset(&display_map, Bias::Left),
369 )
370 .peekable();
371 for (ch, offset) in chars_and_offset {
372 if ch.to_string() == pair.start {
373 anchors.push(offset..offset);
374 break;
375 }
376 }
377 } else {
378 anchors.push(start..start)
379 }
380 } else {
381 anchors.push(start..start)
382 }
383 }
384 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
385 s.select_ranges(anchors);
386 });
387 editor.set_clip_at_line_ends(true, cx);
388 });
389 });
390 }
391 valid
392 }
393}
394
395fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
396 pairs
397 .iter()
398 .find(|pair| pair.start == surround_alias(ch) || pair.end == surround_alias(ch))
399}
400
401fn surround_alias(ch: &str) -> &str {
402 match ch {
403 "b" => ")",
404 "B" => "}",
405 "a" => ">",
406 "r" => "]",
407 _ => ch,
408 }
409}
410
411fn all_support_surround_pair() -> Vec<BracketPair> {
412 vec![
413 BracketPair {
414 start: "{".into(),
415 end: "}".into(),
416 close: true,
417 surround: true,
418 newline: false,
419 },
420 BracketPair {
421 start: "'".into(),
422 end: "'".into(),
423 close: true,
424 surround: true,
425 newline: false,
426 },
427 BracketPair {
428 start: "`".into(),
429 end: "`".into(),
430 close: true,
431 surround: true,
432 newline: false,
433 },
434 BracketPair {
435 start: "\"".into(),
436 end: "\"".into(),
437 close: true,
438 surround: true,
439 newline: false,
440 },
441 BracketPair {
442 start: "(".into(),
443 end: ")".into(),
444 close: true,
445 surround: true,
446 newline: false,
447 },
448 BracketPair {
449 start: "|".into(),
450 end: "|".into(),
451 close: true,
452 surround: true,
453 newline: false,
454 },
455 BracketPair {
456 start: "[".into(),
457 end: "]".into(),
458 close: true,
459 surround: true,
460 newline: false,
461 },
462 BracketPair {
463 start: "{".into(),
464 end: "}".into(),
465 close: true,
466 surround: true,
467 newline: false,
468 },
469 BracketPair {
470 start: "<".into(),
471 end: ">".into(),
472 close: true,
473 surround: true,
474 newline: false,
475 },
476 ]
477}
478
479fn pair_to_object(pair: &BracketPair) -> Option<Object> {
480 match pair.start.as_str() {
481 "'" => Some(Object::Quotes),
482 "`" => Some(Object::BackQuotes),
483 "\"" => Some(Object::DoubleQuotes),
484 "|" => Some(Object::VerticalBars),
485 "(" => Some(Object::Parentheses),
486 "[" => Some(Object::SquareBrackets),
487 "{" => Some(Object::CurlyBrackets),
488 "<" => Some(Object::AngleBrackets),
489 _ => None,
490 }
491}
492
493fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
494 match object {
495 Object::Quotes => Some(BracketPair {
496 start: "'".to_string(),
497 end: "'".to_string(),
498 close: true,
499 surround: true,
500 newline: false,
501 }),
502 Object::BackQuotes => Some(BracketPair {
503 start: "`".to_string(),
504 end: "`".to_string(),
505 close: true,
506 surround: true,
507 newline: false,
508 }),
509 Object::DoubleQuotes => Some(BracketPair {
510 start: "\"".to_string(),
511 end: "\"".to_string(),
512 close: true,
513 surround: true,
514 newline: false,
515 }),
516 Object::VerticalBars => Some(BracketPair {
517 start: "|".to_string(),
518 end: "|".to_string(),
519 close: true,
520 surround: true,
521 newline: false,
522 }),
523 Object::Parentheses => Some(BracketPair {
524 start: "(".to_string(),
525 end: ")".to_string(),
526 close: true,
527 surround: true,
528 newline: false,
529 }),
530 Object::SquareBrackets => Some(BracketPair {
531 start: "[".to_string(),
532 end: "]".to_string(),
533 close: true,
534 surround: true,
535 newline: false,
536 }),
537 Object::CurlyBrackets => Some(BracketPair {
538 start: "{".to_string(),
539 end: "}".to_string(),
540 close: true,
541 surround: true,
542 newline: false,
543 }),
544 Object::AngleBrackets => Some(BracketPair {
545 start: "<".to_string(),
546 end: ">".to_string(),
547 close: true,
548 surround: true,
549 newline: false,
550 }),
551 _ => None,
552 }
553}
554
555#[cfg(test)]
556mod test {
557 use gpui::KeyBinding;
558 use indoc::indoc;
559
560 use crate::{
561 state::{Mode, Operator},
562 test::VimTestContext,
563 PushOperator,
564 };
565
566 #[gpui::test]
567 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
568 let mut cx = VimTestContext::new(cx, true).await;
569
570 // test add surrounds 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 not with around
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 i w }");
596 cx.assert_state(
597 indoc! {"
598 The ˇ{quick} brown
599 fox jumps over
600 the lazy dog."},
601 Mode::Normal,
602 );
603
604 // test add surrounds with motion
605 cx.set_state(
606 indoc! {"
607 The quˇick brown
608 fox jumps over
609 the lazy dog."},
610 Mode::Normal,
611 );
612 cx.simulate_keystrokes("y s $ }");
613 cx.assert_state(
614 indoc! {"
615 The quˇ{ick brown}
616 fox jumps over
617 the lazy dog."},
618 Mode::Normal,
619 );
620
621 // test add surrounds with multi cursor
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 i w '");
630 cx.assert_state(
631 indoc! {"
632 The ˇ'quick' brown
633 fox jumps over
634 the ˇ'lazy' dog."},
635 Mode::Normal,
636 );
637
638 // test multi cursor add surrounds with motion
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 $ '");
647 cx.assert_state(
648 indoc! {"
649 The quˇ'ick brown'
650 fox jumps over
651 the laˇ'zy dog.'"},
652 Mode::Normal,
653 );
654
655 // test multi cursor add surrounds with motion and custom string
656 cx.set_state(
657 indoc! {"
658 The quˇick brown
659 fox jumps over
660 the laˇzy dog."},
661 Mode::Normal,
662 );
663 cx.simulate_keystrokes("y s $ 1");
664 cx.assert_state(
665 indoc! {"
666 The quˇ1ick brown1
667 fox jumps over
668 the laˇ1zy dog.1"},
669 Mode::Normal,
670 );
671
672 // test add surrounds with motion current line
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
689 cx.set_state(
690 indoc! {"
691 The quˇick brown•
692 fox jumps over
693 the lazy dog."},
694 Mode::Normal,
695 );
696 cx.simulate_keystrokes("y s s {");
697 cx.assert_state(
698 indoc! {"
699 ˇ{ The quick brown }•
700 fox jumps over
701 the lazy dog."},
702 Mode::Normal,
703 );
704 cx.simulate_keystrokes("2 y s s )");
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 around 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 a ] )");
722 cx.assert_state(
723 indoc! {"
724 The ˇ([quick]) brown
725 fox jumps over
726 the lazy dog."},
727 Mode::Normal,
728 );
729
730 // test add surrounds inside object
731 cx.set_state(
732 indoc! {"
733 The [quˇick] brown
734 fox jumps over
735 the lazy dog."},
736 Mode::Normal,
737 );
738 cx.simulate_keystrokes("y s i ] )");
739 cx.assert_state(
740 indoc! {"
741 The [ˇ(quick)] brown
742 fox jumps over
743 the lazy dog."},
744 Mode::Normal,
745 );
746 }
747
748 #[gpui::test]
749 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
750 let mut cx = VimTestContext::new(cx, true).await;
751
752 cx.update(|cx| {
753 cx.bind_keys([KeyBinding::new(
754 "shift-s",
755 PushOperator(Operator::AddSurrounds { target: None }),
756 Some("vim_mode == visual"),
757 )])
758 });
759
760 // test add surrounds 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 not with around
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 i w shift-s }");
786 cx.assert_state(
787 indoc! {"
788 The ˇ{quick} brown
789 fox jumps over
790 the lazy dog."},
791 Mode::Normal,
792 );
793
794 // test add surrounds with motion
795 cx.set_state(
796 indoc! {"
797 The quˇick brown
798 fox jumps over
799 the lazy dog."},
800 Mode::Normal,
801 );
802 cx.simulate_keystrokes("v e shift-s }");
803 cx.assert_state(
804 indoc! {"
805 The quˇ{ick} brown
806 fox jumps over
807 the lazy dog."},
808 Mode::Normal,
809 );
810
811 // test add surrounds with multi cursor
812 cx.set_state(
813 indoc! {"
814 The quˇick brown
815 fox jumps over
816 the laˇzy dog."},
817 Mode::Normal,
818 );
819 cx.simulate_keystrokes("v i w 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 block
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("ctrl-v i w j j shift-s '");
837 cx.assert_state(
838 indoc! {"
839 The ˇ'quick' brown
840 fox 'jumps' over
841 the 'lazy 'dog."},
842 Mode::Normal,
843 );
844
845 // test add surrounds with visual line
846 cx.set_state(
847 indoc! {"
848 The quˇick brown
849 fox jumps over
850 the lazy dog."},
851 Mode::Normal,
852 );
853 cx.simulate_keystrokes("j shift-v shift-s '");
854 cx.assert_state(
855 indoc! {"
856 The quick brown
857 ˇ'
858 fox jumps over
859 '
860 the lazy dog."},
861 Mode::Normal,
862 );
863 }
864
865 #[gpui::test]
866 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
867 let mut cx = VimTestContext::new(cx, true).await;
868
869 // test delete surround
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 ˇquick brown
881 fox jumps over
882 the lazy dog."},
883 Mode::Normal,
884 );
885
886 // test delete not exist surrounds
887 cx.set_state(
888 indoc! {"
889 The {quˇick} brown
890 fox jumps over
891 the lazy dog."},
892 Mode::Normal,
893 );
894 cx.simulate_keystrokes("d s [");
895 cx.assert_state(
896 indoc! {"
897 The {quˇick} brown
898 fox jumps over
899 the lazy dog."},
900 Mode::Normal,
901 );
902
903 // test delete surround forward exist, in the surrounds plugin of other editors,
904 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
905 cx.set_state(
906 indoc! {"
907 The {quick} brˇown
908 fox jumps over
909 the lazy dog."},
910 Mode::Normal,
911 );
912 cx.simulate_keystrokes("d s {");
913 cx.assert_state(
914 indoc! {"
915 The {quick} brˇown
916 fox jumps over
917 the lazy dog."},
918 Mode::Normal,
919 );
920
921 // test cursor delete inner surrounds
922 cx.set_state(
923 indoc! {"
924 The { quick brown
925 fox jumˇps over }
926 the lazy 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
939 cx.set_state(
940 indoc! {"
941 The [quˇick] 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 // test multi cursor delete surrounds with around
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 cx.set_state(
973 indoc! {"
974 Tˇhe [ quick ] brown
975 fox jumps over
976 the [laˇzy ] dog."},
977 Mode::Normal,
978 );
979 cx.simulate_keystrokes("d s [");
980 cx.assert_state(
981 indoc! {"
982 The ˇquick brown
983 fox jumps over
984 the ˇlazy dog."},
985 Mode::Normal,
986 );
987
988 // test multi cursor delete different surrounds
989 // the pair corresponding to the two cursors is the same,
990 // so they are combined into one cursor
991 cx.set_state(
992 indoc! {"
993 The [quˇick] brown
994 fox jumps over
995 the {laˇzy} dog."},
996 Mode::Normal,
997 );
998 cx.simulate_keystrokes("d s {");
999 cx.assert_state(
1000 indoc! {"
1001 The [quick] brown
1002 fox jumps over
1003 the ˇlazy dog."},
1004 Mode::Normal,
1005 );
1006
1007 // test delete surround with multi cursor and nest surrounds
1008 cx.set_state(
1009 indoc! {"
1010 fn test_surround() {
1011 ifˇ 2 > 1 {
1012 ˇprintln!(\"it is fine\");
1013 };
1014 }"},
1015 Mode::Normal,
1016 );
1017 cx.simulate_keystrokes("d s }");
1018 cx.assert_state(
1019 indoc! {"
1020 fn test_surround() ˇ
1021 if 2 > 1 ˇ
1022 println!(\"it is fine\");
1023 ;
1024 "},
1025 Mode::Normal,
1026 );
1027 }
1028
1029 #[gpui::test]
1030 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
1031 let mut cx = VimTestContext::new(cx, true).await;
1032
1033 cx.set_state(
1034 indoc! {"
1035 The {quˇick} brown
1036 fox jumps over
1037 the lazy 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 change surrounds
1050 cx.set_state(
1051 indoc! {"
1052 The {quˇick} 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 delete different surrounds with after cursor
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 surrount with not around
1084 cx.set_state(
1085 indoc! {"
1086 Thˇe { quick } 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 multi cursor change with not exist surround
1101 cx.set_state(
1102 indoc! {"
1103 The {quˇick} brown
1104 fox jumps over
1105 the [laˇzy] dog."},
1106 Mode::Normal,
1107 );
1108 cx.simulate_keystrokes("c s [ '");
1109 cx.assert_state(
1110 indoc! {"
1111 The {quick} brown
1112 fox jumps over
1113 the ˇ'lazy' dog."},
1114 Mode::Normal,
1115 );
1116
1117 // test change nesting surrounds
1118 cx.set_state(
1119 indoc! {"
1120 fn test_surround() {
1121 ifˇ 2 > 1 {
1122 ˇprintln!(\"it is fine\");
1123 }
1124 };"},
1125 Mode::Normal,
1126 );
1127 cx.simulate_keystrokes("c s { [");
1128 cx.assert_state(
1129 indoc! {"
1130 fn test_surround() ˇ[
1131 if 2 > 1 ˇ[
1132 println!(\"it is fine\");
1133 ]
1134 ];"},
1135 Mode::Normal,
1136 );
1137 }
1138
1139 #[gpui::test]
1140 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1141 let mut cx = VimTestContext::new(cx, true).await;
1142
1143 cx.set_state(
1144 indoc! {"
1145 The quˇick brown
1146 fox jumps over
1147 the lazy dog."},
1148 Mode::Normal,
1149 );
1150 cx.simulate_keystrokes("y s i w [");
1151 cx.assert_state(
1152 indoc! {"
1153 The ˇ[ quick ] brown
1154 fox jumps over
1155 the lazy dog."},
1156 Mode::Normal,
1157 );
1158
1159 cx.simulate_keystrokes("c s [ }");
1160 cx.assert_state(
1161 indoc! {"
1162 The ˇ{quick} brown
1163 fox jumps over
1164 the lazy dog."},
1165 Mode::Normal,
1166 );
1167
1168 cx.simulate_keystrokes("d s {");
1169 cx.assert_state(
1170 indoc! {"
1171 The ˇquick brown
1172 fox jumps over
1173 the lazy dog."},
1174 Mode::Normal,
1175 );
1176
1177 cx.simulate_keystrokes("u");
1178 cx.assert_state(
1179 indoc! {"
1180 The ˇ{quick} brown
1181 fox jumps over
1182 the lazy dog."},
1183 Mode::Normal,
1184 );
1185 }
1186
1187 #[gpui::test]
1188 async fn test_surround_aliases(cx: &mut gpui::TestAppContext) {
1189 let mut cx = VimTestContext::new(cx, true).await;
1190
1191 // add aliases
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 B");
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 a");
1232 cx.assert_state(
1233 indoc! {"
1234 The ˇ<quick> brown
1235 fox jumps over
1236 the lazy dog."},
1237 Mode::Normal,
1238 );
1239
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("y s i w r");
1248 cx.assert_state(
1249 indoc! {"
1250 The ˇ[quick] brown
1251 fox jumps over
1252 the lazy dog."},
1253 Mode::Normal,
1254 );
1255
1256 // change aliases
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 ( B");
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 ( a");
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 < b");
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 ( r");
1329 cx.assert_state(
1330 indoc! {"
1331 The ˇ[quick] brown
1332 fox jumps over
1333 the lazy dog."},
1334 Mode::Normal,
1335 );
1336
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("c 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 // delete alias
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 b");
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 r");
1394 cx.assert_state(
1395 indoc! {"
1396 The ˇquick brown
1397 fox jumps over
1398 the lazy dog."},
1399 Mode::Normal,
1400 );
1401
1402 cx.set_state(
1403 indoc! {"
1404 The <quˇick> brown
1405 fox jumps over
1406 the lazy dog."},
1407 Mode::Normal,
1408 );
1409 cx.simulate_keystrokes("d s a");
1410 cx.assert_state(
1411 indoc! {"
1412 The ˇquick brown
1413 fox jumps over
1414 the lazy dog."},
1415 Mode::Normal,
1416 );
1417 }
1418}