visual.rs

   1use anyhow::Result;
   2use std::sync::Arc;
   3
   4use collections::HashMap;
   5use editor::{
   6    display_map::{DisplaySnapshot, ToDisplayPoint},
   7    movement,
   8    scroll::autoscroll::Autoscroll,
   9    Bias, DisplayPoint, Editor,
  10};
  11use gpui::{actions, ViewContext, WindowContext};
  12use language::{Selection, SelectionGoal};
  13use workspace::Workspace;
  14
  15use crate::{
  16    motion::{start_of_line, Motion},
  17    object::Object,
  18    state::{Mode, Operator},
  19    utils::copy_selections_content,
  20    Vim,
  21};
  22
  23actions!(
  24    vim,
  25    [
  26        ToggleVisual,
  27        ToggleVisualLine,
  28        ToggleVisualBlock,
  29        VisualDelete,
  30        VisualYank,
  31        OtherEnd,
  32        SelectNext,
  33        SelectPrevious,
  34    ]
  35);
  36
  37pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
  38    workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
  39        toggle_mode(Mode::Visual, cx)
  40    });
  41    workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
  42        toggle_mode(Mode::VisualLine, cx)
  43    });
  44    workspace.register_action(
  45        |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
  46            toggle_mode(Mode::VisualBlock, cx)
  47        },
  48    );
  49    workspace.register_action(other_end);
  50    workspace.register_action(delete);
  51    workspace.register_action(yank);
  52
  53    workspace.register_action(|workspace, action, cx| {
  54        select_next(workspace, action, cx).ok();
  55    });
  56    workspace.register_action(|workspace, action, cx| {
  57        select_previous(workspace, action, cx).ok();
  58    });
  59}
  60
  61pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
  62    Vim::update(cx, |vim, cx| {
  63        vim.update_active_editor(cx, |editor, cx| {
  64            let text_layout_details = editor.text_layout_details(cx);
  65            if vim.state().mode == Mode::VisualBlock
  66                && !matches!(
  67                    motion,
  68                    Motion::EndOfLine {
  69                        display_lines: false
  70                    }
  71                )
  72            {
  73                let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
  74                visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
  75                    motion.move_point(map, point, goal, times, &text_layout_details)
  76                })
  77            } else {
  78                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
  79                    s.move_with(|map, selection| {
  80                        let was_reversed = selection.reversed;
  81                        let mut current_head = selection.head();
  82
  83                        // our motions assume the current character is after the cursor,
  84                        // but in (forward) visual mode the current character is just
  85                        // before the end of the selection.
  86
  87                        // If the file ends with a newline (which is common) we don't do this.
  88                        // so that if you go to the end of such a file you can use "up" to go
  89                        // to the previous line and have it work somewhat as expected.
  90                        if !selection.reversed
  91                            && !selection.is_empty()
  92                            && !(selection.end.column() == 0 && selection.end == map.max_point())
  93                        {
  94                            current_head = movement::left(map, selection.end)
  95                        }
  96
  97                        let Some((new_head, goal)) = motion.move_point(
  98                            map,
  99                            current_head,
 100                            selection.goal,
 101                            times,
 102                            &text_layout_details,
 103                        ) else {
 104                            return;
 105                        };
 106
 107                        selection.set_head(new_head, goal);
 108
 109                        // ensure the current character is included in the selection.
 110                        if !selection.reversed {
 111                            let next_point = if vim.state().mode == Mode::VisualBlock {
 112                                movement::saturating_right(map, selection.end)
 113                            } else {
 114                                movement::right(map, selection.end)
 115                            };
 116
 117                            if !(next_point.column() == 0 && next_point == map.max_point()) {
 118                                selection.end = next_point;
 119                            }
 120                        }
 121
 122                        // vim always ensures the anchor character stays selected.
 123                        // if our selection has reversed, we need to move the opposite end
 124                        // to ensure the anchor is still selected.
 125                        if was_reversed && !selection.reversed {
 126                            selection.start = movement::left(map, selection.start);
 127                        } else if !was_reversed && selection.reversed {
 128                            selection.end = movement::right(map, selection.end);
 129                        }
 130                    })
 131                });
 132            }
 133        });
 134    });
 135}
 136
 137pub fn visual_block_motion(
 138    preserve_goal: bool,
 139    editor: &mut Editor,
 140    cx: &mut ViewContext<Editor>,
 141    mut move_selection: impl FnMut(
 142        &DisplaySnapshot,
 143        DisplayPoint,
 144        SelectionGoal,
 145    ) -> Option<(DisplayPoint, SelectionGoal)>,
 146) {
 147    let text_layout_details = editor.text_layout_details(cx);
 148    editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
 149        let map = &s.display_map();
 150        let mut head = s.newest_anchor().head().to_display_point(map);
 151        let mut tail = s.oldest_anchor().tail().to_display_point(map);
 152
 153        let mut head_x = map.x_for_display_point(head, &text_layout_details);
 154        let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
 155
 156        let (start, end) = match s.newest_anchor().goal {
 157            SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
 158            SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
 159            _ => (tail_x.0, head_x.0),
 160        };
 161        let mut goal = SelectionGoal::HorizontalRange { start, end };
 162
 163        let was_reversed = tail_x > head_x;
 164        if !was_reversed && !preserve_goal {
 165            head = movement::saturating_left(map, head);
 166        }
 167
 168        let Some((new_head, _)) = move_selection(&map, head, goal) else {
 169            return;
 170        };
 171        head = new_head;
 172        head_x = map.x_for_display_point(head, &text_layout_details);
 173
 174        let is_reversed = tail_x > head_x;
 175        if was_reversed && !is_reversed {
 176            tail = movement::saturating_left(map, tail);
 177            tail_x = map.x_for_display_point(tail, &text_layout_details);
 178        } else if !was_reversed && is_reversed {
 179            tail = movement::saturating_right(map, tail);
 180            tail_x = map.x_for_display_point(tail, &text_layout_details);
 181        }
 182        if !is_reversed && !preserve_goal {
 183            head = movement::saturating_right(map, head);
 184            head_x = map.x_for_display_point(head, &text_layout_details);
 185        }
 186
 187        let positions = if is_reversed {
 188            head_x..tail_x
 189        } else {
 190            tail_x..head_x
 191        };
 192
 193        if !preserve_goal {
 194            goal = SelectionGoal::HorizontalRange {
 195                start: positions.start.0,
 196                end: positions.end.0,
 197            };
 198        }
 199
 200        let mut selections = Vec::new();
 201        let mut row = tail.row();
 202
 203        loop {
 204            let layed_out_line = map.layout_row(row, &text_layout_details);
 205            let start = DisplayPoint::new(
 206                row,
 207                layed_out_line.closest_index_for_x(positions.start) as u32,
 208            );
 209            let mut end = DisplayPoint::new(
 210                row,
 211                layed_out_line.closest_index_for_x(positions.end) as u32,
 212            );
 213            if end <= start {
 214                if start.column() == map.line_len(start.row()) {
 215                    end = start;
 216                } else {
 217                    end = movement::saturating_right(map, start);
 218                }
 219            }
 220
 221            if positions.start <= layed_out_line.width {
 222                let selection = Selection {
 223                    id: s.new_selection_id(),
 224                    start: start.to_point(map),
 225                    end: end.to_point(map),
 226                    reversed: is_reversed,
 227                    goal: goal.clone(),
 228                };
 229
 230                selections.push(selection);
 231            }
 232            if row == head.row() {
 233                break;
 234            }
 235            if tail.row() > head.row() {
 236                row -= 1
 237            } else {
 238                row += 1
 239            }
 240        }
 241
 242        s.select(selections);
 243    })
 244}
 245
 246pub fn visual_object(object: Object, cx: &mut WindowContext) {
 247    Vim::update(cx, |vim, cx| {
 248        if let Some(Operator::Object { around }) = vim.active_operator() {
 249            vim.pop_operator(cx);
 250            let current_mode = vim.state().mode;
 251            let target_mode = object.target_visual_mode(current_mode);
 252            if target_mode != current_mode {
 253                vim.switch_mode(target_mode, true, cx);
 254            }
 255
 256            vim.update_active_editor(cx, |editor, cx| {
 257                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
 258                    s.move_with(|map, selection| {
 259                        let mut head = selection.head();
 260
 261                        // all our motions assume that the current character is
 262                        // after the cursor; however in the case of a visual selection
 263                        // the current character is before the cursor.
 264                        if !selection.reversed {
 265                            head = movement::left(map, head);
 266                        }
 267
 268                        if let Some(range) = object.range(map, head, around) {
 269                            if !range.is_empty() {
 270                                let expand_both_ways = object.always_expands_both_ways()
 271                                    || selection.is_empty()
 272                                    || movement::right(map, selection.start) == selection.end;
 273
 274                                if expand_both_ways {
 275                                    selection.start = range.start;
 276                                    selection.end = range.end;
 277                                } else if selection.reversed {
 278                                    selection.start = range.start;
 279                                } else {
 280                                    selection.end = range.end;
 281                                }
 282                            }
 283                        }
 284                    });
 285                });
 286            });
 287        }
 288    });
 289}
 290
 291fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
 292    Vim::update(cx, |vim, cx| {
 293        if vim.state().mode == mode {
 294            vim.switch_mode(Mode::Normal, false, cx);
 295        } else {
 296            vim.switch_mode(mode, false, cx);
 297        }
 298    })
 299}
 300
 301pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
 302    Vim::update(cx, |vim, cx| {
 303        vim.update_active_editor(cx, |editor, cx| {
 304            editor.change_selections(None, cx, |s| {
 305                s.move_with(|_, selection| {
 306                    selection.reversed = !selection.reversed;
 307                })
 308            })
 309        })
 310    });
 311}
 312
 313pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
 314    Vim::update(cx, |vim, cx| {
 315        vim.record_current_action(cx);
 316        vim.update_active_editor(cx, |editor, cx| {
 317            let mut original_columns: HashMap<_, _> = Default::default();
 318            let line_mode = editor.selections.line_mode;
 319
 320            editor.transact(cx, |editor, cx| {
 321                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
 322                    s.move_with(|map, selection| {
 323                        if line_mode {
 324                            let mut position = selection.head();
 325                            if !selection.reversed {
 326                                position = movement::left(map, position);
 327                            }
 328                            original_columns.insert(selection.id, position.to_point(map).column);
 329                        }
 330                        selection.goal = SelectionGoal::None;
 331                    });
 332                });
 333                copy_selections_content(editor, line_mode, cx);
 334                editor.insert("", cx);
 335
 336                // Fixup cursor position after the deletion
 337                editor.set_clip_at_line_ends(true, cx);
 338                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
 339                    s.move_with(|map, selection| {
 340                        let mut cursor = selection.head().to_point(map);
 341
 342                        if let Some(column) = original_columns.get(&selection.id) {
 343                            cursor.column = *column
 344                        }
 345                        let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
 346                        selection.collapse_to(cursor, selection.goal)
 347                    });
 348                    if vim.state().mode == Mode::VisualBlock {
 349                        s.select_anchors(vec![s.first_anchor()])
 350                    }
 351                });
 352            })
 353        });
 354        vim.switch_mode(Mode::Normal, true, cx);
 355    });
 356}
 357
 358pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
 359    Vim::update(cx, |vim, cx| {
 360        vim.update_active_editor(cx, |editor, cx| {
 361            let line_mode = editor.selections.line_mode;
 362            copy_selections_content(editor, line_mode, cx);
 363            editor.change_selections(None, cx, |s| {
 364                s.move_with(|map, selection| {
 365                    if line_mode {
 366                        selection.start = start_of_line(map, false, selection.start);
 367                    };
 368                    selection.collapse_to(selection.start, SelectionGoal::None)
 369                });
 370                if vim.state().mode == Mode::VisualBlock {
 371                    s.select_anchors(vec![s.first_anchor()])
 372                }
 373            });
 374        });
 375        vim.switch_mode(Mode::Normal, true, cx);
 376    });
 377}
 378
 379pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
 380    Vim::update(cx, |vim, cx| {
 381        vim.stop_recording();
 382        vim.update_active_editor(cx, |editor, cx| {
 383            editor.transact(cx, |editor, cx| {
 384                let (display_map, selections) = editor.selections.all_adjusted_display(cx);
 385
 386                // Selections are biased right at the start. So we need to store
 387                // anchors that are biased left so that we can restore the selections
 388                // after the change
 389                let stable_anchors = editor
 390                    .selections
 391                    .disjoint_anchors()
 392                    .into_iter()
 393                    .map(|selection| {
 394                        let start = selection.start.bias_left(&display_map.buffer_snapshot);
 395                        start..start
 396                    })
 397                    .collect::<Vec<_>>();
 398
 399                let mut edits = Vec::new();
 400                for selection in selections.iter() {
 401                    let selection = selection.clone();
 402                    for row_range in
 403                        movement::split_display_range_by_lines(&display_map, selection.range())
 404                    {
 405                        let range = row_range.start.to_offset(&display_map, Bias::Right)
 406                            ..row_range.end.to_offset(&display_map, Bias::Right);
 407                        let text = text.repeat(range.len());
 408                        edits.push((range, text));
 409                    }
 410                }
 411
 412                editor.buffer().update(cx, |buffer, cx| {
 413                    buffer.edit(edits, None, cx);
 414                });
 415                editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
 416            });
 417        });
 418        vim.switch_mode(Mode::Normal, false, cx);
 419    });
 420}
 421
 422pub fn select_next(
 423    _: &mut Workspace,
 424    _: &SelectNext,
 425    cx: &mut ViewContext<Workspace>,
 426) -> Result<()> {
 427    Vim::update(cx, |vim, cx| {
 428        let count =
 429            vim.take_count(cx)
 430                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
 431        vim.update_active_editor(cx, |editor, cx| {
 432            for _ in 0..count {
 433                match editor.select_next(&Default::default(), cx) {
 434                    Err(a) => return Err(a),
 435                    _ => {}
 436                }
 437            }
 438            Ok(())
 439        })
 440    })
 441    .unwrap_or(Ok(()))
 442}
 443
 444pub fn select_previous(
 445    _: &mut Workspace,
 446    _: &SelectPrevious,
 447    cx: &mut ViewContext<Workspace>,
 448) -> Result<()> {
 449    Vim::update(cx, |vim, cx| {
 450        let count =
 451            vim.take_count(cx)
 452                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
 453        vim.update_active_editor(cx, |editor, cx| {
 454            for _ in 0..count {
 455                match editor.select_previous(&Default::default(), cx) {
 456                    Err(a) => return Err(a),
 457                    _ => {}
 458                }
 459            }
 460            Ok(())
 461        })
 462    })
 463    .unwrap_or(Ok(()))
 464}
 465
 466// #[cfg(test)]
 467// mod test {
 468//     use indoc::indoc;
 469//     use workspace::item::Item;
 470
 471//     use crate::{
 472//         state::Mode,
 473//         test::{NeovimBackedTestContext, VimTestContext},
 474//     };
 475
 476//     #[gpui::test]
 477//     async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
 478//         let mut cx = NeovimBackedTestContext::new(cx).await;
 479
 480//         cx.set_shared_state(indoc! {
 481//             "The ˇquick brown
 482//             fox jumps over
 483//             the lazy dog"
 484//         })
 485//         .await;
 486//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
 487
 488//         // entering visual mode should select the character
 489//         // under cursor
 490//         cx.simulate_shared_keystrokes(["v"]).await;
 491//         cx.assert_shared_state(indoc! { "The «qˇ»uick brown
 492//             fox jumps over
 493//             the lazy dog"})
 494//             .await;
 495//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
 496
 497//         // forwards motions should extend the selection
 498//         cx.simulate_shared_keystrokes(["w", "j"]).await;
 499//         cx.assert_shared_state(indoc! { "The «quick brown
 500//             fox jumps oˇ»ver
 501//             the lazy dog"})
 502//             .await;
 503
 504//         cx.simulate_shared_keystrokes(["escape"]).await;
 505//         assert_eq!(Mode::Normal, cx.neovim_mode().await);
 506//         cx.assert_shared_state(indoc! { "The quick brown
 507//             fox jumps ˇover
 508//             the lazy dog"})
 509//             .await;
 510
 511//         // motions work backwards
 512//         cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
 513//         cx.assert_shared_state(indoc! { "The «ˇquick brown
 514//             fox jumps o»ver
 515//             the lazy dog"})
 516//             .await;
 517
 518//         // works on empty lines
 519//         cx.set_shared_state(indoc! {"
 520//             a
 521//             ˇ
 522//             b
 523//             "})
 524//             .await;
 525//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
 526//         cx.simulate_shared_keystrokes(["v"]).await;
 527//         cx.assert_shared_state(indoc! {"
 528//             a
 529//             «
 530//             ˇ»b
 531//         "})
 532//             .await;
 533//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
 534
 535//         // toggles off again
 536//         cx.simulate_shared_keystrokes(["v"]).await;
 537//         cx.assert_shared_state(indoc! {"
 538//             a
 539//             ˇ
 540//             b
 541//             "})
 542//             .await;
 543
 544//         // works at the end of a document
 545//         cx.set_shared_state(indoc! {"
 546//             a
 547//             b
 548//             ˇ"})
 549//             .await;
 550
 551//         cx.simulate_shared_keystrokes(["v"]).await;
 552//         cx.assert_shared_state(indoc! {"
 553//             a
 554//             b
 555//             ˇ"})
 556//             .await;
 557//         assert_eq!(cx.mode(), cx.neovim_mode().await);
 558//     }
 559
 560//     #[gpui::test]
 561//     async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
 562//         let mut cx = NeovimBackedTestContext::new(cx).await;
 563
 564//         cx.set_shared_state(indoc! {
 565//             "The ˇquick brown
 566//             fox jumps over
 567//             the lazy dog"
 568//         })
 569//         .await;
 570//         cx.simulate_shared_keystrokes(["shift-v"]).await;
 571//         cx.assert_shared_state(indoc! { "The «qˇ»uick brown
 572//             fox jumps over
 573//             the lazy dog"})
 574//             .await;
 575//         assert_eq!(cx.mode(), cx.neovim_mode().await);
 576//         cx.simulate_shared_keystrokes(["x"]).await;
 577//         cx.assert_shared_state(indoc! { "fox ˇjumps over
 578//         the lazy dog"})
 579//             .await;
 580
 581//         // it should work on empty lines
 582//         cx.set_shared_state(indoc! {"
 583//             a
 584//             ˇ
 585//             b"})
 586//             .await;
 587//         cx.simulate_shared_keystrokes(["shift-v"]).await;
 588//         cx.assert_shared_state(indoc! { "
 589//             a
 590//             «
 591//             ˇ»b"})
 592//             .await;
 593//         cx.simulate_shared_keystrokes(["x"]).await;
 594//         cx.assert_shared_state(indoc! { "
 595//             a
 596//             ˇb"})
 597//             .await;
 598
 599//         // it should work at the end of the document
 600//         cx.set_shared_state(indoc! {"
 601//             a
 602//             b
 603//             ˇ"})
 604//             .await;
 605//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
 606//         cx.simulate_shared_keystrokes(["shift-v"]).await;
 607//         cx.assert_shared_state(indoc! {"
 608//             a
 609//             b
 610//             ˇ"})
 611//             .await;
 612//         assert_eq!(cx.mode(), cx.neovim_mode().await);
 613//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
 614//         cx.simulate_shared_keystrokes(["x"]).await;
 615//         cx.assert_shared_state(indoc! {"
 616//             a
 617//             ˇb"})
 618//             .await;
 619//     }
 620
 621//     #[gpui::test]
 622//     async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
 623//         let mut cx = NeovimBackedTestContext::new(cx).await;
 624
 625//         cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
 626//             .await;
 627
 628//         cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
 629//             .await;
 630//         cx.assert_binding_matches(
 631//             ["v", "w", "j", "x"],
 632//             indoc! {"
 633//                 The ˇquick brown
 634//                 fox jumps over
 635//                 the lazy dog"},
 636//         )
 637//         .await;
 638//         // Test pasting code copied on delete
 639//         cx.simulate_shared_keystrokes(["j", "p"]).await;
 640//         cx.assert_state_matches().await;
 641
 642//         let mut cx = cx.binding(["v", "w", "j", "x"]);
 643//         cx.assert_all(indoc! {"
 644//                 The ˇquick brown
 645//                 fox jumps over
 646//                 the ˇlazy dog"})
 647//             .await;
 648//         let mut cx = cx.binding(["v", "b", "k", "x"]);
 649//         cx.assert_all(indoc! {"
 650//                 The ˇquick brown
 651//                 fox jumps ˇover
 652//                 the ˇlazy dog"})
 653//             .await;
 654//     }
 655
 656//     #[gpui::test]
 657//     async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
 658//         let mut cx = NeovimBackedTestContext::new(cx).await;
 659
 660//         cx.set_shared_state(indoc! {"
 661//                 The quˇick brown
 662//                 fox jumps over
 663//                 the lazy dog"})
 664//             .await;
 665//         cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
 666//         cx.assert_state_matches().await;
 667
 668//         // Test pasting code copied on delete
 669//         cx.simulate_shared_keystroke("p").await;
 670//         cx.assert_state_matches().await;
 671
 672//         cx.set_shared_state(indoc! {"
 673//                 The quick brown
 674//                 fox jumps over
 675//                 the laˇzy dog"})
 676//             .await;
 677//         cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
 678//         cx.assert_state_matches().await;
 679//         cx.assert_shared_clipboard("the lazy dog\n").await;
 680
 681//         for marked_text in cx.each_marked_position(indoc! {"
 682//                         The quˇick brown
 683//                         fox jumps over
 684//                         the lazy dog"})
 685//         {
 686//             cx.set_shared_state(&marked_text).await;
 687//             cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
 688//             cx.assert_state_matches().await;
 689//             // Test pasting code copied on delete
 690//             cx.simulate_shared_keystroke("p").await;
 691//             cx.assert_state_matches().await;
 692//         }
 693
 694//         cx.set_shared_state(indoc! {"
 695//             The ˇlong line
 696//             should not
 697//             crash
 698//             "})
 699//             .await;
 700//         cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
 701//         cx.assert_state_matches().await;
 702//     }
 703
 704//     #[gpui::test]
 705//     async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
 706//         let mut cx = NeovimBackedTestContext::new(cx).await;
 707
 708//         cx.set_shared_state("The quick ˇbrown").await;
 709//         cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
 710//         cx.assert_shared_state("The quick ˇbrown").await;
 711//         cx.assert_shared_clipboard("brown").await;
 712
 713//         cx.set_shared_state(indoc! {"
 714//                 The ˇquick brown
 715//                 fox jumps over
 716//                 the lazy dog"})
 717//             .await;
 718//         cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
 719//         cx.assert_shared_state(indoc! {"
 720//                     The ˇquick brown
 721//                     fox jumps over
 722//                     the lazy dog"})
 723//             .await;
 724//         cx.assert_shared_clipboard(indoc! {"
 725//                 quick brown
 726//                 fox jumps o"})
 727//             .await;
 728
 729//         cx.set_shared_state(indoc! {"
 730//                     The quick brown
 731//                     fox jumps over
 732//                     the ˇlazy dog"})
 733//             .await;
 734//         cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
 735//         cx.assert_shared_state(indoc! {"
 736//                     The quick brown
 737//                     fox jumps over
 738//                     the ˇlazy dog"})
 739//             .await;
 740//         cx.assert_shared_clipboard("lazy d").await;
 741//         cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
 742//         cx.assert_shared_clipboard("the lazy dog\n").await;
 743
 744//         let mut cx = cx.binding(["v", "b", "k", "y"]);
 745//         cx.set_shared_state(indoc! {"
 746//                     The ˇquick brown
 747//                     fox jumps over
 748//                     the lazy dog"})
 749//             .await;
 750//         cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
 751//         cx.assert_shared_state(indoc! {"
 752//                     ˇThe quick brown
 753//                     fox jumps over
 754//                     the lazy dog"})
 755//             .await;
 756//         cx.assert_clipboard_content(Some("The q"));
 757
 758//         cx.set_shared_state(indoc! {"
 759//                     The quick brown
 760//                     fox ˇjumps over
 761//                     the lazy dog"})
 762//             .await;
 763//         cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
 764//             .await;
 765//         cx.assert_shared_state(indoc! {"
 766//                     The quick brown
 767//                     ˇfox jumps over
 768//                     the lazy dog"})
 769//             .await;
 770//         cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
 771//             .await;
 772//     }
 773
 774//     #[gpui::test]
 775//     async fn test_visual_block_mode(cx: &mut gpui::TestAppContext) {
 776//         let mut cx = NeovimBackedTestContext::new(cx).await;
 777
 778//         cx.set_shared_state(indoc! {
 779//             "The ˇquick brown
 780//              fox jumps over
 781//              the lazy dog"
 782//         })
 783//         .await;
 784//         cx.simulate_shared_keystrokes(["ctrl-v"]).await;
 785//         cx.assert_shared_state(indoc! {
 786//             "The «qˇ»uick brown
 787//             fox jumps over
 788//             the lazy dog"
 789//         })
 790//         .await;
 791//         cx.simulate_shared_keystrokes(["2", "down"]).await;
 792//         cx.assert_shared_state(indoc! {
 793//             "The «qˇ»uick brown
 794//             fox «jˇ»umps over
 795//             the «lˇ»azy dog"
 796//         })
 797//         .await;
 798//         cx.simulate_shared_keystrokes(["e"]).await;
 799//         cx.assert_shared_state(indoc! {
 800//             "The «quicˇ»k brown
 801//             fox «jumpˇ»s over
 802//             the «lazyˇ» dog"
 803//         })
 804//         .await;
 805//         cx.simulate_shared_keystrokes(["^"]).await;
 806//         cx.assert_shared_state(indoc! {
 807//             "«ˇThe q»uick brown
 808//             «ˇfox j»umps over
 809//             «ˇthe l»azy dog"
 810//         })
 811//         .await;
 812//         cx.simulate_shared_keystrokes(["$"]).await;
 813//         cx.assert_shared_state(indoc! {
 814//             "The «quick brownˇ»
 815//             fox «jumps overˇ»
 816//             the «lazy dogˇ»"
 817//         })
 818//         .await;
 819//         cx.simulate_shared_keystrokes(["shift-f", " "]).await;
 820//         cx.assert_shared_state(indoc! {
 821//             "The «quickˇ» brown
 822//             fox «jumpsˇ» over
 823//             the «lazy ˇ»dog"
 824//         })
 825//         .await;
 826
 827//         // toggling through visual mode works as expected
 828//         cx.simulate_shared_keystrokes(["v"]).await;
 829//         cx.assert_shared_state(indoc! {
 830//             "The «quick brown
 831//             fox jumps over
 832//             the lazy ˇ»dog"
 833//         })
 834//         .await;
 835//         cx.simulate_shared_keystrokes(["ctrl-v"]).await;
 836//         cx.assert_shared_state(indoc! {
 837//             "The «quickˇ» brown
 838//             fox «jumpsˇ» over
 839//             the «lazy ˇ»dog"
 840//         })
 841//         .await;
 842
 843//         cx.set_shared_state(indoc! {
 844//             "The ˇquick
 845//              brown
 846//              fox
 847//              jumps over the
 848
 849//              lazy dog
 850//             "
 851//         })
 852//         .await;
 853//         cx.simulate_shared_keystrokes(["ctrl-v", "down", "down"])
 854//             .await;
 855//         cx.assert_shared_state(indoc! {
 856//             "The«ˇ q»uick
 857//             bro«ˇwn»
 858//             foxˇ
 859//             jumps over the
 860
 861//             lazy dog
 862//             "
 863//         })
 864//         .await;
 865//         cx.simulate_shared_keystrokes(["down"]).await;
 866//         cx.assert_shared_state(indoc! {
 867//             "The «qˇ»uick
 868//             brow«nˇ»
 869//             fox
 870//             jump«sˇ» over the
 871
 872//             lazy dog
 873//             "
 874//         })
 875//         .await;
 876//         cx.simulate_shared_keystroke("left").await;
 877//         cx.assert_shared_state(indoc! {
 878//             "The«ˇ q»uick
 879//             bro«ˇwn»
 880//             foxˇ
 881//             jum«ˇps» over the
 882
 883//             lazy dog
 884//             "
 885//         })
 886//         .await;
 887//         cx.simulate_shared_keystrokes(["s", "o", "escape"]).await;
 888//         cx.assert_shared_state(indoc! {
 889//             "Theˇouick
 890//             broo
 891//             foxo
 892//             jumo over the
 893
 894//             lazy dog
 895//             "
 896//         })
 897//         .await;
 898
 899//         //https://github.com/zed-industries/community/issues/1950
 900//         cx.set_shared_state(indoc! {
 901//             "Theˇ quick brown
 902
 903//             fox jumps over
 904//             the lazy dog
 905//             "
 906//         })
 907//         .await;
 908//         cx.simulate_shared_keystrokes(["l", "ctrl-v", "j", "j"])
 909//             .await;
 910//         cx.assert_shared_state(indoc! {
 911//             "The «qˇ»uick brown
 912
 913//             fox «jˇ»umps over
 914//             the lazy dog
 915//             "
 916//         })
 917//         .await;
 918//     }
 919
 920//     #[gpui::test]
 921//     async fn test_visual_block_issue_2123(cx: &mut gpui::TestAppContext) {
 922//         let mut cx = NeovimBackedTestContext::new(cx).await;
 923
 924//         cx.set_shared_state(indoc! {
 925//             "The ˇquick brown
 926//             fox jumps over
 927//             the lazy dog
 928//             "
 929//         })
 930//         .await;
 931//         cx.simulate_shared_keystrokes(["ctrl-v", "right", "down"])
 932//             .await;
 933//         cx.assert_shared_state(indoc! {
 934//             "The «quˇ»ick brown
 935//             fox «juˇ»mps over
 936//             the lazy dog
 937//             "
 938//         })
 939//         .await;
 940//     }
 941
 942//     #[gpui::test]
 943//     async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
 944//         let mut cx = NeovimBackedTestContext::new(cx).await;
 945
 946//         cx.set_shared_state(indoc! {
 947//             "ˇThe quick brown
 948//             fox jumps over
 949//             the lazy dog
 950//             "
 951//         })
 952//         .await;
 953//         cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
 954//         cx.assert_shared_state(indoc! {
 955//             "«Tˇ»he quick brown
 956//             «fˇ»ox jumps over
 957//             «tˇ»he lazy dog
 958//             ˇ"
 959//         })
 960//         .await;
 961
 962//         cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
 963//             .await;
 964//         cx.assert_shared_state(indoc! {
 965//             "ˇkThe quick brown
 966//             kfox jumps over
 967//             kthe lazy dog
 968//             k"
 969//         })
 970//         .await;
 971
 972//         cx.set_shared_state(indoc! {
 973//             "ˇThe quick brown
 974//             fox jumps over
 975//             the lazy dog
 976//             "
 977//         })
 978//         .await;
 979//         cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
 980//         cx.assert_shared_state(indoc! {
 981//             "«Tˇ»he quick brown
 982//             «fˇ»ox jumps over
 983//             «tˇ»he lazy dog
 984//             ˇ"
 985//         })
 986//         .await;
 987//         cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
 988//         cx.assert_shared_state(indoc! {
 989//             "ˇkhe quick brown
 990//             kox jumps over
 991//             khe lazy dog
 992//             k"
 993//         })
 994//         .await;
 995//     }
 996
 997//     #[gpui::test]
 998//     async fn test_visual_object(cx: &mut gpui::TestAppContext) {
 999//         let mut cx = NeovimBackedTestContext::new(cx).await;
