1use crate::{
2 motion::{self, Motion},
3 object::Object,
4 state::Mode,
5 Vim,
6};
7use editor::{movement, scroll::Autoscroll, Bias};
8use gpui::WindowContext;
9use language::BracketPair;
10use serde::Deserialize;
11use std::sync::Arc;
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum SurroundsType {
14 Motion(Motion),
15 Object(Object),
16}
17
18// This exists so that we can have Deserialize on Operators, but not on Motions.
19impl<'de> Deserialize<'de> for SurroundsType {
20 fn deserialize<D>(_: D) -> Result<Self, D::Error>
21 where
22 D: serde::Deserializer<'de>,
23 {
24 Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
25 }
26}
27
28pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
29 Vim::update(cx, |vim, cx| {
30 vim.stop_recording();
31 let count = vim.take_count(cx);
32 vim.update_active_editor(cx, |_, editor, cx| {
33 let text_layout_details = editor.text_layout_details(cx);
34 editor.transact(cx, |editor, cx| {
35 editor.set_clip_at_line_ends(false, cx);
36
37 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
38 Some(pair) => pair.clone(),
39 None => BracketPair {
40 start: text.to_string(),
41 end: text.to_string(),
42 close: true,
43 newline: false,
44 },
45 };
46 let surround = pair.end != *text;
47 let (display_map, display_selections) = editor.selections.all_adjusted_display(cx);
48 let mut edits = Vec::new();
49 let mut anchors = Vec::new();
50
51 for selection in &display_selections {
52 let range = match &target {
53 SurroundsType::Object(object) => {
54 object.range(&display_map, selection.clone(), false)
55 }
56 SurroundsType::Motion(motion) => {
57 let range = motion
58 .range(
59 &display_map,
60 selection.clone(),
61 count,
62 true,
63 &text_layout_details,
64 )
65 .map(|mut range| {
66 // The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
67 if let Motion::CurrentLine = motion {
68 range.start = motion::first_non_whitespace(
69 &display_map,
70 false,
71 range.start,
72 );
73 range.end = movement::saturating_right(
74 &display_map,
75 motion::last_non_whitespace(
76 &display_map,
77 movement::left(&display_map, range.end),
78 1,
79 ),
80 );
81 }
82 range
83 });
84 range
85 }
86 };
87
88 if let Some(range) = range {
89 let start = range.start.to_offset(&display_map, Bias::Right);
90 let end = range.end.to_offset(&display_map, Bias::Left);
91 let start_cursor_str =
92 format!("{}{}", pair.start, if surround { " " } else { "" });
93 let close_cursor_str =
94 format!("{}{}", if surround { " " } else { "" }, pair.end);
95 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
96
97 edits.push((start..start, start_cursor_str));
98 edits.push((end..end, close_cursor_str));
99 anchors.push(start_anchor..start_anchor);
100 } else {
101 let start_anchor = display_map
102 .buffer_snapshot
103 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
104 anchors.push(start_anchor..start_anchor);
105 }
106 }
107
108 editor.buffer().update(cx, |buffer, cx| {
109 buffer.edit(edits, None, cx);
110 });
111 editor.set_clip_at_line_ends(true, cx);
112 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
113 s.select_anchor_ranges(anchors)
114 });
115 });
116 });
117 vim.switch_mode(Mode::Normal, false, cx);
118 });
119}
120
121pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
122 Vim::update(cx, |vim, cx| {
123 vim.stop_recording();
124
125 // only legitimate surrounds can be removed
126 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
127 Some(pair) => pair.clone(),
128 None => return,
129 };
130 let pair_object = match pair_to_object(&pair) {
131 Some(pair_object) => pair_object,
132 None => return,
133 };
134 let surround = pair.end != *text;
135
136 vim.update_active_editor(cx, |_, editor, cx| {
137 editor.transact(cx, |editor, cx| {
138 editor.set_clip_at_line_ends(false, cx);
139
140 let (display_map, display_selections) = editor.selections.all_display(cx);
141 let mut edits = Vec::new();
142 let mut anchors = Vec::new();
143
144 for selection in &display_selections {
145 let start = selection.start.to_offset(&display_map, Bias::Left);
146 if let Some(range) = pair_object.range(&display_map, selection.clone(), true) {
147 // If the current parenthesis object is single-line,
148 // then we need to filter whether it is the current line or not
149 if !pair_object.is_multiline() {
150 let is_same_row = selection.start.row() == range.start.row()
151 && selection.end.row() == range.end.row();
152 if !is_same_row {
153 anchors.push(start..start);
154 continue;
155 }
156 }
157 // This is a bit cumbersome, and it is written to deal with some special cases, as shown below
158 // hello«ˇ "hello in a word" »again.
159 // Sometimes the expand_selection will not be matched at both ends, and there will be extra spaces
160 // In order to be able to accurately match and replace in this case, some cumbersome methods are used
161 let mut chars_and_offset = display_map
162 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
163 .peekable();
164 while let Some((ch, offset)) = chars_and_offset.next() {
165 if ch.to_string() == pair.start {
166 let start = offset;
167 let mut end = start + 1;
168 if surround {
169 if let Some((next_ch, _)) = chars_and_offset.peek() {
170 if next_ch.eq(&' ') {
171 end += 1;
172 }
173 }
174 }
175 edits.push((start..end, ""));
176 anchors.push(start..start);
177 break;
178 }
179 }
180 let mut reverse_chars_and_offsets = display_map
181 .reverse_buffer_chars_at(range.end.to_offset(&display_map, Bias::Left))
182 .peekable();
183 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
184 if ch.to_string() == pair.end {
185 let mut start = offset;
186 let end = start + 1;
187 if surround {
188 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
189 if next_ch.eq(&' ') {
190 start -= 1;
191 }
192 }
193 }
194 edits.push((start..end, ""));
195 break;
196 }
197 }
198 } else {
199 anchors.push(start..start);
200 }
201 }
202
203 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
204 s.select_ranges(anchors);
205 });
206 edits.sort_by_key(|(range, _)| range.start);
207 editor.buffer().update(cx, |buffer, cx| {
208 buffer.edit(edits, None, cx);
209 });
210 editor.set_clip_at_line_ends(true, cx);
211 });
212 });
213 });
214}
215
216pub fn change_surrounds(text: Arc<str>, target: Object, cx: &mut WindowContext) {
217 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
218 Vim::update(cx, |vim, cx| {
219 vim.stop_recording();
220 vim.update_active_editor(cx, |_, editor, cx| {
221 editor.transact(cx, |editor, cx| {
222 editor.set_clip_at_line_ends(false, cx);
223
224 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
225 Some(pair) => pair.clone(),
226 None => BracketPair {
227 start: text.to_string(),
228 end: text.to_string(),
229 close: true,
230 newline: false,
231 },
232 };
233 let surround = pair.end != *text;
234 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
235 let mut edits = Vec::new();
236 let mut anchors = Vec::new();
237
238 for selection in &selections {
239 let start = selection.start.to_offset(&display_map, Bias::Left);
240 if let Some(range) = target.range(&display_map, selection.clone(), true) {
241 if !target.is_multiline() {
242 let is_same_row = selection.start.row() == range.start.row()
243 && selection.end.row() == range.end.row();
244 if !is_same_row {
245 anchors.push(start..start);
246 continue;
247 }
248 }
249 let mut chars_and_offset = display_map
250 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
251 .peekable();
252 while let Some((ch, offset)) = chars_and_offset.next() {
253 if ch.to_string() == will_replace_pair.start {
254 let mut open_str = pair.start.clone();
255 let start = offset;
256 let mut end = start + 1;
257 match chars_and_offset.peek() {
258 Some((next_ch, _)) => {
259 // If the next position is already a space or line break,
260 // we don't need to splice another space even under arround
261 if surround && !next_ch.is_whitespace() {
262 open_str.push_str(" ");
263 } else if !surround && next_ch.to_string() == " " {
264 end += 1;
265 }
266 }
267 None => {}
268 }
269 edits.push((start..end, open_str));
270 anchors.push(start..start);
271 break;
272 }
273 }
274
275 let mut reverse_chars_and_offsets = display_map
276 .reverse_buffer_chars_at(
277 range.end.to_offset(&display_map, Bias::Left),
278 )
279 .peekable();
280 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
281 if ch.to_string() == will_replace_pair.end {
282 let mut close_str = pair.end.clone();
283 let mut start = offset;
284 let end = start + 1;
285 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
286 if surround && !next_ch.is_whitespace() {
287 close_str.insert_str(0, " ")
288 } else if !surround && next_ch.to_string() == " " {
289 start -= 1;
290 }
291 }
292 edits.push((start..end, close_str));
293 break;
294 }
295 }
296 } else {
297 anchors.push(start..start);
298 }
299 }
300
301 let stable_anchors = editor
302 .selections
303 .disjoint_anchors()
304 .into_iter()
305 .map(|selection| {
306 let start = selection.start.bias_left(&display_map.buffer_snapshot);
307 start..start
308 })
309 .collect::<Vec<_>>();
310 edits.sort_by_key(|(range, _)| range.start);
311 editor.buffer().update(cx, |buffer, cx| {
312 buffer.edit(edits, None, cx);
313 });
314 editor.set_clip_at_line_ends(true, cx);
315 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
316 s.select_anchor_ranges(stable_anchors);
317 });
318 });
319 });
320 });
321 }
322}
323
324/// Checks if any of the current cursors are surrounded by a valid pair of brackets.
325///
326/// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
327/// A pair of brackets is considered valid if it is well-formed and properly closed.
328///
329/// 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.
330/// If no valid pair of brackets is found for any cursor, the method returns `false`.
331pub fn check_and_move_to_valid_bracket_pair(
332 vim: &mut Vim,
333 object: Object,
334 cx: &mut WindowContext,
335) -> bool {
336 let mut valid = false;
337 if let Some(pair) = object_to_bracket_pair(object) {
338 vim.update_active_editor(cx, |_, editor, cx| {
339 editor.transact(cx, |editor, cx| {
340 editor.set_clip_at_line_ends(false, cx);
341 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
342 let mut anchors = Vec::new();
343
344 for selection in &selections {
345 let start = selection.start.to_offset(&display_map, Bias::Left);
346 if let Some(range) = object.range(&display_map, selection.clone(), true) {
347 // If the current parenthesis object is single-line,
348 // then we need to filter whether it is the current line or not
349 if object.is_multiline()
350 || (!object.is_multiline()
351 && selection.start.row() == range.start.row()
352 && selection.end.row() == range.end.row())
353 {
354 valid = true;
355 let mut chars_and_offset = display_map
356 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
357 .peekable();
358 while let Some((ch, offset)) = chars_and_offset.next() {
359 if ch.to_string() == pair.start {
360 anchors.push(offset..offset);
361 break;
362 }
363 }
364 } else {
365 anchors.push(start..start)
366 }
367 } else {
368 anchors.push(start..start)
369 }
370 }
371 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
372 s.select_ranges(anchors);
373 });
374 editor.set_clip_at_line_ends(true, cx);
375 });
376 });
377 }
378 return valid;
379}
380
381fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
382 pairs.iter().find(|pair| pair.start == ch || pair.end == ch)
383}
384
385fn all_support_surround_pair() -> Vec<BracketPair> {
386 return vec![
387 BracketPair {
388 start: "{".into(),
389 end: "}".into(),
390 close: true,
391 newline: false,
392 },
393 BracketPair {
394 start: "'".into(),
395 end: "'".into(),
396 close: true,
397 newline: false,
398 },
399 BracketPair {
400 start: "`".into(),
401 end: "`".into(),
402 close: true,
403 newline: false,
404 },
405 BracketPair {
406 start: "\"".into(),
407 end: "\"".into(),
408 close: true,
409 newline: false,
410 },
411 BracketPair {
412 start: "(".into(),
413 end: ")".into(),
414 close: true,
415 newline: false,
416 },
417 BracketPair {
418 start: "|".into(),
419 end: "|".into(),
420 close: true,
421 newline: false,
422 },
423 BracketPair {
424 start: "[".into(),
425 end: "]".into(),
426 close: true,
427 newline: false,
428 },
429 BracketPair {
430 start: "{".into(),
431 end: "}".into(),
432 close: true,
433 newline: false,
434 },
435 BracketPair {
436 start: "<".into(),
437 end: ">".into(),
438 close: true,
439 newline: false,
440 },
441 ];
442}
443
444fn pair_to_object(pair: &BracketPair) -> Option<Object> {
445 match pair.start.as_str() {
446 "'" => Some(Object::Quotes),
447 "`" => Some(Object::BackQuotes),
448 "\"" => Some(Object::DoubleQuotes),
449 "|" => Some(Object::VerticalBars),
450 "(" => Some(Object::Parentheses),
451 "[" => Some(Object::SquareBrackets),
452 "{" => Some(Object::CurlyBrackets),
453 "<" => Some(Object::AngleBrackets),
454 _ => None,
455 }
456}
457
458fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
459 match object {
460 Object::Quotes => Some(BracketPair {
461 start: "'".to_string(),
462 end: "'".to_string(),
463 close: true,
464 newline: false,
465 }),
466 Object::BackQuotes => Some(BracketPair {
467 start: "`".to_string(),
468 end: "`".to_string(),
469 close: true,
470 newline: false,
471 }),
472 Object::DoubleQuotes => Some(BracketPair {
473 start: "\"".to_string(),
474 end: "\"".to_string(),
475 close: true,
476 newline: false,
477 }),
478 Object::VerticalBars => Some(BracketPair {
479 start: "|".to_string(),
480 end: "|".to_string(),
481 close: true,
482 newline: false,
483 }),
484 Object::Parentheses => Some(BracketPair {
485 start: "(".to_string(),
486 end: ")".to_string(),
487 close: true,
488 newline: false,
489 }),
490 Object::SquareBrackets => Some(BracketPair {
491 start: "[".to_string(),
492 end: "]".to_string(),
493 close: true,
494 newline: false,
495 }),
496 Object::CurlyBrackets => Some(BracketPair {
497 start: "{".to_string(),
498 end: "}".to_string(),
499 close: true,
500 newline: false,
501 }),
502 Object::AngleBrackets => Some(BracketPair {
503 start: "<".to_string(),
504 end: ">".to_string(),
505 close: true,
506 newline: false,
507 }),
508 _ => None,
509 }
510}
511
512#[cfg(test)]
513mod test {
514 use indoc::indoc;
515
516 use crate::{state::Mode, test::VimTestContext};
517
518 #[gpui::test]
519 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
520 let mut cx = VimTestContext::new(cx, true).await;
521
522 // test add surrounds with arround
523 cx.set_state(
524 indoc! {"
525 The quˇick brown
526 fox jumps over
527 the lazy dog."},
528 Mode::Normal,
529 );
530 cx.simulate_keystrokes("y s i w {");
531 cx.assert_state(
532 indoc! {"
533 The ˇ{ quick } brown
534 fox jumps over
535 the lazy dog."},
536 Mode::Normal,
537 );
538
539 // test add surrounds not with arround
540 cx.set_state(
541 indoc! {"
542 The quˇick brown
543 fox jumps over
544 the lazy dog."},
545 Mode::Normal,
546 );
547 cx.simulate_keystrokes("y s i w }");
548 cx.assert_state(
549 indoc! {"
550 The ˇ{quick} brown
551 fox jumps over
552 the lazy dog."},
553 Mode::Normal,
554 );
555
556 // test add surrounds with motion
557 cx.set_state(
558 indoc! {"
559 The quˇick brown
560 fox jumps over
561 the lazy dog."},
562 Mode::Normal,
563 );
564 cx.simulate_keystrokes("y s $ }");
565 cx.assert_state(
566 indoc! {"
567 The quˇ{ick brown}
568 fox jumps over
569 the lazy dog."},
570 Mode::Normal,
571 );
572
573 // test add surrounds with multi cursor
574 cx.set_state(
575 indoc! {"
576 The quˇick brown
577 fox jumps over
578 the laˇzy dog."},
579 Mode::Normal,
580 );
581 cx.simulate_keystrokes("y s i w '");
582 cx.assert_state(
583 indoc! {"
584 The ˇ'quick' brown
585 fox jumps over
586 the ˇ'lazy' dog."},
587 Mode::Normal,
588 );
589
590 // test multi cursor add surrounds with motion
591 cx.set_state(
592 indoc! {"
593 The quˇick brown
594 fox jumps over
595 the laˇzy dog."},
596 Mode::Normal,
597 );
598 cx.simulate_keystrokes("y s $ '");
599 cx.assert_state(
600 indoc! {"
601 The quˇ'ick brown'
602 fox jumps over
603 the laˇ'zy dog.'"},
604 Mode::Normal,
605 );
606
607 // test multi cursor add surrounds with motion and custom string
608 cx.set_state(
609 indoc! {"
610 The quˇick brown
611 fox jumps over
612 the laˇzy dog."},
613 Mode::Normal,
614 );
615 cx.simulate_keystrokes("y s $ 1");
616 cx.assert_state(
617 indoc! {"
618 The quˇ1ick brown1
619 fox jumps over
620 the laˇ1zy dog.1"},
621 Mode::Normal,
622 );
623
624 // test add surrounds with motion current line
625 cx.set_state(
626 indoc! {"
627 The quˇick brown
628 fox jumps over
629 the lazy dog."},
630 Mode::Normal,
631 );
632 cx.simulate_keystrokes("y s s {");
633 cx.assert_state(
634 indoc! {"
635 ˇ{ The quick brown }
636 fox jumps over
637 the lazy dog."},
638 Mode::Normal,
639 );
640
641 cx.set_state(
642 indoc! {"
643 The quˇick brown•
644 fox jumps over
645 the lazy dog."},
646 Mode::Normal,
647 );
648 cx.simulate_keystrokes("y s s {");
649 cx.assert_state(
650 indoc! {"
651 ˇ{ The quick brown }•
652 fox jumps over
653 the lazy dog."},
654 Mode::Normal,
655 );
656 cx.simulate_keystrokes("2 y s s )");
657 cx.assert_state(
658 indoc! {"
659 ˇ({ The quick brown }•
660 fox jumps over)
661 the lazy dog."},
662 Mode::Normal,
663 );
664 }
665
666 #[gpui::test]
667 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
668 let mut cx = VimTestContext::new(cx, true).await;
669
670 // test delete surround
671 cx.set_state(
672 indoc! {"
673 The {quˇick} brown
674 fox jumps over
675 the lazy dog."},
676 Mode::Normal,
677 );
678 cx.simulate_keystrokes("d s {");
679 cx.assert_state(
680 indoc! {"
681 The ˇquick brown
682 fox jumps over
683 the lazy dog."},
684 Mode::Normal,
685 );
686
687 // test delete not exist surrounds
688 cx.set_state(
689 indoc! {"
690 The {quˇick} brown
691 fox jumps over
692 the lazy dog."},
693 Mode::Normal,
694 );
695 cx.simulate_keystrokes("d s [");
696 cx.assert_state(
697 indoc! {"
698 The {quˇick} brown
699 fox jumps over
700 the lazy dog."},
701 Mode::Normal,
702 );
703
704 // test delete surround forward exist, in the surrounds plugin of other editors,
705 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
706 cx.set_state(
707 indoc! {"
708 The {quick} brˇown
709 fox jumps over
710 the lazy dog."},
711 Mode::Normal,
712 );
713 cx.simulate_keystrokes("d s {");
714 cx.assert_state(
715 indoc! {"
716 The {quick} brˇown
717 fox jumps over
718 the lazy dog."},
719 Mode::Normal,
720 );
721
722 // test cursor delete inner surrounds
723 cx.set_state(
724 indoc! {"
725 The { quick brown
726 fox jumˇps over }
727 the lazy dog."},
728 Mode::Normal,
729 );
730 cx.simulate_keystrokes("d s {");
731 cx.assert_state(
732 indoc! {"
733 The ˇquick brown
734 fox jumps over
735 the lazy dog."},
736 Mode::Normal,
737 );
738
739 // test multi cursor delete surrounds
740 cx.set_state(
741 indoc! {"
742 The [quˇick] brown
743 fox jumps over
744 the [laˇzy] dog."},
745 Mode::Normal,
746 );
747 cx.simulate_keystrokes("d s ]");
748 cx.assert_state(
749 indoc! {"
750 The ˇquick brown
751 fox jumps over
752 the ˇlazy dog."},
753 Mode::Normal,
754 );
755
756 // test multi cursor delete surrounds with arround
757 cx.set_state(
758 indoc! {"
759 Tˇhe [ quick ] brown
760 fox jumps over
761 the [laˇzy] dog."},
762 Mode::Normal,
763 );
764 cx.simulate_keystrokes("d s [");
765 cx.assert_state(
766 indoc! {"
767 The ˇquick brown
768 fox jumps over
769 the ˇlazy dog."},
770 Mode::Normal,
771 );
772
773 cx.set_state(
774 indoc! {"
775 Tˇhe [ quick ] brown
776 fox jumps over
777 the [laˇzy ] dog."},
778 Mode::Normal,
779 );
780 cx.simulate_keystrokes("d s [");
781 cx.assert_state(
782 indoc! {"
783 The ˇquick brown
784 fox jumps over
785 the ˇlazy dog."},
786 Mode::Normal,
787 );
788
789 // test multi cursor delete different surrounds
790 // the pair corresponding to the two cursors is the same,
791 // so they are combined into one cursor
792 cx.set_state(
793 indoc! {"
794 The [quˇick] brown
795 fox jumps over
796 the {laˇzy} dog."},
797 Mode::Normal,
798 );
799 cx.simulate_keystrokes("d s {");
800 cx.assert_state(
801 indoc! {"
802 The [quick] brown
803 fox jumps over
804 the ˇlazy dog."},
805 Mode::Normal,
806 );
807
808 // test delete surround with multi cursor and nest surrounds
809 cx.set_state(
810 indoc! {"
811 fn test_surround() {
812 ifˇ 2 > 1 {
813 ˇprintln!(\"it is fine\");
814 };
815 }"},
816 Mode::Normal,
817 );
818 cx.simulate_keystrokes("d s }");
819 cx.assert_state(
820 indoc! {"
821 fn test_surround() ˇ
822 if 2 > 1 ˇ
823 println!(\"it is fine\");
824 ;
825 "},
826 Mode::Normal,
827 );
828 }
829
830 #[gpui::test]
831 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
832 let mut cx = VimTestContext::new(cx, true).await;
833
834 cx.set_state(
835 indoc! {"
836 The {quˇick} brown
837 fox jumps over
838 the lazy dog."},
839 Mode::Normal,
840 );
841 cx.simulate_keystrokes("c s { [");
842 cx.assert_state(
843 indoc! {"
844 The ˇ[ quick ] brown
845 fox jumps over
846 the lazy dog."},
847 Mode::Normal,
848 );
849
850 // test multi cursor change surrounds
851 cx.set_state(
852 indoc! {"
853 The {quˇick} brown
854 fox jumps over
855 the {laˇzy} dog."},
856 Mode::Normal,
857 );
858 cx.simulate_keystrokes("c s { [");
859 cx.assert_state(
860 indoc! {"
861 The ˇ[ quick ] brown
862 fox jumps over
863 the ˇ[ lazy ] dog."},
864 Mode::Normal,
865 );
866
867 // test multi cursor delete different surrounds with after cursor
868 cx.set_state(
869 indoc! {"
870 Thˇe {quick} brown
871 fox jumps over
872 the {laˇzy} dog."},
873 Mode::Normal,
874 );
875 cx.simulate_keystrokes("c s { [");
876 cx.assert_state(
877 indoc! {"
878 The ˇ[ quick ] brown
879 fox jumps over
880 the ˇ[ lazy ] dog."},
881 Mode::Normal,
882 );
883
884 // test multi cursor change surrount with not arround
885 cx.set_state(
886 indoc! {"
887 Thˇe { quick } brown
888 fox jumps over
889 the {laˇzy} dog."},
890 Mode::Normal,
891 );
892 cx.simulate_keystrokes("c s { ]");
893 cx.assert_state(
894 indoc! {"
895 The ˇ[quick] brown
896 fox jumps over
897 the ˇ[lazy] dog."},
898 Mode::Normal,
899 );
900
901 // test multi cursor change with not exist surround
902 cx.set_state(
903 indoc! {"
904 The {quˇick} brown
905 fox jumps over
906 the [laˇzy] dog."},
907 Mode::Normal,
908 );
909 cx.simulate_keystrokes("c s [ '");
910 cx.assert_state(
911 indoc! {"
912 The {quick} brown
913 fox jumps over
914 the ˇ'lazy' dog."},
915 Mode::Normal,
916 );
917
918 // test change nesting surrounds
919 cx.set_state(
920 indoc! {"
921 fn test_surround() {
922 ifˇ 2 > 1 {
923 ˇprintln!(\"it is fine\");
924 }
925 };"},
926 Mode::Normal,
927 );
928 cx.simulate_keystrokes("c s { [");
929 cx.assert_state(
930 indoc! {"
931 fn test_surround() ˇ[
932 if 2 > 1 ˇ[
933 println!(\"it is fine\");
934 ]
935 ];"},
936 Mode::Normal,
937 );
938 }
939
940 #[gpui::test]
941 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
942 let mut cx = VimTestContext::new(cx, true).await;
943
944 cx.set_state(
945 indoc! {"
946 The quˇick brown
947 fox jumps over
948 the lazy dog."},
949 Mode::Normal,
950 );
951 cx.simulate_keystrokes("y s i w [");
952 cx.assert_state(
953 indoc! {"
954 The ˇ[ quick ] brown
955 fox jumps over
956 the lazy dog."},
957 Mode::Normal,
958 );
959
960 cx.simulate_keystrokes("c s [ }");
961 cx.assert_state(
962 indoc! {"
963 The ˇ{quick} brown
964 fox jumps over
965 the lazy dog."},
966 Mode::Normal,
967 );
968
969 cx.simulate_keystrokes("d s {");
970 cx.assert_state(
971 indoc! {"
972 The ˇquick brown
973 fox jumps over
974 the lazy dog."},
975 Mode::Normal,
976 );
977
978 cx.simulate_keystrokes("u");
979 cx.assert_state(
980 indoc! {"
981 The ˇ{quick} brown
982 fox jumps over
983 the lazy dog."},
984 Mode::Normal,
985 );
986 }
987}