normal.rs

   1mod change;
   2mod convert;
   3mod delete;
   4mod increment;
   5pub(crate) mod mark;
   6mod paste;
   7pub(crate) mod repeat;
   8mod scroll;
   9pub(crate) mod search;
  10pub mod substitute;
  11mod toggle_comments;
  12pub(crate) mod yank;
  13
  14use std::collections::HashMap;
  15use std::sync::Arc;
  16
  17use crate::{
  18    Vim,
  19    indent::IndentDirection,
  20    motion::{self, Motion, first_non_whitespace, next_line_end, right},
  21    object::Object,
  22    state::{Mark, Mode, Operator},
  23    surrounds::SurroundsType,
  24};
  25use collections::BTreeSet;
  26use convert::ConvertTarget;
  27use editor::Anchor;
  28use editor::Bias;
  29use editor::Editor;
  30use editor::scroll::Autoscroll;
  31use editor::{display_map::ToDisplayPoint, movement};
  32use gpui::{Context, Window, actions};
  33use language::{Point, SelectionGoal, ToPoint};
  34use log::error;
  35use multi_buffer::MultiBufferRow;
  36
  37actions!(
  38    vim,
  39    [
  40        InsertAfter,
  41        InsertBefore,
  42        InsertFirstNonWhitespace,
  43        InsertEndOfLine,
  44        InsertLineAbove,
  45        InsertLineBelow,
  46        InsertEmptyLineAbove,
  47        InsertEmptyLineBelow,
  48        InsertAtPrevious,
  49        JoinLines,
  50        JoinLinesNoWhitespace,
  51        DeleteLeft,
  52        DeleteRight,
  53        HelixDelete,
  54        ChangeToEndOfLine,
  55        DeleteToEndOfLine,
  56        Yank,
  57        YankLine,
  58        ChangeCase,
  59        ConvertToUpperCase,
  60        ConvertToLowerCase,
  61        ConvertToRot13,
  62        ConvertToRot47,
  63        ToggleComments,
  64        ShowLocation,
  65        Undo,
  66        Redo,
  67    ]
  68);
  69
  70pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
  71    Vim::action(editor, cx, Vim::insert_after);
  72    Vim::action(editor, cx, Vim::insert_before);
  73    Vim::action(editor, cx, Vim::insert_first_non_whitespace);
  74    Vim::action(editor, cx, Vim::insert_end_of_line);
  75    Vim::action(editor, cx, Vim::insert_line_above);
  76    Vim::action(editor, cx, Vim::insert_line_below);
  77    Vim::action(editor, cx, Vim::insert_empty_line_above);
  78    Vim::action(editor, cx, Vim::insert_empty_line_below);
  79    Vim::action(editor, cx, Vim::insert_at_previous);
  80    Vim::action(editor, cx, Vim::change_case);
  81    Vim::action(editor, cx, Vim::convert_to_upper_case);
  82    Vim::action(editor, cx, Vim::convert_to_lower_case);
  83    Vim::action(editor, cx, Vim::convert_to_rot13);
  84    Vim::action(editor, cx, Vim::convert_to_rot47);
  85    Vim::action(editor, cx, Vim::yank_line);
  86    Vim::action(editor, cx, Vim::toggle_comments);
  87    Vim::action(editor, cx, Vim::paste);
  88    Vim::action(editor, cx, Vim::show_location);
  89
  90    Vim::action(editor, cx, |vim, _: &DeleteLeft, window, cx| {
  91        vim.record_current_action(cx);
  92        let times = Vim::take_count(cx);
  93        let forced_motion = Vim::take_forced_motion(cx);
  94        vim.delete_motion(Motion::Left, times, forced_motion, window, cx);
  95    });
  96    Vim::action(editor, cx, |vim, _: &DeleteRight, window, cx| {
  97        vim.record_current_action(cx);
  98        let times = Vim::take_count(cx);
  99        let forced_motion = Vim::take_forced_motion(cx);
 100        vim.delete_motion(Motion::Right, times, forced_motion, window, cx);
 101    });
 102
 103    Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
 104        vim.record_current_action(cx);
 105        vim.update_editor(window, cx, |_, editor, window, cx| {
 106            editor.change_selections(None, window, cx, |s| {
 107                s.move_with(|map, selection| {
 108                    if selection.is_empty() {
 109                        selection.end = movement::right(map, selection.end)
 110                    }
 111                })
 112            })
 113        });
 114        vim.visual_delete(false, window, cx);
 115    });
 116
 117    Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, window, cx| {
 118        vim.start_recording(cx);
 119        let times = Vim::take_count(cx);
 120        let forced_motion = Vim::take_forced_motion(cx);
 121        vim.change_motion(
 122            Motion::EndOfLine {
 123                display_lines: false,
 124            },
 125            times,
 126            forced_motion,
 127            window,
 128            cx,
 129        );
 130    });
 131    Vim::action(editor, cx, |vim, _: &DeleteToEndOfLine, window, cx| {
 132        vim.record_current_action(cx);
 133        let times = Vim::take_count(cx);
 134        let forced_motion = Vim::take_forced_motion(cx);
 135        vim.delete_motion(
 136            Motion::EndOfLine {
 137                display_lines: false,
 138            },
 139            times,
 140            forced_motion,
 141            window,
 142            cx,
 143        );
 144    });
 145    Vim::action(editor, cx, |vim, _: &JoinLines, window, cx| {
 146        vim.join_lines_impl(true, window, cx);
 147    });
 148
 149    Vim::action(editor, cx, |vim, _: &JoinLinesNoWhitespace, window, cx| {
 150        vim.join_lines_impl(false, window, cx);
 151    });
 152
 153    Vim::action(editor, cx, |vim, _: &Undo, window, cx| {
 154        let times = Vim::take_count(cx);
 155        Vim::take_forced_motion(cx);
 156        vim.update_editor(window, cx, |_, editor, window, cx| {
 157            for _ in 0..times.unwrap_or(1) {
 158                editor.undo(&editor::actions::Undo, window, cx);
 159            }
 160        });
 161    });
 162    Vim::action(editor, cx, |vim, _: &Redo, window, cx| {
 163        let times = Vim::take_count(cx);
 164        Vim::take_forced_motion(cx);
 165        vim.update_editor(window, cx, |_, editor, window, cx| {
 166            for _ in 0..times.unwrap_or(1) {
 167                editor.redo(&editor::actions::Redo, window, cx);
 168            }
 169        });
 170    });
 171
 172    repeat::register(editor, cx);
 173    scroll::register(editor, cx);
 174    search::register(editor, cx);
 175    substitute::register(editor, cx);
 176    increment::register(editor, cx);
 177}
 178
 179impl Vim {
 180    pub fn normal_motion(
 181        &mut self,
 182        motion: Motion,
 183        operator: Option<Operator>,
 184        times: Option<usize>,
 185        forced_motion: bool,
 186        window: &mut Window,
 187        cx: &mut Context<Self>,
 188    ) {
 189        match operator {
 190            None => self.move_cursor(motion, times, window, cx),
 191            Some(Operator::Change) => self.change_motion(motion, times, forced_motion, window, cx),
 192            Some(Operator::Delete) => self.delete_motion(motion, times, forced_motion, window, cx),
 193            Some(Operator::Yank) => self.yank_motion(motion, times, forced_motion, window, cx),
 194            Some(Operator::AddSurrounds { target: None }) => {}
 195            Some(Operator::Indent) => self.indent_motion(
 196                motion,
 197                times,
 198                forced_motion,
 199                IndentDirection::In,
 200                window,
 201                cx,
 202            ),
 203            Some(Operator::Rewrap) => self.rewrap_motion(motion, times, forced_motion, window, cx),
 204            Some(Operator::Outdent) => self.indent_motion(
 205                motion,
 206                times,
 207                forced_motion,
 208                IndentDirection::Out,
 209                window,
 210                cx,
 211            ),
 212            Some(Operator::AutoIndent) => self.indent_motion(
 213                motion,
 214                times,
 215                forced_motion,
 216                IndentDirection::Auto,
 217                window,
 218                cx,
 219            ),
 220            Some(Operator::ShellCommand) => {
 221                self.shell_command_motion(motion, times, forced_motion, window, cx)
 222            }
 223            Some(Operator::Lowercase) => self.convert_motion(
 224                motion,
 225                times,
 226                forced_motion,
 227                ConvertTarget::LowerCase,
 228                window,
 229                cx,
 230            ),
 231            Some(Operator::Uppercase) => self.convert_motion(
 232                motion,
 233                times,
 234                forced_motion,
 235                ConvertTarget::UpperCase,
 236                window,
 237                cx,
 238            ),
 239            Some(Operator::OppositeCase) => self.convert_motion(
 240                motion,
 241                times,
 242                forced_motion,
 243                ConvertTarget::OppositeCase,
 244                window,
 245                cx,
 246            ),
 247            Some(Operator::Rot13) => self.convert_motion(
 248                motion,
 249                times,
 250                forced_motion,
 251                ConvertTarget::Rot13,
 252                window,
 253                cx,
 254            ),
 255            Some(Operator::Rot47) => self.convert_motion(
 256                motion,
 257                times,
 258                forced_motion,
 259                ConvertTarget::Rot47,
 260                window,
 261                cx,
 262            ),
 263            Some(Operator::ToggleComments) => {
 264                self.toggle_comments_motion(motion, times, forced_motion, window, cx)
 265            }
 266            Some(Operator::ReplaceWithRegister) => {
 267                self.replace_with_register_motion(motion, times, forced_motion, window, cx)
 268            }
 269            Some(Operator::Exchange) => {
 270                self.exchange_motion(motion, times, forced_motion, window, cx)
 271            }
 272            Some(operator) => {
 273                // Can't do anything for text objects, Ignoring
 274                error!("Unexpected normal mode motion operator: {:?}", operator)
 275            }
 276        }
 277        // Exit temporary normal mode (if active).
 278        self.exit_temporary_normal(window, cx);
 279    }
 280
 281    pub fn normal_object(&mut self, object: Object, window: &mut Window, cx: &mut Context<Self>) {
 282        let mut waiting_operator: Option<Operator> = None;
 283        match self.maybe_pop_operator() {
 284            Some(Operator::Object { around }) => match self.maybe_pop_operator() {
 285                Some(Operator::Change) => self.change_object(object, around, window, cx),
 286                Some(Operator::Delete) => self.delete_object(object, around, window, cx),
 287                Some(Operator::Yank) => self.yank_object(object, around, window, cx),
 288                Some(Operator::Indent) => {
 289                    self.indent_object(object, around, IndentDirection::In, window, cx)
 290                }
 291                Some(Operator::Outdent) => {
 292                    self.indent_object(object, around, IndentDirection::Out, window, cx)
 293                }
 294                Some(Operator::AutoIndent) => {
 295                    self.indent_object(object, around, IndentDirection::Auto, window, cx)
 296                }
 297                Some(Operator::ShellCommand) => {
 298                    self.shell_command_object(object, around, window, cx);
 299                }
 300                Some(Operator::Rewrap) => self.rewrap_object(object, around, window, cx),
 301                Some(Operator::Lowercase) => {
 302                    self.convert_object(object, around, ConvertTarget::LowerCase, window, cx)
 303                }
 304                Some(Operator::Uppercase) => {
 305                    self.convert_object(object, around, ConvertTarget::UpperCase, window, cx)
 306                }
 307                Some(Operator::OppositeCase) => {
 308                    self.convert_object(object, around, ConvertTarget::OppositeCase, window, cx)
 309                }
 310                Some(Operator::Rot13) => {
 311                    self.convert_object(object, around, ConvertTarget::Rot13, window, cx)
 312                }
 313                Some(Operator::Rot47) => {
 314                    self.convert_object(object, around, ConvertTarget::Rot47, window, cx)
 315                }
 316                Some(Operator::AddSurrounds { target: None }) => {
 317                    waiting_operator = Some(Operator::AddSurrounds {
 318                        target: Some(SurroundsType::Object(object, around)),
 319                    });
 320                }
 321                Some(Operator::ToggleComments) => {
 322                    self.toggle_comments_object(object, around, window, cx)
 323                }
 324                Some(Operator::ReplaceWithRegister) => {
 325                    self.replace_with_register_object(object, around, window, cx)
 326                }
 327                Some(Operator::Exchange) => self.exchange_object(object, around, window, cx),
 328                _ => {
 329                    // Can't do anything for namespace operators. Ignoring
 330                }
 331            },
 332            Some(Operator::DeleteSurrounds) => {
 333                waiting_operator = Some(Operator::DeleteSurrounds);
 334            }
 335            Some(Operator::ChangeSurrounds { target: None }) => {
 336                if self.check_and_move_to_valid_bracket_pair(object, window, cx) {
 337                    waiting_operator = Some(Operator::ChangeSurrounds {
 338                        target: Some(object),
 339                    });
 340                }
 341            }
 342            _ => {
 343                // Can't do anything with change/delete/yank/surrounds and text objects. Ignoring
 344            }
 345        }
 346        self.clear_operator(window, cx);
 347        if let Some(operator) = waiting_operator {
 348            self.push_operator(operator, window, cx);
 349        }
 350    }
 351
 352    pub(crate) fn move_cursor(
 353        &mut self,
 354        motion: Motion,
 355        times: Option<usize>,
 356        window: &mut Window,
 357        cx: &mut Context<Self>,
 358    ) {
 359        self.update_editor(window, cx, |_, editor, window, cx| {
 360            let text_layout_details = editor.text_layout_details(window);
 361            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 362                s.move_cursors_with(|map, cursor, goal| {
 363                    motion
 364                        .move_point(map, cursor, goal, times, &text_layout_details)
 365                        .unwrap_or((cursor, goal))
 366                })
 367            })
 368        });
 369    }
 370
 371    fn insert_after(&mut self, _: &InsertAfter, window: &mut Window, cx: &mut Context<Self>) {
 372        self.start_recording(cx);
 373        self.switch_mode(Mode::Insert, false, window, cx);
 374        self.update_editor(window, cx, |_, editor, window, cx| {
 375            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 376                s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
 377            });
 378        });
 379    }
 380
 381    fn insert_before(&mut self, _: &InsertBefore, window: &mut Window, cx: &mut Context<Self>) {
 382        self.start_recording(cx);
 383        if self.mode.is_visual() {
 384            let current_mode = self.mode;
 385            self.update_editor(window, cx, |_, editor, window, cx| {
 386                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 387                    s.move_with(|map, selection| {
 388                        if current_mode == Mode::VisualLine {
 389                            let start_of_line = motion::start_of_line(map, false, selection.start);
 390                            selection.collapse_to(start_of_line, SelectionGoal::None)
 391                        } else {
 392                            selection.collapse_to(selection.start, SelectionGoal::None)
 393                        }
 394                    });
 395                });
 396            });
 397        }
 398        self.switch_mode(Mode::Insert, false, window, cx);
 399    }
 400
 401    fn insert_first_non_whitespace(
 402        &mut self,
 403        _: &InsertFirstNonWhitespace,
 404        window: &mut Window,
 405        cx: &mut Context<Self>,
 406    ) {
 407        self.start_recording(cx);
 408        self.switch_mode(Mode::Insert, false, window, cx);
 409        self.update_editor(window, cx, |_, editor, window, cx| {
 410            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 411                s.move_cursors_with(|map, cursor, _| {
 412                    (
 413                        first_non_whitespace(map, false, cursor),
 414                        SelectionGoal::None,
 415                    )
 416                });
 417            });
 418        });
 419    }
 420
 421    fn insert_end_of_line(
 422        &mut self,
 423        _: &InsertEndOfLine,
 424        window: &mut Window,
 425        cx: &mut Context<Self>,
 426    ) {
 427        self.start_recording(cx);
 428        self.switch_mode(Mode::Insert, false, window, cx);
 429        self.update_editor(window, cx, |_, editor, window, cx| {
 430            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 431                s.move_cursors_with(|map, cursor, _| {
 432                    (next_line_end(map, cursor, 1), SelectionGoal::None)
 433                });
 434            });
 435        });
 436    }
 437
 438    fn insert_at_previous(
 439        &mut self,
 440        _: &InsertAtPrevious,
 441        window: &mut Window,
 442        cx: &mut Context<Self>,
 443    ) {
 444        self.start_recording(cx);
 445        self.switch_mode(Mode::Insert, false, window, cx);
 446        self.update_editor(window, cx, |vim, editor, window, cx| {
 447            let Some(Mark::Local(marks)) = vim.get_mark("^", editor, window, cx) else {
 448                return;
 449            };
 450
 451            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 452                s.select_anchor_ranges(marks.iter().map(|mark| *mark..*mark))
 453            });
 454        });
 455    }
 456
 457    fn insert_line_above(
 458        &mut self,
 459        _: &InsertLineAbove,
 460        window: &mut Window,
 461        cx: &mut Context<Self>,
 462    ) {
 463        self.start_recording(cx);
 464        self.switch_mode(Mode::Insert, false, window, cx);
 465        self.update_editor(window, cx, |_, editor, window, cx| {
 466            editor.transact(window, cx, |editor, window, cx| {
 467                let selections = editor.selections.all::<Point>(cx);
 468                let snapshot = editor.buffer().read(cx).snapshot(cx);
 469
 470                let selection_start_rows: BTreeSet<u32> = selections
 471                    .into_iter()
 472                    .map(|selection| selection.start.row)
 473                    .collect();
 474                let edits = selection_start_rows
 475                    .into_iter()
 476                    .map(|row| {
 477                        let indent = snapshot
 478                            .indent_and_comment_for_line(MultiBufferRow(row), cx)
 479                            .chars()
 480                            .collect::<String>();
 481
 482                        let start_of_line = Point::new(row, 0);
 483                        (start_of_line..start_of_line, indent + "\n")
 484                    })
 485                    .collect::<Vec<_>>();
 486                editor.edit_with_autoindent(edits, cx);
 487                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 488                    s.move_cursors_with(|map, cursor, _| {
 489                        let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
 490                        let insert_point = motion::end_of_line(map, false, previous_line, 1);
 491                        (insert_point, SelectionGoal::None)
 492                    });
 493                });
 494            });
 495        });
 496    }
 497
 498    fn insert_line_below(
 499        &mut self,
 500        _: &InsertLineBelow,
 501        window: &mut Window,
 502        cx: &mut Context<Self>,
 503    ) {
 504        self.start_recording(cx);
 505        self.switch_mode(Mode::Insert, false, window, cx);
 506        self.update_editor(window, cx, |_, editor, window, cx| {
 507            let text_layout_details = editor.text_layout_details(window);
 508            editor.transact(window, cx, |editor, window, cx| {
 509                let selections = editor.selections.all::<Point>(cx);
 510                let snapshot = editor.buffer().read(cx).snapshot(cx);
 511
 512                let selection_end_rows: BTreeSet<u32> = selections
 513                    .into_iter()
 514                    .map(|selection| selection.end.row)
 515                    .collect();
 516                let edits = selection_end_rows
 517                    .into_iter()
 518                    .map(|row| {
 519                        let indent = snapshot
 520                            .indent_and_comment_for_line(MultiBufferRow(row), cx)
 521                            .chars()
 522                            .collect::<String>();
 523
 524                        let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
 525                        (end_of_line..end_of_line, "\n".to_string() + &indent)
 526                    })
 527                    .collect::<Vec<_>>();
 528                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 529                    s.maybe_move_cursors_with(|map, cursor, goal| {
 530                        Motion::CurrentLine.move_point(
 531                            map,
 532                            cursor,
 533                            goal,
 534                            None,
 535                            &text_layout_details,
 536                        )
 537                    });
 538                });
 539                editor.edit_with_autoindent(edits, cx);
 540            });
 541        });
 542    }
 543
 544    fn insert_empty_line_above(
 545        &mut self,
 546        _: &InsertEmptyLineAbove,
 547        window: &mut Window,
 548        cx: &mut Context<Self>,
 549    ) {
 550        self.record_current_action(cx);
 551        self.update_editor(window, cx, |_, editor, window, cx| {
 552            editor.transact(window, cx, |editor, _, cx| {
 553                let selections = editor.selections.all::<Point>(cx);
 554
 555                let selection_start_rows: BTreeSet<u32> = selections
 556                    .into_iter()
 557                    .map(|selection| selection.start.row)
 558                    .collect();
 559                let edits = selection_start_rows
 560                    .into_iter()
 561                    .map(|row| {
 562                        let start_of_line = Point::new(row, 0);
 563                        (start_of_line..start_of_line, "\n".to_string())
 564                    })
 565                    .collect::<Vec<_>>();
 566                editor.edit(edits, cx);
 567            });
 568        });
 569    }
 570
 571    fn insert_empty_line_below(
 572        &mut self,
 573        _: &InsertEmptyLineBelow,
 574        window: &mut Window,
 575        cx: &mut Context<Self>,
 576    ) {
 577        self.record_current_action(cx);
 578        self.update_editor(window, cx, |_, editor, window, cx| {
 579            editor.transact(window, cx, |editor, _, cx| {
 580                let selections = editor.selections.all::<Point>(cx);
 581                let snapshot = editor.buffer().read(cx).snapshot(cx);
 582
 583                let selection_end_rows: BTreeSet<u32> = selections
 584                    .into_iter()
 585                    .map(|selection| selection.end.row)
 586                    .collect();
 587                let edits = selection_end_rows
 588                    .into_iter()
 589                    .map(|row| {
 590                        let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
 591                        (end_of_line..end_of_line, "\n".to_string())
 592                    })
 593                    .collect::<Vec<_>>();
 594                editor.edit(edits, cx);
 595            });
 596        });
 597    }
 598
 599    fn join_lines_impl(
 600        &mut self,
 601        insert_whitespace: bool,
 602        window: &mut Window,
 603        cx: &mut Context<Self>,
 604    ) {
 605        self.record_current_action(cx);
 606        let mut times = Vim::take_count(cx).unwrap_or(1);
 607        Vim::take_forced_motion(cx);
 608        if self.mode.is_visual() {
 609            times = 1;
 610        } else if times > 1 {
 611            // 2J joins two lines together (same as J or 1J)
 612            times -= 1;
 613        }
 614
 615        self.update_editor(window, cx, |_, editor, window, cx| {
 616            editor.transact(window, cx, |editor, window, cx| {
 617                for _ in 0..times {
 618                    editor.join_lines_impl(insert_whitespace, window, cx)
 619                }
 620            })
 621        });
 622        if self.mode.is_visual() {
 623            self.switch_mode(Mode::Normal, true, window, cx)
 624        }
 625    }
 626
 627    fn yank_line(&mut self, _: &YankLine, window: &mut Window, cx: &mut Context<Self>) {
 628        let count = Vim::take_count(cx);
 629        let forced_motion = Vim::take_forced_motion(cx);
 630        self.yank_motion(
 631            motion::Motion::CurrentLine,
 632            count,
 633            forced_motion,
 634            window,
 635            cx,
 636        )
 637    }
 638
 639    fn show_location(&mut self, _: &ShowLocation, window: &mut Window, cx: &mut Context<Self>) {
 640        let count = Vim::take_count(cx);
 641        Vim::take_forced_motion(cx);
 642        self.update_editor(window, cx, |vim, editor, _window, cx| {
 643            let selection = editor.selections.newest_anchor();
 644            if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
 645                let filename = if let Some(file) = buffer.read(cx).file() {
 646                    if count.is_some() {
 647                        if let Some(local) = file.as_local() {
 648                            local.abs_path(cx).to_string_lossy().to_string()
 649                        } else {
 650                            file.full_path(cx).to_string_lossy().to_string()
 651                        }
 652                    } else {
 653                        file.path().to_string_lossy().to_string()
 654                    }
 655                } else {
 656                    "[No Name]".into()
 657                };
 658                let buffer = buffer.read(cx);
 659                let snapshot = buffer.snapshot();
 660                let lines = buffer.max_point().row + 1;
 661                let current_line = selection.head().text_anchor.to_point(&snapshot).row;
 662                let percentage = current_line as f32 / lines as f32;
 663                let modified = if buffer.is_dirty() { " [modified]" } else { "" };
 664                vim.status_label = Some(
 665                    format!(
 666                        "{}{} {} lines --{:.0}%--",
 667                        filename,
 668                        modified,
 669                        lines,
 670                        percentage * 100.0,
 671                    )
 672                    .into(),
 673                );
 674                cx.notify();
 675            }
 676        });
 677    }
 678
 679    fn toggle_comments(&mut self, _: &ToggleComments, window: &mut Window, cx: &mut Context<Self>) {
 680        self.record_current_action(cx);
 681        self.store_visual_marks(window, cx);
 682        self.update_editor(window, cx, |vim, editor, window, cx| {
 683            editor.transact(window, cx, |editor, window, cx| {
 684                let original_positions = vim.save_selection_starts(editor, cx);
 685                editor.toggle_comments(&Default::default(), window, cx);
 686                vim.restore_selection_cursors(editor, window, cx, original_positions);
 687            });
 688        });
 689        if self.mode.is_visual() {
 690            self.switch_mode(Mode::Normal, true, window, cx)
 691        }
 692    }
 693
 694    pub(crate) fn normal_replace(
 695        &mut self,
 696        text: Arc<str>,
 697        window: &mut Window,
 698        cx: &mut Context<Self>,
 699    ) {
 700        let is_return_char = text == "\n".into() || text == "\r".into();
 701        let count = Vim::take_count(cx).unwrap_or(1);
 702        Vim::take_forced_motion(cx);
 703        self.stop_recording(cx);
 704        self.update_editor(window, cx, |_, editor, window, cx| {
 705            editor.transact(window, cx, |editor, window, cx| {
 706                editor.set_clip_at_line_ends(false, cx);
 707                let (map, display_selections) = editor.selections.all_display(cx);
 708
 709                let mut edits = Vec::new();
 710                for selection in &display_selections {
 711                    let mut range = selection.range();
 712                    for _ in 0..count {
 713                        let new_point = movement::saturating_right(&map, range.end);
 714                        if range.end == new_point {
 715                            return;
 716                        }
 717                        range.end = new_point;
 718                    }
 719
 720                    edits.push((
 721                        range.start.to_offset(&map, Bias::Left)
 722                            ..range.end.to_offset(&map, Bias::Left),
 723                        text.repeat(if is_return_char { 0 } else { count }),
 724                    ));
 725                }
 726
 727                editor.edit(edits, cx);
 728                if is_return_char {
 729                    editor.newline(&editor::actions::Newline, window, cx);
 730                }
 731                editor.set_clip_at_line_ends(true, cx);
 732                editor.change_selections(None, window, cx, |s| {
 733                    s.move_with(|map, selection| {
 734                        let point = movement::saturating_left(map, selection.head());
 735                        selection.collapse_to(point, SelectionGoal::None)
 736                    });
 737                });
 738            });
 739        });
 740        self.pop_operator(window, cx);
 741    }
 742
 743    pub fn save_selection_starts(
 744        &self,
 745        editor: &Editor,
 746
 747        cx: &mut Context<Editor>,
 748    ) -> HashMap<usize, Anchor> {
 749        let (map, selections) = editor.selections.all_display(cx);
 750        selections
 751            .iter()
 752            .map(|selection| {
 753                (
 754                    selection.id,
 755                    map.display_point_to_anchor(selection.start, Bias::Right),
 756                )
 757            })
 758            .collect::<HashMap<_, _>>()
 759    }
 760
 761    pub fn restore_selection_cursors(
 762        &self,
 763        editor: &mut Editor,
 764        window: &mut Window,
 765        cx: &mut Context<Editor>,
 766        mut positions: HashMap<usize, Anchor>,
 767    ) {
 768        editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
 769            s.move_with(|map, selection| {
 770                if let Some(anchor) = positions.remove(&selection.id) {
 771                    selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
 772                }
 773            });
 774        });
 775    }
 776
 777    fn exit_temporary_normal(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 778        if self.temp_mode {
 779            self.switch_mode(Mode::Insert, true, window, cx);
 780        }
 781    }
 782}
 783#[cfg(test)]
 784mod test {
 785    use gpui::{KeyBinding, TestAppContext, UpdateGlobal};
 786    use indoc::indoc;
 787    use language::language_settings::AllLanguageSettings;
 788    use settings::SettingsStore;
 789
 790    use crate::{
 791        VimSettings, motion,
 792        state::Mode::{self},
 793        test::{NeovimBackedTestContext, VimTestContext},
 794    };
 795
 796    #[gpui::test]
 797    async fn test_h(cx: &mut gpui::TestAppContext) {
 798        let mut cx = NeovimBackedTestContext::new(cx).await;
 799        cx.simulate_at_each_offset(
 800            "h",
 801            indoc! {"
 802            ˇThe qˇuick
 803            ˇbrown"
 804            },
 805        )
 806        .await
 807        .assert_matches();
 808    }
 809
 810    #[gpui::test]
 811    async fn test_backspace(cx: &mut gpui::TestAppContext) {
 812        let mut cx = NeovimBackedTestContext::new(cx).await;
 813        cx.simulate_at_each_offset(
 814            "backspace",
 815            indoc! {"
 816            ˇThe qˇuick
 817            ˇbrown"
 818            },
 819        )
 820        .await
 821        .assert_matches();
 822    }
 823
 824    #[gpui::test]
 825    async fn test_j(cx: &mut gpui::TestAppContext) {
 826        let mut cx = NeovimBackedTestContext::new(cx).await;
 827
 828        cx.set_shared_state(indoc! {"
 829            aaˇaa
 830            😃😃"
 831        })
 832        .await;
 833        cx.simulate_shared_keystrokes("j").await;
 834        cx.shared_state().await.assert_eq(indoc! {"
 835            aaaa
 836            😃ˇ😃"
 837        });
 838
 839        cx.simulate_at_each_offset(
 840            "j",
 841            indoc! {"
 842                ˇThe qˇuick broˇwn
 843                ˇfox jumps"
 844            },
 845        )
 846        .await
 847        .assert_matches();
 848    }
 849
 850    #[gpui::test]
 851    async fn test_enter(cx: &mut gpui::TestAppContext) {
 852        let mut cx = NeovimBackedTestContext::new(cx).await;
 853        cx.simulate_at_each_offset(
 854            "enter",
 855            indoc! {"
 856            ˇThe qˇuick broˇwn
 857            ˇfox jumps"
 858            },
 859        )
 860        .await
 861        .assert_matches();
 862    }
 863
 864    #[gpui::test]
 865    async fn test_k(cx: &mut gpui::TestAppContext) {
 866        let mut cx = NeovimBackedTestContext::new(cx).await;
 867        cx.simulate_at_each_offset(
 868            "k",
 869            indoc! {"
 870            ˇThe qˇuick
 871            ˇbrown fˇox jumˇps"
 872            },
 873        )
 874        .await
 875        .assert_matches();
 876    }
 877
 878    #[gpui::test]
 879    async fn test_l(cx: &mut gpui::TestAppContext) {
 880        let mut cx = NeovimBackedTestContext::new(cx).await;
 881        cx.simulate_at_each_offset(
 882            "l",
 883            indoc! {"
 884            ˇThe qˇuicˇk
 885            ˇbrowˇn"},
 886        )
 887        .await
 888        .assert_matches();
 889    }
 890
 891    #[gpui::test]
 892    async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
 893        let mut cx = NeovimBackedTestContext::new(cx).await;
 894        cx.simulate_at_each_offset(
 895            "$",
 896            indoc! {"
 897            ˇThe qˇuicˇk
 898            ˇbrowˇn"},
 899        )
 900        .await
 901        .assert_matches();
 902        cx.simulate_at_each_offset(
 903            "0",
 904            indoc! {"
 905                ˇThe qˇuicˇk
 906                ˇbrowˇn"},
 907        )
 908        .await
 909        .assert_matches();
 910    }
 911
 912    #[gpui::test]
 913    async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
 914        let mut cx = NeovimBackedTestContext::new(cx).await;
 915
 916        cx.simulate_at_each_offset(
 917            "shift-g",
 918            indoc! {"
 919                The ˇquick
 920
 921                brown fox jumps
 922                overˇ the lazy doˇg"},
 923        )
 924        .await
 925        .assert_matches();
 926        cx.simulate(
 927            "shift-g",
 928            indoc! {"
 929            The quiˇck
 930
 931            brown"},
 932        )
 933        .await
 934        .assert_matches();
 935        cx.simulate(
 936            "shift-g",
 937            indoc! {"
 938            The quiˇck
 939
 940            "},
 941        )
 942        .await
 943        .assert_matches();
 944    }
 945
 946    #[gpui::test]
 947    async fn test_w(cx: &mut gpui::TestAppContext) {
 948        let mut cx = NeovimBackedTestContext::new(cx).await;
 949        cx.simulate_at_each_offset(
 950            "w",
 951            indoc! {"
 952            The ˇquickˇ-ˇbrown
 953            ˇ
 954            ˇ
 955            ˇfox_jumps ˇover
 956            ˇthˇe"},
 957        )
 958        .await
 959        .assert_matches();
 960        cx.simulate_at_each_offset(
 961            "shift-w",
 962            indoc! {"
 963            The ˇquickˇ-ˇbrown
 964            ˇ
 965            ˇ
 966            ˇfox_jumps ˇover
 967            ˇthˇe"},
 968        )
 969        .await
 970        .assert_matches();
 971    }
 972
 973    #[gpui::test]
 974    async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
 975        let mut cx = NeovimBackedTestContext::new(cx).await;
 976        cx.simulate_at_each_offset(
 977            "e",
 978            indoc! {"
 979            Thˇe quicˇkˇ-browˇn
 980
 981
 982            fox_jumpˇs oveˇr
 983            thˇe"},
 984        )
 985        .await
 986        .assert_matches();
 987        cx.simulate_at_each_offset(
 988            "shift-e",
 989            indoc! {"
 990            Thˇe quicˇkˇ-browˇn
 991
 992
 993            fox_jumpˇs oveˇr
 994            thˇe"},
 995        )
 996        .await
 997        .assert_matches();
 998    }
 999
1000    #[gpui::test]
1001    async fn test_b(cx: &mut gpui::TestAppContext) {
1002        let mut cx = NeovimBackedTestContext::new(cx).await;
1003        cx.simulate_at_each_offset(
1004            "b",
1005            indoc! {"
1006            ˇThe ˇquickˇ-ˇbrown
1007            ˇ
1008            ˇ
1009            ˇfox_jumps ˇover
1010            ˇthe"},
1011        )
1012        .await
1013        .assert_matches();
1014        cx.simulate_at_each_offset(
1015            "shift-b",
1016            indoc! {"
1017            ˇThe ˇquickˇ-ˇbrown
1018            ˇ
1019            ˇ
1020            ˇfox_jumps ˇover
1021            ˇthe"},
1022        )
1023        .await
1024        .assert_matches();
1025    }
1026
1027    #[gpui::test]
1028    async fn test_gg(cx: &mut gpui::TestAppContext) {
1029        let mut cx = NeovimBackedTestContext::new(cx).await;
1030        cx.simulate_at_each_offset(
1031            "g g",
1032            indoc! {"
1033                The qˇuick
1034
1035                brown fox jumps
1036                over ˇthe laˇzy dog"},
1037        )
1038        .await
1039        .assert_matches();
1040        cx.simulate(
1041            "g g",
1042            indoc! {"
1043
1044
1045                brown fox jumps
1046                over the laˇzy dog"},
1047        )
1048        .await
1049        .assert_matches();
1050        cx.simulate(
1051            "2 g g",
1052            indoc! {"
1053                ˇ
1054
1055                brown fox jumps
1056                over the lazydog"},
1057        )
1058        .await
1059        .assert_matches();
1060    }
1061
1062    #[gpui::test]
1063    async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
1064        let mut cx = NeovimBackedTestContext::new(cx).await;
1065        cx.simulate_at_each_offset(
1066            "shift-g",
1067            indoc! {"
1068                The qˇuick
1069
1070                brown fox jumps
1071                over ˇthe laˇzy dog"},
1072        )
1073        .await
1074        .assert_matches();
1075        cx.simulate(
1076            "shift-g",
1077            indoc! {"
1078
1079
1080                brown fox jumps
1081                over the laˇzy dog"},
1082        )
1083        .await
1084        .assert_matches();
1085        cx.simulate(
1086            "2 shift-g",
1087            indoc! {"
1088                ˇ
1089
1090                brown fox jumps
1091                over the lazydog"},
1092        )
1093        .await
1094        .assert_matches();
1095    }
1096
1097    #[gpui::test]
1098    async fn test_a(cx: &mut gpui::TestAppContext) {
1099        let mut cx = NeovimBackedTestContext::new(cx).await;
1100        cx.simulate_at_each_offset("a", "The qˇuicˇk")
1101            .await
1102            .assert_matches();
1103    }
1104
1105    #[gpui::test]
1106    async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
1107        let mut cx = NeovimBackedTestContext::new(cx).await;
1108        cx.simulate_at_each_offset(
1109            "shift-a",
1110            indoc! {"
1111            ˇ
1112            The qˇuick
1113            brown ˇfox "},
1114        )
1115        .await
1116        .assert_matches();
1117    }
1118
1119    #[gpui::test]
1120    async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
1121        let mut cx = NeovimBackedTestContext::new(cx).await;
1122        cx.simulate("^", "The qˇuick").await.assert_matches();
1123        cx.simulate("^", " The qˇuick").await.assert_matches();
1124        cx.simulate("^", "ˇ").await.assert_matches();
1125        cx.simulate(
1126            "^",
1127            indoc! {"
1128                The qˇuick
1129                brown fox"},
1130        )
1131        .await
1132        .assert_matches();
1133        cx.simulate(
1134            "^",
1135            indoc! {"
1136                ˇ
1137                The quick"},
1138        )
1139        .await
1140        .assert_matches();
1141        // Indoc disallows trailing whitespace.
1142        cx.simulate("^", "   ˇ \nThe quick").await.assert_matches();
1143    }
1144
1145    #[gpui::test]
1146    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
1147        let mut cx = NeovimBackedTestContext::new(cx).await;
1148        cx.simulate("shift-i", "The qˇuick").await.assert_matches();
1149        cx.simulate("shift-i", " The qˇuick").await.assert_matches();
1150        cx.simulate("shift-i", "ˇ").await.assert_matches();
1151        cx.simulate(
1152            "shift-i",
1153            indoc! {"
1154                The qˇuick
1155                brown fox"},
1156        )
1157        .await
1158        .assert_matches();
1159        cx.simulate(
1160            "shift-i",
1161            indoc! {"
1162                ˇ
1163                The quick"},
1164        )
1165        .await
1166        .assert_matches();
1167    }
1168
1169    #[gpui::test]
1170    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
1171        let mut cx = NeovimBackedTestContext::new(cx).await;
1172        cx.simulate(
1173            "shift-d",
1174            indoc! {"
1175                The qˇuick
1176                brown fox"},
1177        )
1178        .await
1179        .assert_matches();
1180        cx.simulate(
1181            "shift-d",
1182            indoc! {"
1183                The quick
1184                ˇ
1185                brown fox"},
1186        )
1187        .await
1188        .assert_matches();
1189    }
1190
1191    #[gpui::test]
1192    async fn test_x(cx: &mut gpui::TestAppContext) {
1193        let mut cx = NeovimBackedTestContext::new(cx).await;
1194        cx.simulate_at_each_offset("x", "ˇTeˇsˇt")
1195            .await
1196            .assert_matches();
1197        cx.simulate(
1198            "x",
1199            indoc! {"
1200                Tesˇt
1201                test"},
1202        )
1203        .await
1204        .assert_matches();
1205    }
1206
1207    #[gpui::test]
1208    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
1209        let mut cx = NeovimBackedTestContext::new(cx).await;
1210        cx.simulate_at_each_offset("shift-x", "ˇTˇeˇsˇt")
1211            .await
1212            .assert_matches();
1213        cx.simulate(
1214            "shift-x",
1215            indoc! {"
1216                Test
1217                ˇtest"},
1218        )
1219        .await
1220        .assert_matches();
1221    }
1222
1223    #[gpui::test]
1224    async fn test_o(cx: &mut gpui::TestAppContext) {
1225        let mut cx = NeovimBackedTestContext::new(cx).await;
1226        cx.simulate("o", "ˇ").await.assert_matches();
1227        cx.simulate("o", "The ˇquick").await.assert_matches();
1228        cx.simulate_at_each_offset(
1229            "o",
1230            indoc! {"
1231                The qˇuick
1232                brown ˇfox
1233                jumps ˇover"},
1234        )
1235        .await
1236        .assert_matches();
1237        cx.simulate(
1238            "o",
1239            indoc! {"
1240                The quick
1241                ˇ
1242                brown fox"},
1243        )
1244        .await
1245        .assert_matches();
1246
1247        cx.assert_binding(
1248            "o",
1249            indoc! {"
1250                fn test() {
1251                    println!(ˇ);
1252                }"},
1253            Mode::Normal,
1254            indoc! {"
1255                fn test() {
1256                    println!();
1257                    ˇ
1258                }"},
1259            Mode::Insert,
1260        );
1261
1262        cx.assert_binding(
1263            "o",
1264            indoc! {"
1265                fn test(ˇ) {
1266                    println!();
1267                }"},
1268            Mode::Normal,
1269            indoc! {"
1270                fn test() {
1271                    ˇ
1272                    println!();
1273                }"},
1274            Mode::Insert,
1275        );
1276    }
1277
1278    #[gpui::test]
1279    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
1280        let mut cx = NeovimBackedTestContext::new(cx).await;
1281        cx.simulate("shift-o", "ˇ").await.assert_matches();
1282        cx.simulate("shift-o", "The ˇquick").await.assert_matches();
1283        cx.simulate_at_each_offset(
1284            "shift-o",
1285            indoc! {"
1286            The qˇuick
1287            brown ˇfox
1288            jumps ˇover"},
1289        )
1290        .await
1291        .assert_matches();
1292        cx.simulate(
1293            "shift-o",
1294            indoc! {"
1295            The quick
1296            ˇ
1297            brown fox"},
1298        )
1299        .await
1300        .assert_matches();
1301
1302        // Our indentation is smarter than vims. So we don't match here
1303        cx.assert_binding(
1304            "shift-o",
1305            indoc! {"
1306                fn test() {
1307                    println!(ˇ);
1308                }"},
1309            Mode::Normal,
1310            indoc! {"
1311                fn test() {
1312                    ˇ
1313                    println!();
1314                }"},
1315            Mode::Insert,
1316        );
1317        cx.assert_binding(
1318            "shift-o",
1319            indoc! {"
1320                fn test(ˇ) {
1321                    println!();
1322                }"},
1323            Mode::Normal,
1324            indoc! {"
1325                ˇ
1326                fn test() {
1327                    println!();
1328                }"},
1329            Mode::Insert,
1330        );
1331    }
1332
1333    #[gpui::test]
1334    async fn test_insert_empty_line_above(cx: &mut gpui::TestAppContext) {
1335        let mut cx = NeovimBackedTestContext::new(cx).await;
1336        cx.simulate("[ space", "ˇ").await.assert_matches();
1337        cx.simulate("[ space", "The ˇquick").await.assert_matches();
1338        cx.simulate_at_each_offset(
1339            "[ space",
1340            indoc! {"
1341            The qˇuick
1342            brown ˇfox
1343            jumps ˇover"},
1344        )
1345        .await
1346        .assert_matches();
1347        cx.simulate(
1348            "[ space",
1349            indoc! {"
1350            The quick
1351            ˇ
1352            brown fox"},
1353        )
1354        .await
1355        .assert_matches();
1356    }
1357
1358    #[gpui::test]
1359    async fn test_dd(cx: &mut gpui::TestAppContext) {
1360        let mut cx = NeovimBackedTestContext::new(cx).await;
1361        cx.simulate("d d", "ˇ").await.assert_matches();
1362        cx.simulate("d d", "The ˇquick").await.assert_matches();
1363        cx.simulate_at_each_offset(
1364            "d d",
1365            indoc! {"
1366            The qˇuick
1367            brown ˇfox
1368            jumps ˇover"},
1369        )
1370        .await
1371        .assert_matches();
1372        cx.simulate(
1373            "d d",
1374            indoc! {"
1375                The quick
1376                ˇ
1377                brown fox"},
1378        )
1379        .await
1380        .assert_matches();
1381    }
1382
1383    #[gpui::test]
1384    async fn test_cc(cx: &mut gpui::TestAppContext) {
1385        let mut cx = NeovimBackedTestContext::new(cx).await;
1386        cx.simulate("c c", "ˇ").await.assert_matches();
1387        cx.simulate("c c", "The ˇquick").await.assert_matches();
1388        cx.simulate_at_each_offset(
1389            "c c",
1390            indoc! {"
1391                The quˇick
1392                brown ˇfox
1393                jumps ˇover"},
1394        )
1395        .await
1396        .assert_matches();
1397        cx.simulate(
1398            "c c",
1399            indoc! {"
1400                The quick
1401                ˇ
1402                brown fox"},
1403        )
1404        .await
1405        .assert_matches();
1406    }
1407
1408    #[gpui::test]
1409    async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
1410        let mut cx = NeovimBackedTestContext::new(cx).await;
1411
1412        for count in 1..=5 {
1413            cx.simulate_at_each_offset(
1414                &format!("{count} w"),
1415                indoc! {"
1416                    ˇThe quˇickˇ browˇn
1417                    ˇ
1418                    ˇfox ˇjumpsˇ-ˇoˇver
1419                    ˇthe lazy dog
1420                "},
1421            )
1422            .await
1423            .assert_matches();
1424        }
1425    }
1426
1427    #[gpui::test]
1428    async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
1429        let mut cx = NeovimBackedTestContext::new(cx).await;
1430        cx.simulate_at_each_offset("h", "Testˇ├ˇ──ˇ┐ˇTest")
1431            .await
1432            .assert_matches();
1433    }
1434
1435    #[gpui::test]
1436    async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
1437        let mut cx = NeovimBackedTestContext::new(cx).await;
1438
1439        for count in 1..=3 {
1440            let test_case = indoc! {"
1441                ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
1442                ˇ    ˇbˇaaˇa ˇbˇbˇb
1443                ˇ
1444                ˇb
1445            "};
1446
1447            cx.simulate_at_each_offset(&format!("{count} f b"), test_case)
1448                .await
1449                .assert_matches();
1450
1451            cx.simulate_at_each_offset(&format!("{count} t b"), test_case)
1452                .await
1453                .assert_matches();
1454        }
1455    }
1456
1457    #[gpui::test]
1458    async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
1459        let mut cx = NeovimBackedTestContext::new(cx).await;
1460        let test_case = indoc! {"
1461            ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
1462            ˇ    ˇbˇaaˇa ˇbˇbˇb
1463            ˇ•••
1464            ˇb
1465            "
1466        };
1467
1468        for count in 1..=3 {
1469            cx.simulate_at_each_offset(&format!("{count} shift-f b"), test_case)
1470                .await
1471                .assert_matches();
1472
1473            cx.simulate_at_each_offset(&format!("{count} shift-t b"), test_case)
1474                .await
1475                .assert_matches();
1476        }
1477    }
1478
1479    #[gpui::test]
1480    async fn test_f_and_t_multiline(cx: &mut gpui::TestAppContext) {
1481        let mut cx = VimTestContext::new(cx, true).await;
1482        cx.update_global(|store: &mut SettingsStore, cx| {
1483            store.update_user_settings::<VimSettings>(cx, |s| {
1484                s.use_multiline_find = Some(true);
1485            });
1486        });
1487
1488        cx.assert_binding(
1489            "f l",
1490            indoc! {"
1491            ˇfunction print() {
1492                console.log('ok')
1493            }
1494            "},
1495            Mode::Normal,
1496            indoc! {"
1497            function print() {
1498                consoˇle.log('ok')
1499            }
1500            "},
1501            Mode::Normal,
1502        );
1503
1504        cx.assert_binding(
1505            "t l",
1506            indoc! {"
1507            ˇfunction print() {
1508                console.log('ok')
1509            }
1510            "},
1511            Mode::Normal,
1512            indoc! {"
1513            function print() {
1514                consˇole.log('ok')
1515            }
1516            "},
1517            Mode::Normal,
1518        );
1519    }
1520
1521    #[gpui::test]
1522    async fn test_capital_f_and_capital_t_multiline(cx: &mut gpui::TestAppContext) {
1523        let mut cx = VimTestContext::new(cx, true).await;
1524        cx.update_global(|store: &mut SettingsStore, cx| {
1525            store.update_user_settings::<VimSettings>(cx, |s| {
1526                s.use_multiline_find = Some(true);
1527            });
1528        });
1529
1530        cx.assert_binding(
1531            "shift-f p",
1532            indoc! {"
1533            function print() {
1534                console.ˇlog('ok')
1535            }
1536            "},
1537            Mode::Normal,
1538            indoc! {"
1539            function ˇprint() {
1540                console.log('ok')
1541            }
1542            "},
1543            Mode::Normal,
1544        );
1545
1546        cx.assert_binding(
1547            "shift-t p",
1548            indoc! {"
1549            function print() {
1550                console.ˇlog('ok')
1551            }
1552            "},
1553            Mode::Normal,
1554            indoc! {"
1555            function pˇrint() {
1556                console.log('ok')
1557            }
1558            "},
1559            Mode::Normal,
1560        );
1561    }
1562
1563    #[gpui::test]
1564    async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) {
1565        let mut cx = VimTestContext::new(cx, true).await;
1566        cx.update_global(|store: &mut SettingsStore, cx| {
1567            store.update_user_settings::<VimSettings>(cx, |s| {
1568                s.use_smartcase_find = Some(true);
1569            });
1570        });
1571
1572        cx.assert_binding(
1573            "f p",
1574            indoc! {"ˇfmt.Println(\"Hello, World!\")"},
1575            Mode::Normal,
1576            indoc! {"fmt.ˇPrintln(\"Hello, World!\")"},
1577            Mode::Normal,
1578        );
1579
1580        cx.assert_binding(
1581            "shift-f p",
1582            indoc! {"fmt.Printlnˇ(\"Hello, World!\")"},
1583            Mode::Normal,
1584            indoc! {"fmt.ˇPrintln(\"Hello, World!\")"},
1585            Mode::Normal,
1586        );
1587
1588        cx.assert_binding(
1589            "t p",
1590            indoc! {"ˇfmt.Println(\"Hello, World!\")"},
1591            Mode::Normal,
1592            indoc! {"fmtˇ.Println(\"Hello, World!\")"},
1593            Mode::Normal,
1594        );
1595
1596        cx.assert_binding(
1597            "shift-t p",
1598            indoc! {"fmt.Printlnˇ(\"Hello, World!\")"},
1599            Mode::Normal,
1600            indoc! {"fmt.Pˇrintln(\"Hello, World!\")"},
1601            Mode::Normal,
1602        );
1603    }
1604
1605    #[gpui::test]
1606    async fn test_percent(cx: &mut TestAppContext) {
1607        let mut cx = NeovimBackedTestContext::new(cx).await;
1608        cx.simulate_at_each_offset("%", "ˇconsole.logˇ(ˇvaˇrˇ)ˇ;")
1609            .await
1610            .assert_matches();
1611        cx.simulate_at_each_offset("%", "ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
1612            .await
1613            .assert_matches();
1614        cx.simulate_at_each_offset("%", "let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;")
1615            .await
1616            .assert_matches();
1617    }
1618
1619    #[gpui::test]
1620    async fn test_end_of_line_with_neovim(cx: &mut gpui::TestAppContext) {
1621        let mut cx = NeovimBackedTestContext::new(cx).await;
1622
1623        // goes to current line end
1624        cx.set_shared_state(indoc! {"ˇaa\nbb\ncc"}).await;
1625        cx.simulate_shared_keystrokes("$").await;
1626        cx.shared_state().await.assert_eq("aˇa\nbb\ncc");
1627
1628        // goes to next line end
1629        cx.simulate_shared_keystrokes("2 $").await;
1630        cx.shared_state().await.assert_eq("aa\nbˇb\ncc");
1631
1632        // try to exceed the final line.
1633        cx.simulate_shared_keystrokes("4 $").await;
1634        cx.shared_state().await.assert_eq("aa\nbb\ncˇc");
1635    }
1636
1637    #[gpui::test]
1638    async fn test_subword_motions(cx: &mut gpui::TestAppContext) {
1639        let mut cx = VimTestContext::new(cx, true).await;
1640        cx.update(|_, cx| {
1641            cx.bind_keys(vec![
1642                KeyBinding::new(
1643                    "w",
1644                    motion::NextSubwordStart {
1645                        ignore_punctuation: false,
1646                    },
1647                    Some("Editor && VimControl && !VimWaiting && !menu"),
1648                ),
1649                KeyBinding::new(
1650                    "b",
1651                    motion::PreviousSubwordStart {
1652                        ignore_punctuation: false,
1653                    },
1654                    Some("Editor && VimControl && !VimWaiting && !menu"),
1655                ),
1656                KeyBinding::new(
1657                    "e",
1658                    motion::NextSubwordEnd {
1659                        ignore_punctuation: false,
1660                    },
1661                    Some("Editor && VimControl && !VimWaiting && !menu"),
1662                ),
1663                KeyBinding::new(
1664                    "g e",
1665                    motion::PreviousSubwordEnd {
1666                        ignore_punctuation: false,
1667                    },
1668                    Some("Editor && VimControl && !VimWaiting && !menu"),
1669                ),
1670            ]);
1671        });
1672
1673        cx.assert_binding_normal("w", indoc! {"ˇassert_binding"}, indoc! {"assert_ˇbinding"});
1674        // Special case: In 'cw', 'w' acts like 'e'
1675        cx.assert_binding(
1676            "c w",
1677            indoc! {"ˇassert_binding"},
1678            Mode::Normal,
1679            indoc! {"ˇ_binding"},
1680            Mode::Insert,
1681        );
1682
1683        cx.assert_binding_normal("e", indoc! {"ˇassert_binding"}, indoc! {"asserˇt_binding"});
1684
1685        cx.assert_binding_normal("b", indoc! {"assert_ˇbinding"}, indoc! {"ˇassert_binding"});
1686
1687        cx.assert_binding_normal(
1688            "g e",
1689            indoc! {"assert_bindinˇg"},
1690            indoc! {"asserˇt_binding"},
1691        );
1692    }
1693
1694    #[gpui::test]
1695    async fn test_r(cx: &mut gpui::TestAppContext) {
1696        let mut cx = NeovimBackedTestContext::new(cx).await;
1697
1698        cx.set_shared_state("ˇhello\n").await;
1699        cx.simulate_shared_keystrokes("r -").await;
1700        cx.shared_state().await.assert_eq("ˇ-ello\n");
1701
1702        cx.set_shared_state("ˇhello\n").await;
1703        cx.simulate_shared_keystrokes("3 r -").await;
1704        cx.shared_state().await.assert_eq("--ˇ-lo\n");
1705
1706        cx.set_shared_state("ˇhello\n").await;
1707        cx.simulate_shared_keystrokes("r - 2 l .").await;
1708        cx.shared_state().await.assert_eq("-eˇ-lo\n");
1709
1710        cx.set_shared_state("ˇhello world\n").await;
1711        cx.simulate_shared_keystrokes("2 r - f w .").await;
1712        cx.shared_state().await.assert_eq("--llo -ˇ-rld\n");
1713
1714        cx.set_shared_state("ˇhello world\n").await;
1715        cx.simulate_shared_keystrokes("2 0 r - ").await;
1716        cx.shared_state().await.assert_eq("ˇhello world\n");
1717
1718        cx.set_shared_state("  helloˇ world\n").await;
1719        cx.simulate_shared_keystrokes("r enter").await;
1720        cx.shared_state().await.assert_eq("  hello\n ˇ world\n");
1721
1722        cx.set_shared_state("  helloˇ world\n").await;
1723        cx.simulate_shared_keystrokes("2 r enter").await;
1724        cx.shared_state().await.assert_eq("  hello\n ˇ orld\n");
1725    }
1726
1727    #[gpui::test]
1728    async fn test_gq(cx: &mut gpui::TestAppContext) {
1729        let mut cx = NeovimBackedTestContext::new(cx).await;
1730        cx.set_neovim_option("textwidth=5").await;
1731
1732        cx.update(|_, cx| {
1733            SettingsStore::update_global(cx, |settings, cx| {
1734                settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1735                    settings.defaults.preferred_line_length = Some(5);
1736                });
1737            })
1738        });
1739
1740        cx.set_shared_state("ˇth th th th th th\n").await;
1741        cx.simulate_shared_keystrokes("g q q").await;
1742        cx.shared_state().await.assert_eq("th th\nth th\nˇth th\n");
1743
1744        cx.set_shared_state("ˇth th th th th th\nth th th th th th\n")
1745            .await;
1746        cx.simulate_shared_keystrokes("v j g q").await;
1747        cx.shared_state()
1748            .await
1749            .assert_eq("th th\nth th\nth th\nth th\nth th\nˇth th\n");
1750    }
1751
1752    #[gpui::test]
1753    async fn test_o_comment(cx: &mut gpui::TestAppContext) {
1754        let mut cx = NeovimBackedTestContext::new(cx).await;
1755        cx.set_neovim_option("filetype=rust").await;
1756
1757        cx.set_shared_state("// helloˇ\n").await;
1758        cx.simulate_shared_keystrokes("o").await;
1759        cx.shared_state().await.assert_eq("// hello\n// ˇ\n");
1760        cx.simulate_shared_keystrokes("x escape shift-o").await;
1761        cx.shared_state().await.assert_eq("// hello\n// ˇ\n// x\n");
1762    }
1763
1764    #[gpui::test]
1765    async fn test_yank_line_with_trailing_newline(cx: &mut gpui::TestAppContext) {
1766        let mut cx = NeovimBackedTestContext::new(cx).await;
1767        cx.set_shared_state("heˇllo\n").await;
1768        cx.simulate_shared_keystrokes("y y p").await;
1769        cx.shared_state().await.assert_eq("hello\nˇhello\n");
1770    }
1771
1772    #[gpui::test]
1773    async fn test_yank_line_without_trailing_newline(cx: &mut gpui::TestAppContext) {
1774        let mut cx = NeovimBackedTestContext::new(cx).await;
1775        cx.set_shared_state("heˇllo").await;
1776        cx.simulate_shared_keystrokes("y y p").await;
1777        cx.shared_state().await.assert_eq("hello\nˇhello");
1778    }
1779
1780    #[gpui::test]
1781    async fn test_yank_multiline_without_trailing_newline(cx: &mut gpui::TestAppContext) {
1782        let mut cx = NeovimBackedTestContext::new(cx).await;
1783        cx.set_shared_state("heˇllo\nhello").await;
1784        cx.simulate_shared_keystrokes("2 y y p").await;
1785        cx.shared_state()
1786            .await
1787            .assert_eq("hello\nˇhello\nhello\nhello");
1788    }
1789
1790    #[gpui::test]
1791    async fn test_dd_then_paste_without_trailing_newline(cx: &mut gpui::TestAppContext) {
1792        let mut cx = NeovimBackedTestContext::new(cx).await;
1793        cx.set_shared_state("heˇllo").await;
1794        cx.simulate_shared_keystrokes("d d").await;
1795        cx.shared_state().await.assert_eq("ˇ");
1796        cx.simulate_shared_keystrokes("p p").await;
1797        cx.shared_state().await.assert_eq("\nhello\nˇhello");
1798    }
1799
1800    #[gpui::test]
1801    async fn test_visual_mode_insert_before_after(cx: &mut gpui::TestAppContext) {
1802        let mut cx = NeovimBackedTestContext::new(cx).await;
1803
1804        cx.set_shared_state("heˇllo").await;
1805        cx.simulate_shared_keystrokes("v i w shift-i").await;
1806        cx.shared_state().await.assert_eq("ˇhello");
1807
1808        cx.set_shared_state(indoc! {"
1809            The quick brown
1810            fox ˇjumps over
1811            the lazy dog"})
1812            .await;
1813        cx.simulate_shared_keystrokes("shift-v shift-i").await;
1814        cx.shared_state().await.assert_eq(indoc! {"
1815            The quick brown
1816            ˇfox jumps over
1817            the lazy dog"});
1818
1819        cx.set_shared_state(indoc! {"
1820            The quick brown
1821            fox ˇjumps over
1822            the lazy dog"})
1823            .await;
1824        cx.simulate_shared_keystrokes("shift-v shift-a").await;
1825        cx.shared_state().await.assert_eq(indoc! {"
1826            The quick brown
1827            fox jˇumps over
1828            the lazy dog"});
1829    }
1830}