1000
1001//         cx.set_shared_state("hello (in [parˇens] o)").await;
1002//         cx.simulate_shared_keystrokes(["ctrl-v", "l"]).await;
1003//         cx.simulate_shared_keystrokes(["a", "]"]).await;
1004//         cx.assert_shared_state("hello (in «[parens]ˇ» o)").await;
1005//         assert_eq!(cx.mode(), Mode::Visual);
1006//         cx.simulate_shared_keystrokes(["i", "("]).await;
1007//         cx.assert_shared_state("hello («in [parens] oˇ»)").await;
1008
1009//         cx.set_shared_state("hello in a wˇord again.").await;
1010//         cx.simulate_shared_keystrokes(["ctrl-v", "l", "i", "w"])
1011//             .await;
1012//         cx.assert_shared_state("hello in a w«ordˇ» again.").await;
1013//         assert_eq!(cx.mode(), Mode::VisualBlock);
1014//         cx.simulate_shared_keystrokes(["o", "a", "s"]).await;
1015//         cx.assert_shared_state("«ˇhello in a word» again.").await;
1016//         assert_eq!(cx.mode(), Mode::Visual);
1017//     }
1018
1019//     #[gpui::test]
1020//     async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
1021//         let mut cx = VimTestContext::new(cx, true).await;
1022
1023//         cx.set_state("aˇbc", Mode::Normal);
1024//         cx.simulate_keystrokes(["ctrl-v"]);
1025//         assert_eq!(cx.mode(), Mode::VisualBlock);
1026//         cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
1027//         assert_eq!(cx.mode(), Mode::VisualBlock);
1028//     }
1029// }