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