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