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