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