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