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