surrounds.rs

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