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),
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 != *text;
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) => {
63 object.range(&display_map, selection.clone(), false)
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 != *text;
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.iter().find(|pair| pair.start == ch || pair.end == ch)
397}
398
399fn all_support_surround_pair() -> Vec<BracketPair> {
400 vec![
401 BracketPair {
402 start: "{".into(),
403 end: "}".into(),
404 close: true,
405 surround: true,
406 newline: false,
407 },
408 BracketPair {
409 start: "'".into(),
410 end: "'".into(),
411 close: true,
412 surround: true,
413 newline: false,
414 },
415 BracketPair {
416 start: "`".into(),
417 end: "`".into(),
418 close: true,
419 surround: true,
420 newline: false,
421 },
422 BracketPair {
423 start: "\"".into(),
424 end: "\"".into(),
425 close: true,
426 surround: true,
427 newline: false,
428 },
429 BracketPair {
430 start: "(".into(),
431 end: ")".into(),
432 close: true,
433 surround: true,
434 newline: false,
435 },
436 BracketPair {
437 start: "|".into(),
438 end: "|".into(),
439 close: true,
440 surround: true,
441 newline: false,
442 },
443 BracketPair {
444 start: "[".into(),
445 end: "]".into(),
446 close: true,
447 surround: true,
448 newline: false,
449 },
450 BracketPair {
451 start: "{".into(),
452 end: "}".into(),
453 close: true,
454 surround: true,
455 newline: false,
456 },
457 BracketPair {
458 start: "<".into(),
459 end: ">".into(),
460 close: true,
461 surround: true,
462 newline: false,
463 },
464 ]
465}
466
467fn pair_to_object(pair: &BracketPair) -> Option<Object> {
468 match pair.start.as_str() {
469 "'" => Some(Object::Quotes),
470 "`" => Some(Object::BackQuotes),
471 "\"" => Some(Object::DoubleQuotes),
472 "|" => Some(Object::VerticalBars),
473 "(" => Some(Object::Parentheses),
474 "[" => Some(Object::SquareBrackets),
475 "{" => Some(Object::CurlyBrackets),
476 "<" => Some(Object::AngleBrackets),
477 _ => None,
478 }
479}
480
481fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
482 match object {
483 Object::Quotes => Some(BracketPair {
484 start: "'".to_string(),
485 end: "'".to_string(),
486 close: true,
487 surround: true,
488 newline: false,
489 }),
490 Object::BackQuotes => Some(BracketPair {
491 start: "`".to_string(),
492 end: "`".to_string(),
493 close: true,
494 surround: true,
495 newline: false,
496 }),
497 Object::DoubleQuotes => Some(BracketPair {
498 start: "\"".to_string(),
499 end: "\"".to_string(),
500 close: true,
501 surround: true,
502 newline: false,
503 }),
504 Object::VerticalBars => Some(BracketPair {
505 start: "|".to_string(),
506 end: "|".to_string(),
507 close: true,
508 surround: true,
509 newline: false,
510 }),
511 Object::Parentheses => Some(BracketPair {
512 start: "(".to_string(),
513 end: ")".to_string(),
514 close: true,
515 surround: true,
516 newline: false,
517 }),
518 Object::SquareBrackets => Some(BracketPair {
519 start: "[".to_string(),
520 end: "]".to_string(),
521 close: true,
522 surround: true,
523 newline: false,
524 }),
525 Object::CurlyBrackets => Some(BracketPair {
526 start: "{".to_string(),
527 end: "}".to_string(),
528 close: true,
529 surround: true,
530 newline: false,
531 }),
532 Object::AngleBrackets => Some(BracketPair {
533 start: "<".to_string(),
534 end: ">".to_string(),
535 close: true,
536 surround: true,
537 newline: false,
538 }),
539 _ => None,
540 }
541}
542
543#[cfg(test)]
544mod test {
545 use gpui::KeyBinding;
546 use indoc::indoc;
547
548 use crate::{
549 state::{Mode, Operator},
550 test::VimTestContext,
551 PushOperator,
552 };
553
554 #[gpui::test]
555 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
556 let mut cx = VimTestContext::new(cx, true).await;
557
558 // test add surrounds with around
559 cx.set_state(
560 indoc! {"
561 The quˇick brown
562 fox jumps over
563 the lazy dog."},
564 Mode::Normal,
565 );
566 cx.simulate_keystrokes("y s i w {");
567 cx.assert_state(
568 indoc! {"
569 The ˇ{ quick } brown
570 fox jumps over
571 the lazy dog."},
572 Mode::Normal,
573 );
574
575 // test add surrounds not with around
576 cx.set_state(
577 indoc! {"
578 The quˇick brown
579 fox jumps over
580 the lazy dog."},
581 Mode::Normal,
582 );
583 cx.simulate_keystrokes("y s i w }");
584 cx.assert_state(
585 indoc! {"
586 The ˇ{quick} brown
587 fox jumps over
588 the lazy dog."},
589 Mode::Normal,
590 );
591
592 // test add surrounds with motion
593 cx.set_state(
594 indoc! {"
595 The quˇick brown
596 fox jumps over
597 the lazy dog."},
598 Mode::Normal,
599 );
600 cx.simulate_keystrokes("y s $ }");
601 cx.assert_state(
602 indoc! {"
603 The quˇ{ick brown}
604 fox jumps over
605 the lazy dog."},
606 Mode::Normal,
607 );
608
609 // test add surrounds with multi cursor
610 cx.set_state(
611 indoc! {"
612 The quˇick brown
613 fox jumps over
614 the laˇzy dog."},
615 Mode::Normal,
616 );
617 cx.simulate_keystrokes("y s i w '");
618 cx.assert_state(
619 indoc! {"
620 The ˇ'quick' brown
621 fox jumps over
622 the ˇ'lazy' dog."},
623 Mode::Normal,
624 );
625
626 // test multi cursor add surrounds with motion
627 cx.set_state(
628 indoc! {"
629 The quˇick brown
630 fox jumps over
631 the laˇzy dog."},
632 Mode::Normal,
633 );
634 cx.simulate_keystrokes("y s $ '");
635 cx.assert_state(
636 indoc! {"
637 The quˇ'ick brown'
638 fox jumps over
639 the laˇ'zy dog.'"},
640 Mode::Normal,
641 );
642
643 // test multi cursor add surrounds with motion and custom string
644 cx.set_state(
645 indoc! {"
646 The quˇick brown
647 fox jumps over
648 the laˇzy dog."},
649 Mode::Normal,
650 );
651 cx.simulate_keystrokes("y s $ 1");
652 cx.assert_state(
653 indoc! {"
654 The quˇ1ick brown1
655 fox jumps over
656 the laˇ1zy dog.1"},
657 Mode::Normal,
658 );
659
660 // test add surrounds with motion current line
661 cx.set_state(
662 indoc! {"
663 The quˇick brown
664 fox jumps over
665 the lazy dog."},
666 Mode::Normal,
667 );
668 cx.simulate_keystrokes("y s s {");
669 cx.assert_state(
670 indoc! {"
671 ˇ{ The quick brown }
672 fox jumps over
673 the lazy dog."},
674 Mode::Normal,
675 );
676
677 cx.set_state(
678 indoc! {"
679 The quˇick brown•
680 fox jumps over
681 the lazy dog."},
682 Mode::Normal,
683 );
684 cx.simulate_keystrokes("y s s {");
685 cx.assert_state(
686 indoc! {"
687 ˇ{ The quick brown }•
688 fox jumps over
689 the lazy dog."},
690 Mode::Normal,
691 );
692 cx.simulate_keystrokes("2 y s s )");
693 cx.assert_state(
694 indoc! {"
695 ˇ({ The quick brown }•
696 fox jumps over)
697 the lazy dog."},
698 Mode::Normal,
699 );
700 }
701
702 #[gpui::test]
703 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
704 let mut cx = VimTestContext::new(cx, true).await;
705
706 cx.update(|cx| {
707 cx.bind_keys([KeyBinding::new(
708 "shift-s",
709 PushOperator(Operator::AddSurrounds { target: None }),
710 Some("vim_mode == visual"),
711 )])
712 });
713
714 // test add surrounds with around
715 cx.set_state(
716 indoc! {"
717 The quˇick brown
718 fox jumps over
719 the lazy dog."},
720 Mode::Normal,
721 );
722 cx.simulate_keystrokes("v i w shift-s {");
723 cx.assert_state(
724 indoc! {"
725 The ˇ{ quick } brown
726 fox jumps over
727 the lazy dog."},
728 Mode::Normal,
729 );
730
731 // test add surrounds not with around
732 cx.set_state(
733 indoc! {"
734 The quˇick brown
735 fox jumps over
736 the lazy dog."},
737 Mode::Normal,
738 );
739 cx.simulate_keystrokes("v i w shift-s }");
740 cx.assert_state(
741 indoc! {"
742 The ˇ{quick} brown
743 fox jumps over
744 the lazy dog."},
745 Mode::Normal,
746 );
747
748 // test add surrounds with motion
749 cx.set_state(
750 indoc! {"
751 The quˇick brown
752 fox jumps over
753 the lazy dog."},
754 Mode::Normal,
755 );
756 cx.simulate_keystrokes("v e shift-s }");
757 cx.assert_state(
758 indoc! {"
759 The quˇ{ick} brown
760 fox jumps over
761 the lazy dog."},
762 Mode::Normal,
763 );
764
765 // test add surrounds with multi cursor
766 cx.set_state(
767 indoc! {"
768 The quˇick brown
769 fox jumps over
770 the laˇzy dog."},
771 Mode::Normal,
772 );
773 cx.simulate_keystrokes("v i w shift-s '");
774 cx.assert_state(
775 indoc! {"
776 The ˇ'quick' brown
777 fox jumps over
778 the ˇ'lazy' dog."},
779 Mode::Normal,
780 );
781
782 // test add surrounds with visual block
783 cx.set_state(
784 indoc! {"
785 The quˇick brown
786 fox jumps over
787 the lazy dog."},
788 Mode::Normal,
789 );
790 cx.simulate_keystrokes("ctrl-v i w j j shift-s '");
791 cx.assert_state(
792 indoc! {"
793 The ˇ'quick' brown
794 fox 'jumps' over
795 the 'lazy 'dog."},
796 Mode::Normal,
797 );
798
799 // test add surrounds with visual line
800 cx.set_state(
801 indoc! {"
802 The quˇick brown
803 fox jumps over
804 the lazy dog."},
805 Mode::Normal,
806 );
807 cx.simulate_keystrokes("j shift-v shift-s '");
808 cx.assert_state(
809 indoc! {"
810 The quick brown
811 ˇ'
812 fox jumps over
813 '
814 the lazy dog."},
815 Mode::Normal,
816 );
817 }
818
819 #[gpui::test]
820 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
821 let mut cx = VimTestContext::new(cx, true).await;
822
823 // test delete surround
824 cx.set_state(
825 indoc! {"
826 The {quˇick} brown
827 fox jumps over
828 the lazy dog."},
829 Mode::Normal,
830 );
831 cx.simulate_keystrokes("d s {");
832 cx.assert_state(
833 indoc! {"
834 The ˇquick brown
835 fox jumps over
836 the lazy dog."},
837 Mode::Normal,
838 );
839
840 // test delete not exist surrounds
841 cx.set_state(
842 indoc! {"
843 The {quˇick} brown
844 fox jumps over
845 the lazy dog."},
846 Mode::Normal,
847 );
848 cx.simulate_keystrokes("d s [");
849 cx.assert_state(
850 indoc! {"
851 The {quˇick} brown
852 fox jumps over
853 the lazy dog."},
854 Mode::Normal,
855 );
856
857 // test delete surround forward exist, in the surrounds plugin of other editors,
858 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
859 cx.set_state(
860 indoc! {"
861 The {quick} brˇown
862 fox jumps over
863 the lazy dog."},
864 Mode::Normal,
865 );
866 cx.simulate_keystrokes("d s {");
867 cx.assert_state(
868 indoc! {"
869 The {quick} brˇown
870 fox jumps over
871 the lazy dog."},
872 Mode::Normal,
873 );
874
875 // test cursor delete inner surrounds
876 cx.set_state(
877 indoc! {"
878 The { quick brown
879 fox jumˇps over }
880 the lazy dog."},
881 Mode::Normal,
882 );
883 cx.simulate_keystrokes("d s {");
884 cx.assert_state(
885 indoc! {"
886 The ˇquick brown
887 fox jumps over
888 the lazy dog."},
889 Mode::Normal,
890 );
891
892 // test multi cursor delete surrounds
893 cx.set_state(
894 indoc! {"
895 The [quˇick] brown
896 fox jumps over
897 the [laˇzy] dog."},
898 Mode::Normal,
899 );
900 cx.simulate_keystrokes("d s ]");
901 cx.assert_state(
902 indoc! {"
903 The ˇquick brown
904 fox jumps over
905 the ˇlazy dog."},
906 Mode::Normal,
907 );
908
909 // test multi cursor delete surrounds with around
910 cx.set_state(
911 indoc! {"
912 Tˇhe [ quick ] brown
913 fox jumps over
914 the [laˇzy] dog."},
915 Mode::Normal,
916 );
917 cx.simulate_keystrokes("d s [");
918 cx.assert_state(
919 indoc! {"
920 The ˇquick brown
921 fox jumps over
922 the ˇlazy dog."},
923 Mode::Normal,
924 );
925
926 cx.set_state(
927 indoc! {"
928 Tˇhe [ quick ] brown
929 fox jumps over
930 the [laˇzy ] dog."},
931 Mode::Normal,
932 );
933 cx.simulate_keystrokes("d s [");
934 cx.assert_state(
935 indoc! {"
936 The ˇquick brown
937 fox jumps over
938 the ˇlazy dog."},
939 Mode::Normal,
940 );
941
942 // test multi cursor delete different surrounds
943 // the pair corresponding to the two cursors is the same,
944 // so they are combined into one cursor
945 cx.set_state(
946 indoc! {"
947 The [quˇick] brown
948 fox jumps over
949 the {laˇzy} dog."},
950 Mode::Normal,
951 );
952 cx.simulate_keystrokes("d s {");
953 cx.assert_state(
954 indoc! {"
955 The [quick] brown
956 fox jumps over
957 the ˇlazy dog."},
958 Mode::Normal,
959 );
960
961 // test delete surround with multi cursor and nest surrounds
962 cx.set_state(
963 indoc! {"
964 fn test_surround() {
965 ifˇ 2 > 1 {
966 ˇprintln!(\"it is fine\");
967 };
968 }"},
969 Mode::Normal,
970 );
971 cx.simulate_keystrokes("d s }");
972 cx.assert_state(
973 indoc! {"
974 fn test_surround() ˇ
975 if 2 > 1 ˇ
976 println!(\"it is fine\");
977 ;
978 "},
979 Mode::Normal,
980 );
981 }
982
983 #[gpui::test]
984 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
985 let mut cx = VimTestContext::new(cx, true).await;
986
987 cx.set_state(
988 indoc! {"
989 The {quˇick} brown
990 fox jumps over
991 the lazy dog."},
992 Mode::Normal,
993 );
994 cx.simulate_keystrokes("c s { [");
995 cx.assert_state(
996 indoc! {"
997 The ˇ[ quick ] brown
998 fox jumps over
999 the lazy dog."},
1000 Mode::Normal,
1001 );
1002
1003 // test multi cursor change surrounds
1004 cx.set_state(
1005 indoc! {"
1006 The {quˇick} brown
1007 fox jumps over
1008 the {laˇzy} dog."},
1009 Mode::Normal,
1010 );
1011 cx.simulate_keystrokes("c s { [");
1012 cx.assert_state(
1013 indoc! {"
1014 The ˇ[ quick ] brown
1015 fox jumps over
1016 the ˇ[ lazy ] dog."},
1017 Mode::Normal,
1018 );
1019
1020 // test multi cursor delete different surrounds with after cursor
1021 cx.set_state(
1022 indoc! {"
1023 Thˇe {quick} brown
1024 fox jumps over
1025 the {laˇzy} dog."},
1026 Mode::Normal,
1027 );
1028 cx.simulate_keystrokes("c s { [");
1029 cx.assert_state(
1030 indoc! {"
1031 The ˇ[ quick ] brown
1032 fox jumps over
1033 the ˇ[ lazy ] dog."},
1034 Mode::Normal,
1035 );
1036
1037 // test multi cursor change surrount with not around
1038 cx.set_state(
1039 indoc! {"
1040 Thˇe { quick } brown
1041 fox jumps over
1042 the {laˇzy} dog."},
1043 Mode::Normal,
1044 );
1045 cx.simulate_keystrokes("c s { ]");
1046 cx.assert_state(
1047 indoc! {"
1048 The ˇ[quick] brown
1049 fox jumps over
1050 the ˇ[lazy] dog."},
1051 Mode::Normal,
1052 );
1053
1054 // test multi cursor change with not exist surround
1055 cx.set_state(
1056 indoc! {"
1057 The {quˇick} brown
1058 fox jumps over
1059 the [laˇzy] dog."},
1060 Mode::Normal,
1061 );
1062 cx.simulate_keystrokes("c s [ '");
1063 cx.assert_state(
1064 indoc! {"
1065 The {quick} brown
1066 fox jumps over
1067 the ˇ'lazy' dog."},
1068 Mode::Normal,
1069 );
1070
1071 // test change nesting surrounds
1072 cx.set_state(
1073 indoc! {"
1074 fn test_surround() {
1075 ifˇ 2 > 1 {
1076 ˇprintln!(\"it is fine\");
1077 }
1078 };"},
1079 Mode::Normal,
1080 );
1081 cx.simulate_keystrokes("c s { [");
1082 cx.assert_state(
1083 indoc! {"
1084 fn test_surround() ˇ[
1085 if 2 > 1 ˇ[
1086 println!(\"it is fine\");
1087 ]
1088 ];"},
1089 Mode::Normal,
1090 );
1091 }
1092
1093 #[gpui::test]
1094 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1095 let mut cx = VimTestContext::new(cx, true).await;
1096
1097 cx.set_state(
1098 indoc! {"
1099 The quˇick brown
1100 fox jumps over
1101 the lazy dog."},
1102 Mode::Normal,
1103 );
1104 cx.simulate_keystrokes("y s i w [");
1105 cx.assert_state(
1106 indoc! {"
1107 The ˇ[ quick ] brown
1108 fox jumps over
1109 the lazy dog."},
1110 Mode::Normal,
1111 );
1112
1113 cx.simulate_keystrokes("c s [ }");
1114 cx.assert_state(
1115 indoc! {"
1116 The ˇ{quick} brown
1117 fox jumps over
1118 the lazy dog."},
1119 Mode::Normal,
1120 );
1121
1122 cx.simulate_keystrokes("d s {");
1123 cx.assert_state(
1124 indoc! {"
1125 The ˇquick brown
1126 fox jumps over
1127 the lazy dog."},
1128 Mode::Normal,
1129 );
1130
1131 cx.simulate_keystrokes("u");
1132 cx.assert_state(
1133 indoc! {"
1134 The ˇ{quick} brown
1135 fox jumps over
1136 the lazy dog."},
1137 Mode::Normal,
1138 );
1139 }
1140}