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
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
30pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
31 Vim::update(cx, |vim, cx| {
32 vim.stop_recording();
33 let count = vim.take_count(cx);
34 let mode = vim.state().mode;
35 vim.update_active_editor(cx, |_, editor, cx| {
36 let text_layout_details = editor.text_layout_details(cx);
37 editor.transact(cx, |editor, cx| {
38 editor.set_clip_at_line_ends(false, cx);
39
40 let pair = match find_surround_pair(&all_support_surround_pair(), &text) {
41 Some(pair) => pair.clone(),
42 None => BracketPair {
43 start: text.to_string(),
44 end: text.to_string(),
45 close: true,
46 surround: true,
47 newline: false,
48 },
49 };
50 let surround = pair.end != *text;
51 let (display_map, display_selections) = editor.selections.all_adjusted_display(cx);
52 let mut edits = Vec::new();
53 let mut anchors = Vec::new();
54
55 for selection in &display_selections {
56 let range = match &target {
57 SurroundsType::Object(object) => {
58 object.range(&display_map, selection.clone(), false)
59 }
60 SurroundsType::Motion(motion) => {
61 let range = motion
62 .range(
63 &display_map,
64 selection.clone(),
65 count,
66 true,
67 &text_layout_details,
68 )
69 .map(|mut range| {
70 // The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
71 if let Motion::CurrentLine = motion {
72 range.start = motion::first_non_whitespace(
73 &display_map,
74 false,
75 range.start,
76 );
77 range.end = movement::saturating_right(
78 &display_map,
79 motion::last_non_whitespace(
80 &display_map,
81 movement::left(&display_map, range.end),
82 1,
83 ),
84 );
85 }
86 range
87 });
88 range
89 }
90 SurroundsType::Selection => Some(selection.range()),
91 };
92
93 if let Some(range) = range {
94 let start = range.start.to_offset(&display_map, Bias::Right);
95 let end = range.end.to_offset(&display_map, Bias::Left);
96 let (start_cursor_str, end_cursor_str) = if mode == Mode::VisualLine {
97 (format!("{}\n", pair.start), format!("{}\n", pair.end))
98 } else {
99 let maybe_space = if surround { " " } else { "" };
100 (
101 format!("{}{}", pair.start, maybe_space),
102 format!("{}{}", maybe_space, pair.end),
103 )
104 };
105 let start_anchor = display_map.buffer_snapshot.anchor_before(start);
106
107 edits.push((start..start, start_cursor_str));
108 edits.push((end..end, end_cursor_str));
109 anchors.push(start_anchor..start_anchor);
110 } else {
111 let start_anchor = display_map
112 .buffer_snapshot
113 .anchor_before(selection.head().to_offset(&display_map, Bias::Left));
114 anchors.push(start_anchor..start_anchor);
115 }
116 }
117
118 editor.buffer().update(cx, |buffer, cx| {
119 buffer.edit(edits, None, cx);
120 });
121 editor.set_clip_at_line_ends(true, cx);
122 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
123 if mode == Mode::VisualBlock {
124 s.select_anchor_ranges(anchors.into_iter().take(1))
125 } else {
126 s.select_anchor_ranges(anchors)
127 }
128 });
129 });
130 });
131 vim.switch_mode(Mode::Normal, false, cx);
132 });
133}
134
135pub fn delete_surrounds(text: Arc<str>, cx: &mut WindowContext) {
136 Vim::update(cx, |vim, cx| {
137 vim.stop_recording();
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 vim.update_active_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.buffer().update(cx, |buffer, cx| {
222 buffer.edit(edits, None, cx);
223 });
224 editor.set_clip_at_line_ends(true, cx);
225 });
226 });
227 });
228}
229
230pub fn change_surrounds(text: Arc<str>, target: Object, cx: &mut WindowContext) {
231 if let Some(will_replace_pair) = object_to_bracket_pair(target) {
232 Vim::update(cx, |vim, cx| {
233 vim.stop_recording();
234 vim.update_active_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 match chars_and_offset.peek() {
273 Some((next_ch, _)) => {
274 // If the next position is already a space or line break,
275 // we don't need to splice another space even under arround
276 if surround && !next_ch.is_whitespace() {
277 open_str.push_str(" ");
278 } else if !surround && next_ch.to_string() == " " {
279 end += 1;
280 }
281 }
282 None => {}
283 }
284 edits.push((start..end, open_str));
285 anchors.push(start..start);
286 break;
287 }
288 }
289
290 let mut reverse_chars_and_offsets = display_map
291 .reverse_buffer_chars_at(
292 range.end.to_offset(&display_map, Bias::Left),
293 )
294 .peekable();
295 while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
296 if ch.to_string() == will_replace_pair.end {
297 let mut close_str = pair.end.clone();
298 let mut start = offset;
299 let end = start + 1;
300 if let Some((next_ch, _)) = reverse_chars_and_offsets.peek() {
301 if surround && !next_ch.is_whitespace() {
302 close_str.insert_str(0, " ")
303 } else if !surround && next_ch.to_string() == " " {
304 start -= 1;
305 }
306 }
307 edits.push((start..end, close_str));
308 break;
309 }
310 }
311 } else {
312 anchors.push(start..start);
313 }
314 }
315
316 let stable_anchors = editor
317 .selections
318 .disjoint_anchors()
319 .into_iter()
320 .map(|selection| {
321 let start = selection.start.bias_left(&display_map.buffer_snapshot);
322 start..start
323 })
324 .collect::<Vec<_>>();
325 edits.sort_by_key(|(range, _)| range.start);
326 editor.buffer().update(cx, |buffer, cx| {
327 buffer.edit(edits, None, cx);
328 });
329 editor.set_clip_at_line_ends(true, cx);
330 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
331 s.select_anchor_ranges(stable_anchors);
332 });
333 });
334 });
335 });
336 }
337}
338
339/// Checks if any of the current cursors are surrounded by a valid pair of brackets.
340///
341/// This method supports multiple cursors and checks each cursor for a valid pair of brackets.
342/// A pair of brackets is considered valid if it is well-formed and properly closed.
343///
344/// 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.
345/// If no valid pair of brackets is found for any cursor, the method returns `false`.
346pub fn check_and_move_to_valid_bracket_pair(
347 vim: &mut Vim,
348 object: Object,
349 cx: &mut WindowContext,
350) -> bool {
351 let mut valid = false;
352 if let Some(pair) = object_to_bracket_pair(object) {
353 vim.update_active_editor(cx, |_, editor, cx| {
354 editor.transact(cx, |editor, cx| {
355 editor.set_clip_at_line_ends(false, cx);
356 let (display_map, selections) = editor.selections.all_adjusted_display(cx);
357 let mut anchors = Vec::new();
358
359 for selection in &selections {
360 let start = selection.start.to_offset(&display_map, Bias::Left);
361 if let Some(range) = object.range(&display_map, selection.clone(), true) {
362 // If the current parenthesis object is single-line,
363 // then we need to filter whether it is the current line or not
364 if object.is_multiline()
365 || (!object.is_multiline()
366 && selection.start.row() == range.start.row()
367 && selection.end.row() == range.end.row())
368 {
369 valid = true;
370 let mut chars_and_offset = display_map
371 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
372 .peekable();
373 while let Some((ch, offset)) = chars_and_offset.next() {
374 if ch.to_string() == pair.start {
375 anchors.push(offset..offset);
376 break;
377 }
378 }
379 } else {
380 anchors.push(start..start)
381 }
382 } else {
383 anchors.push(start..start)
384 }
385 }
386 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
387 s.select_ranges(anchors);
388 });
389 editor.set_clip_at_line_ends(true, cx);
390 });
391 });
392 }
393 return valid;
394}
395
396fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> {
397 pairs.iter().find(|pair| pair.start == ch || pair.end == ch)
398}
399
400fn all_support_surround_pair() -> Vec<BracketPair> {
401 return vec![
402 BracketPair {
403 start: "{".into(),
404 end: "}".into(),
405 close: true,
406 surround: true,
407 newline: false,
408 },
409 BracketPair {
410 start: "'".into(),
411 end: "'".into(),
412 close: true,
413 surround: true,
414 newline: false,
415 },
416 BracketPair {
417 start: "`".into(),
418 end: "`".into(),
419 close: true,
420 surround: true,
421 newline: false,
422 },
423 BracketPair {
424 start: "\"".into(),
425 end: "\"".into(),
426 close: true,
427 surround: true,
428 newline: false,
429 },
430 BracketPair {
431 start: "(".into(),
432 end: ")".into(),
433 close: true,
434 surround: true,
435 newline: false,
436 },
437 BracketPair {
438 start: "|".into(),
439 end: "|".into(),
440 close: true,
441 surround: true,
442 newline: false,
443 },
444 BracketPair {
445 start: "[".into(),
446 end: "]".into(),
447 close: true,
448 surround: true,
449 newline: false,
450 },
451 BracketPair {
452 start: "{".into(),
453 end: "}".into(),
454 close: true,
455 surround: true,
456 newline: false,
457 },
458 BracketPair {
459 start: "<".into(),
460 end: ">".into(),
461 close: true,
462 surround: true,
463 newline: false,
464 },
465 ];
466}
467
468fn pair_to_object(pair: &BracketPair) -> Option<Object> {
469 match pair.start.as_str() {
470 "'" => Some(Object::Quotes),
471 "`" => Some(Object::BackQuotes),
472 "\"" => Some(Object::DoubleQuotes),
473 "|" => Some(Object::VerticalBars),
474 "(" => Some(Object::Parentheses),
475 "[" => Some(Object::SquareBrackets),
476 "{" => Some(Object::CurlyBrackets),
477 "<" => Some(Object::AngleBrackets),
478 _ => None,
479 }
480}
481
482fn object_to_bracket_pair(object: Object) -> Option<BracketPair> {
483 match object {
484 Object::Quotes => Some(BracketPair {
485 start: "'".to_string(),
486 end: "'".to_string(),
487 close: true,
488 surround: true,
489 newline: false,
490 }),
491 Object::BackQuotes => Some(BracketPair {
492 start: "`".to_string(),
493 end: "`".to_string(),
494 close: true,
495 surround: true,
496 newline: false,
497 }),
498 Object::DoubleQuotes => Some(BracketPair {
499 start: "\"".to_string(),
500 end: "\"".to_string(),
501 close: true,
502 surround: true,
503 newline: false,
504 }),
505 Object::VerticalBars => Some(BracketPair {
506 start: "|".to_string(),
507 end: "|".to_string(),
508 close: true,
509 surround: true,
510 newline: false,
511 }),
512 Object::Parentheses => Some(BracketPair {
513 start: "(".to_string(),
514 end: ")".to_string(),
515 close: true,
516 surround: true,
517 newline: false,
518 }),
519 Object::SquareBrackets => Some(BracketPair {
520 start: "[".to_string(),
521 end: "]".to_string(),
522 close: true,
523 surround: true,
524 newline: false,
525 }),
526 Object::CurlyBrackets => Some(BracketPair {
527 start: "{".to_string(),
528 end: "}".to_string(),
529 close: true,
530 surround: true,
531 newline: false,
532 }),
533 Object::AngleBrackets => Some(BracketPair {
534 start: "<".to_string(),
535 end: ">".to_string(),
536 close: true,
537 surround: true,
538 newline: false,
539 }),
540 _ => None,
541 }
542}
543
544#[cfg(test)]
545mod test {
546 use gpui::KeyBinding;
547 use indoc::indoc;
548
549 use crate::{
550 state::{Mode, Operator},
551 test::VimTestContext,
552 PushOperator,
553 };
554
555 #[gpui::test]
556 async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
557 let mut cx = VimTestContext::new(cx, true).await;
558
559 // test add surrounds with arround
560 cx.set_state(
561 indoc! {"
562 The quˇick brown
563 fox jumps over
564 the lazy dog."},
565 Mode::Normal,
566 );
567 cx.simulate_keystrokes("y s i w {");
568 cx.assert_state(
569 indoc! {"
570 The ˇ{ quick } brown
571 fox jumps over
572 the lazy dog."},
573 Mode::Normal,
574 );
575
576 // test add surrounds not with arround
577 cx.set_state(
578 indoc! {"
579 The quˇick brown
580 fox jumps over
581 the lazy dog."},
582 Mode::Normal,
583 );
584 cx.simulate_keystrokes("y s i w }");
585 cx.assert_state(
586 indoc! {"
587 The ˇ{quick} brown
588 fox jumps over
589 the lazy dog."},
590 Mode::Normal,
591 );
592
593 // test add surrounds with motion
594 cx.set_state(
595 indoc! {"
596 The quˇick brown
597 fox jumps over
598 the lazy dog."},
599 Mode::Normal,
600 );
601 cx.simulate_keystrokes("y s $ }");
602 cx.assert_state(
603 indoc! {"
604 The quˇ{ick brown}
605 fox jumps over
606 the lazy dog."},
607 Mode::Normal,
608 );
609
610 // test add surrounds with multi cursor
611 cx.set_state(
612 indoc! {"
613 The quˇick brown
614 fox jumps over
615 the laˇzy dog."},
616 Mode::Normal,
617 );
618 cx.simulate_keystrokes("y s i w '");
619 cx.assert_state(
620 indoc! {"
621 The ˇ'quick' brown
622 fox jumps over
623 the ˇ'lazy' dog."},
624 Mode::Normal,
625 );
626
627 // test multi cursor add surrounds with motion
628 cx.set_state(
629 indoc! {"
630 The quˇick brown
631 fox jumps over
632 the laˇzy dog."},
633 Mode::Normal,
634 );
635 cx.simulate_keystrokes("y s $ '");
636 cx.assert_state(
637 indoc! {"
638 The quˇ'ick brown'
639 fox jumps over
640 the laˇ'zy dog.'"},
641 Mode::Normal,
642 );
643
644 // test multi cursor add surrounds with motion and custom string
645 cx.set_state(
646 indoc! {"
647 The quˇick brown
648 fox jumps over
649 the laˇzy dog."},
650 Mode::Normal,
651 );
652 cx.simulate_keystrokes("y s $ 1");
653 cx.assert_state(
654 indoc! {"
655 The quˇ1ick brown1
656 fox jumps over
657 the laˇ1zy dog.1"},
658 Mode::Normal,
659 );
660
661 // test add surrounds with motion current line
662 cx.set_state(
663 indoc! {"
664 The quˇick brown
665 fox jumps over
666 the lazy dog."},
667 Mode::Normal,
668 );
669 cx.simulate_keystrokes("y s s {");
670 cx.assert_state(
671 indoc! {"
672 ˇ{ The quick brown }
673 fox jumps over
674 the lazy dog."},
675 Mode::Normal,
676 );
677
678 cx.set_state(
679 indoc! {"
680 The quˇick brown•
681 fox jumps over
682 the lazy dog."},
683 Mode::Normal,
684 );
685 cx.simulate_keystrokes("y s s {");
686 cx.assert_state(
687 indoc! {"
688 ˇ{ The quick brown }•
689 fox jumps over
690 the lazy dog."},
691 Mode::Normal,
692 );
693 cx.simulate_keystrokes("2 y s s )");
694 cx.assert_state(
695 indoc! {"
696 ˇ({ The quick brown }•
697 fox jumps over)
698 the lazy dog."},
699 Mode::Normal,
700 );
701 }
702
703 #[gpui::test]
704 async fn test_add_surrounds_visual(cx: &mut gpui::TestAppContext) {
705 let mut cx = VimTestContext::new(cx, true).await;
706
707 cx.update(|cx| {
708 cx.bind_keys([KeyBinding::new(
709 "shift-s",
710 PushOperator(Operator::AddSurrounds { target: None }),
711 Some("vim_mode == visual"),
712 )])
713 });
714
715 // test add surrounds with arround
716 cx.set_state(
717 indoc! {"
718 The quˇick brown
719 fox jumps over
720 the lazy dog."},
721 Mode::Normal,
722 );
723 cx.simulate_keystrokes("v i w shift-s {");
724 cx.assert_state(
725 indoc! {"
726 The ˇ{ quick } brown
727 fox jumps over
728 the lazy dog."},
729 Mode::Normal,
730 );
731
732 // test add surrounds not with arround
733 cx.set_state(
734 indoc! {"
735 The quˇick brown
736 fox jumps over
737 the lazy dog."},
738 Mode::Normal,
739 );
740 cx.simulate_keystrokes("v i w shift-s }");
741 cx.assert_state(
742 indoc! {"
743 The ˇ{quick} brown
744 fox jumps over
745 the lazy dog."},
746 Mode::Normal,
747 );
748
749 // test add surrounds with motion
750 cx.set_state(
751 indoc! {"
752 The quˇick brown
753 fox jumps over
754 the lazy dog."},
755 Mode::Normal,
756 );
757 cx.simulate_keystrokes("v e shift-s }");
758 cx.assert_state(
759 indoc! {"
760 The quˇ{ick} brown
761 fox jumps over
762 the lazy dog."},
763 Mode::Normal,
764 );
765
766 // test add surrounds with multi cursor
767 cx.set_state(
768 indoc! {"
769 The quˇick brown
770 fox jumps over
771 the laˇzy dog."},
772 Mode::Normal,
773 );
774 cx.simulate_keystrokes("v i w shift-s '");
775 cx.assert_state(
776 indoc! {"
777 The ˇ'quick' brown
778 fox jumps over
779 the ˇ'lazy' dog."},
780 Mode::Normal,
781 );
782
783 // test add surrounds with visual block
784 cx.set_state(
785 indoc! {"
786 The quˇick brown
787 fox jumps over
788 the lazy dog."},
789 Mode::Normal,
790 );
791 cx.simulate_keystrokes("ctrl-v i w j j shift-s '");
792 cx.assert_state(
793 indoc! {"
794 The ˇ'quick' brown
795 fox 'jumps' over
796 the 'lazy 'dog."},
797 Mode::Normal,
798 );
799
800 // test add surrounds with visual line
801 cx.set_state(
802 indoc! {"
803 The quˇick brown
804 fox jumps over
805 the lazy dog."},
806 Mode::Normal,
807 );
808 cx.simulate_keystrokes("j shift-v shift-s '");
809 cx.assert_state(
810 indoc! {"
811 The quick brown
812 ˇ'
813 fox jumps over
814 '
815 the lazy dog."},
816 Mode::Normal,
817 );
818 }
819
820 #[gpui::test]
821 async fn test_delete_surrounds(cx: &mut gpui::TestAppContext) {
822 let mut cx = VimTestContext::new(cx, true).await;
823
824 // test delete surround
825 cx.set_state(
826 indoc! {"
827 The {quˇick} brown
828 fox jumps over
829 the lazy dog."},
830 Mode::Normal,
831 );
832 cx.simulate_keystrokes("d s {");
833 cx.assert_state(
834 indoc! {"
835 The ˇquick brown
836 fox jumps over
837 the lazy dog."},
838 Mode::Normal,
839 );
840
841 // test delete not exist surrounds
842 cx.set_state(
843 indoc! {"
844 The {quˇick} brown
845 fox jumps over
846 the lazy dog."},
847 Mode::Normal,
848 );
849 cx.simulate_keystrokes("d s [");
850 cx.assert_state(
851 indoc! {"
852 The {quˇick} brown
853 fox jumps over
854 the lazy dog."},
855 Mode::Normal,
856 );
857
858 // test delete surround forward exist, in the surrounds plugin of other editors,
859 // the bracket pair in front of the current line will be deleted here, which is not implemented at the moment
860 cx.set_state(
861 indoc! {"
862 The {quick} brˇown
863 fox jumps over
864 the lazy dog."},
865 Mode::Normal,
866 );
867 cx.simulate_keystrokes("d s {");
868 cx.assert_state(
869 indoc! {"
870 The {quick} brˇown
871 fox jumps over
872 the lazy dog."},
873 Mode::Normal,
874 );
875
876 // test cursor delete inner surrounds
877 cx.set_state(
878 indoc! {"
879 The { quick brown
880 fox jumˇps over }
881 the lazy dog."},
882 Mode::Normal,
883 );
884 cx.simulate_keystrokes("d s {");
885 cx.assert_state(
886 indoc! {"
887 The ˇquick brown
888 fox jumps over
889 the lazy dog."},
890 Mode::Normal,
891 );
892
893 // test multi cursor delete surrounds
894 cx.set_state(
895 indoc! {"
896 The [quˇick] brown
897 fox jumps over
898 the [laˇzy] dog."},
899 Mode::Normal,
900 );
901 cx.simulate_keystrokes("d s ]");
902 cx.assert_state(
903 indoc! {"
904 The ˇquick brown
905 fox jumps over
906 the ˇlazy dog."},
907 Mode::Normal,
908 );
909
910 // test multi cursor delete surrounds with arround
911 cx.set_state(
912 indoc! {"
913 Tˇhe [ quick ] brown
914 fox jumps over
915 the [laˇzy] dog."},
916 Mode::Normal,
917 );
918 cx.simulate_keystrokes("d s [");
919 cx.assert_state(
920 indoc! {"
921 The ˇquick brown
922 fox jumps over
923 the ˇlazy dog."},
924 Mode::Normal,
925 );
926
927 cx.set_state(
928 indoc! {"
929 Tˇhe [ quick ] brown
930 fox jumps over
931 the [laˇzy ] dog."},
932 Mode::Normal,
933 );
934 cx.simulate_keystrokes("d s [");
935 cx.assert_state(
936 indoc! {"
937 The ˇquick brown
938 fox jumps over
939 the ˇlazy dog."},
940 Mode::Normal,
941 );
942
943 // test multi cursor delete different surrounds
944 // the pair corresponding to the two cursors is the same,
945 // so they are combined into one cursor
946 cx.set_state(
947 indoc! {"
948 The [quˇick] brown
949 fox jumps over
950 the {laˇzy} dog."},
951 Mode::Normal,
952 );
953 cx.simulate_keystrokes("d s {");
954 cx.assert_state(
955 indoc! {"
956 The [quick] brown
957 fox jumps over
958 the ˇlazy dog."},
959 Mode::Normal,
960 );
961
962 // test delete surround with multi cursor and nest surrounds
963 cx.set_state(
964 indoc! {"
965 fn test_surround() {
966 ifˇ 2 > 1 {
967 ˇprintln!(\"it is fine\");
968 };
969 }"},
970 Mode::Normal,
971 );
972 cx.simulate_keystrokes("d s }");
973 cx.assert_state(
974 indoc! {"
975 fn test_surround() ˇ
976 if 2 > 1 ˇ
977 println!(\"it is fine\");
978 ;
979 "},
980 Mode::Normal,
981 );
982 }
983
984 #[gpui::test]
985 async fn test_change_surrounds(cx: &mut gpui::TestAppContext) {
986 let mut cx = VimTestContext::new(cx, true).await;
987
988 cx.set_state(
989 indoc! {"
990 The {quˇick} brown
991 fox jumps over
992 the lazy dog."},
993 Mode::Normal,
994 );
995 cx.simulate_keystrokes("c s { [");
996 cx.assert_state(
997 indoc! {"
998 The ˇ[ quick ] brown
999 fox jumps over
1000 the lazy dog."},
1001 Mode::Normal,
1002 );
1003
1004 // test multi cursor change surrounds
1005 cx.set_state(
1006 indoc! {"
1007 The {quˇick} brown
1008 fox jumps over
1009 the {laˇzy} dog."},
1010 Mode::Normal,
1011 );
1012 cx.simulate_keystrokes("c s { [");
1013 cx.assert_state(
1014 indoc! {"
1015 The ˇ[ quick ] brown
1016 fox jumps over
1017 the ˇ[ lazy ] dog."},
1018 Mode::Normal,
1019 );
1020
1021 // test multi cursor delete different surrounds with after cursor
1022 cx.set_state(
1023 indoc! {"
1024 Thˇe {quick} brown
1025 fox jumps over
1026 the {laˇzy} dog."},
1027 Mode::Normal,
1028 );
1029 cx.simulate_keystrokes("c s { [");
1030 cx.assert_state(
1031 indoc! {"
1032 The ˇ[ quick ] brown
1033 fox jumps over
1034 the ˇ[ lazy ] dog."},
1035 Mode::Normal,
1036 );
1037
1038 // test multi cursor change surrount with not arround
1039 cx.set_state(
1040 indoc! {"
1041 Thˇe { quick } brown
1042 fox jumps over
1043 the {laˇzy} dog."},
1044 Mode::Normal,
1045 );
1046 cx.simulate_keystrokes("c s { ]");
1047 cx.assert_state(
1048 indoc! {"
1049 The ˇ[quick] brown
1050 fox jumps over
1051 the ˇ[lazy] dog."},
1052 Mode::Normal,
1053 );
1054
1055 // test multi cursor change with not exist surround
1056 cx.set_state(
1057 indoc! {"
1058 The {quˇick} brown
1059 fox jumps over
1060 the [laˇzy] dog."},
1061 Mode::Normal,
1062 );
1063 cx.simulate_keystrokes("c s [ '");
1064 cx.assert_state(
1065 indoc! {"
1066 The {quick} brown
1067 fox jumps over
1068 the ˇ'lazy' dog."},
1069 Mode::Normal,
1070 );
1071
1072 // test change nesting surrounds
1073 cx.set_state(
1074 indoc! {"
1075 fn test_surround() {
1076 ifˇ 2 > 1 {
1077 ˇprintln!(\"it is fine\");
1078 }
1079 };"},
1080 Mode::Normal,
1081 );
1082 cx.simulate_keystrokes("c s { [");
1083 cx.assert_state(
1084 indoc! {"
1085 fn test_surround() ˇ[
1086 if 2 > 1 ˇ[
1087 println!(\"it is fine\");
1088 ]
1089 ];"},
1090 Mode::Normal,
1091 );
1092 }
1093
1094 #[gpui::test]
1095 async fn test_surrounds(cx: &mut gpui::TestAppContext) {
1096 let mut cx = VimTestContext::new(cx, true).await;
1097
1098 cx.set_state(
1099 indoc! {"
1100 The quˇick brown
1101 fox jumps over
1102 the lazy dog."},
1103 Mode::Normal,
1104 );
1105 cx.simulate_keystrokes("y s i w [");
1106 cx.assert_state(
1107 indoc! {"
1108 The ˇ[ quick ] brown
1109 fox jumps over
1110 the lazy dog."},
1111 Mode::Normal,
1112 );
1113
1114 cx.simulate_keystrokes("c s [ }");
1115 cx.assert_state(
1116 indoc! {"
1117 The ˇ{quick} brown
1118 fox jumps over
1119 the lazy dog."},
1120 Mode::Normal,
1121 );
1122
1123 cx.simulate_keystrokes("d s {");
1124 cx.assert_state(
1125 indoc! {"
1126 The ˇquick brown
1127 fox jumps over
1128 the lazy dog."},
1129 Mode::Normal,
1130 );
1131
1132 cx.simulate_keystrokes("u");
1133 cx.assert_state(
1134 indoc! {"
1135 The ˇ{quick} brown
1136 fox jumps over
1137 the lazy dog."},
1138 Mode::Normal,
1139 );
1140 }
1141}