surrounds.rs

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