editor_tests.rs

    1use super::*;
    2use crate::{
    3    scroll::scroll_amount::ScrollAmount,
    4    test::{
    5        assert_text_with_selections, build_editor, editor_hunks,
    6        editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
    7        expanded_hunks, expanded_hunks_background_highlights, select_ranges,
    8    },
    9    JoinLines,
   10};
   11use futures::StreamExt;
   12use gpui::{
   13    div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
   14    WindowBounds, WindowOptions,
   15};
   16use indoc::indoc;
   17use language::{
   18    language_settings::{
   19        AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
   20    },
   21    BracketPairConfig,
   22    Capability::ReadWrite,
   23    FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
   24    Point,
   25};
   26use language_settings::IndentGuideSettings;
   27use multi_buffer::MultiBufferIndentGuide;
   28use parking_lot::Mutex;
   29use project::project_settings::{LspSettings, ProjectSettings};
   30use project::FakeFs;
   31use serde_json::{self, json};
   32use std::sync::atomic;
   33use std::sync::atomic::AtomicUsize;
   34use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
   35use unindent::Unindent;
   36use util::{
   37    assert_set_eq,
   38    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
   39};
   40use workspace::{
   41    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   42    NavigationEntry, ViewId,
   43};
   44
   45#[gpui::test]
   46fn test_edit_events(cx: &mut TestAppContext) {
   47    init_test(cx, |_| {});
   48
   49    let buffer = cx.new_model(|cx| {
   50        let mut buffer = language::Buffer::local("123456", cx);
   51        buffer.set_group_interval(Duration::from_secs(1));
   52        buffer
   53    });
   54
   55    let events = Rc::new(RefCell::new(Vec::new()));
   56    let editor1 = cx.add_window({
   57        let events = events.clone();
   58        |cx| {
   59            let view = cx.view().clone();
   60            cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
   61                EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   62                EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
   63                _ => {}
   64            })
   65            .detach();
   66            Editor::for_buffer(buffer.clone(), None, cx)
   67        }
   68    });
   69
   70    let editor2 = cx.add_window({
   71        let events = events.clone();
   72        |cx| {
   73            cx.subscribe(
   74                &cx.view().clone(),
   75                move |_, _, event: &EditorEvent, _| match event {
   76                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
   77                    EditorEvent::BufferEdited => {
   78                        events.borrow_mut().push(("editor2", "buffer edited"))
   79                    }
   80                    _ => {}
   81                },
   82            )
   83            .detach();
   84            Editor::for_buffer(buffer.clone(), None, cx)
   85        }
   86    });
   87
   88    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
   89
   90    // Mutating editor 1 will emit an `Edited` event only for that editor.
   91    _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
   92    assert_eq!(
   93        mem::take(&mut *events.borrow_mut()),
   94        [
   95            ("editor1", "edited"),
   96            ("editor1", "buffer edited"),
   97            ("editor2", "buffer edited"),
   98        ]
   99    );
  100
  101    // Mutating editor 2 will emit an `Edited` event only for that editor.
  102    _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
  103    assert_eq!(
  104        mem::take(&mut *events.borrow_mut()),
  105        [
  106            ("editor2", "edited"),
  107            ("editor1", "buffer edited"),
  108            ("editor2", "buffer edited"),
  109        ]
  110    );
  111
  112    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  113    _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
  114    assert_eq!(
  115        mem::take(&mut *events.borrow_mut()),
  116        [
  117            ("editor1", "edited"),
  118            ("editor1", "buffer edited"),
  119            ("editor2", "buffer edited"),
  120        ]
  121    );
  122
  123    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // No event is emitted when the mutation is a no-op.
  157    _ = editor2.update(cx, |editor, cx| {
  158        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
  159
  160        editor.backspace(&Backspace, cx);
  161    });
  162    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  163}
  164
  165#[gpui::test]
  166fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  167    init_test(cx, |_| {});
  168
  169    let mut now = Instant::now();
  170    let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
  171    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
  172    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
  173    let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
  174
  175    _ = editor.update(cx, |editor, cx| {
  176        editor.start_transaction_at(now, cx);
  177        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
  178
  179        editor.insert("cd", cx);
  180        editor.end_transaction_at(now, cx);
  181        assert_eq!(editor.text(cx), "12cd56");
  182        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  183
  184        editor.start_transaction_at(now, cx);
  185        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
  186        editor.insert("e", cx);
  187        editor.end_transaction_at(now, cx);
  188        assert_eq!(editor.text(cx), "12cde6");
  189        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  190
  191        now += group_interval + Duration::from_millis(1);
  192        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
  193
  194        // Simulate an edit in another editor
  195        _ = buffer.update(cx, |buffer, cx| {
  196            buffer.start_transaction_at(now, cx);
  197            buffer.edit([(0..1, "a")], None, cx);
  198            buffer.edit([(1..1, "b")], None, cx);
  199            buffer.end_transaction_at(now, cx);
  200        });
  201
  202        assert_eq!(editor.text(cx), "ab2cde6");
  203        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  204
  205        // Last transaction happened past the group interval in a different editor.
  206        // Undo it individually and don't restore selections.
  207        editor.undo(&Undo, cx);
  208        assert_eq!(editor.text(cx), "12cde6");
  209        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  210
  211        // First two transactions happened within the group interval in this editor.
  212        // Undo them together and restore selections.
  213        editor.undo(&Undo, cx);
  214        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
  215        assert_eq!(editor.text(cx), "123456");
  216        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  217
  218        // Redo the first two transactions together.
  219        editor.redo(&Redo, cx);
  220        assert_eq!(editor.text(cx), "12cde6");
  221        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  222
  223        // Redo the last transaction on its own.
  224        editor.redo(&Redo, cx);
  225        assert_eq!(editor.text(cx), "ab2cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  227
  228        // Test empty transactions.
  229        editor.start_transaction_at(now, cx);
  230        editor.end_transaction_at(now, cx);
  231        editor.undo(&Undo, cx);
  232        assert_eq!(editor.text(cx), "12cde6");
  233    });
  234}
  235
  236#[gpui::test]
  237fn test_ime_composition(cx: &mut TestAppContext) {
  238    init_test(cx, |_| {});
  239
  240    let buffer = cx.new_model(|cx| {
  241        let mut buffer = language::Buffer::local("abcde", cx);
  242        // Ensure automatic grouping doesn't occur.
  243        buffer.set_group_interval(Duration::ZERO);
  244        buffer
  245    });
  246
  247    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
  248    cx.add_window(|cx| {
  249        let mut editor = build_editor(buffer.clone(), cx);
  250
  251        // Start a new IME composition.
  252        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
  253        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
  254        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
  255        assert_eq!(editor.text(cx), "äbcde");
  256        assert_eq!(
  257            editor.marked_text_ranges(cx),
  258            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  259        );
  260
  261        // Finalize IME composition.
  262        editor.replace_text_in_range(None, "ā", cx);
  263        assert_eq!(editor.text(cx), "ābcde");
  264        assert_eq!(editor.marked_text_ranges(cx), None);
  265
  266        // IME composition edits are grouped and are undone/redone at once.
  267        editor.undo(&Default::default(), cx);
  268        assert_eq!(editor.text(cx), "abcde");
  269        assert_eq!(editor.marked_text_ranges(cx), None);
  270        editor.redo(&Default::default(), cx);
  271        assert_eq!(editor.text(cx), "ābcde");
  272        assert_eq!(editor.marked_text_ranges(cx), None);
  273
  274        // Start a new IME composition.
  275        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
  276        assert_eq!(
  277            editor.marked_text_ranges(cx),
  278            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  279        );
  280
  281        // Undoing during an IME composition cancels it.
  282        editor.undo(&Default::default(), cx);
  283        assert_eq!(editor.text(cx), "ābcde");
  284        assert_eq!(editor.marked_text_ranges(cx), None);
  285
  286        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  287        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
  288        assert_eq!(editor.text(cx), "ābcdè");
  289        assert_eq!(
  290            editor.marked_text_ranges(cx),
  291            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  292        );
  293
  294        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  295        editor.replace_text_in_range(Some(4..999), "ę", cx);
  296        assert_eq!(editor.text(cx), "ābcdę");
  297        assert_eq!(editor.marked_text_ranges(cx), None);
  298
  299        // Start a new IME composition with multiple cursors.
  300        editor.change_selections(None, cx, |s| {
  301            s.select_ranges([
  302                OffsetUtf16(1)..OffsetUtf16(1),
  303                OffsetUtf16(3)..OffsetUtf16(3),
  304                OffsetUtf16(5)..OffsetUtf16(5),
  305            ])
  306        });
  307        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
  308        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  309        assert_eq!(
  310            editor.marked_text_ranges(cx),
  311            Some(vec![
  312                OffsetUtf16(0)..OffsetUtf16(3),
  313                OffsetUtf16(4)..OffsetUtf16(7),
  314                OffsetUtf16(8)..OffsetUtf16(11)
  315            ])
  316        );
  317
  318        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  319        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
  320        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![
  324                OffsetUtf16(1)..OffsetUtf16(2),
  325                OffsetUtf16(5)..OffsetUtf16(6),
  326                OffsetUtf16(9)..OffsetUtf16(10)
  327            ])
  328        );
  329
  330        // Finalize IME composition with multiple cursors.
  331        editor.replace_text_in_range(Some(9..10), "2", cx);
  332        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  333        assert_eq!(editor.marked_text_ranges(cx), None);
  334
  335        editor
  336    });
  337}
  338
  339#[gpui::test]
  340fn test_selection_with_mouse(cx: &mut TestAppContext) {
  341    init_test(cx, |_| {});
  342
  343    let editor = cx.add_window(|cx| {
  344        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  345        build_editor(buffer, cx)
  346    });
  347
  348    _ = editor.update(cx, |view, cx| {
  349        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
  350    });
  351    assert_eq!(
  352        editor
  353            .update(cx, |view, cx| view.selections.display_ranges(cx))
  354            .unwrap(),
  355        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  356    );
  357
  358    _ = editor.update(cx, |view, cx| {
  359        view.update_selection(
  360            DisplayPoint::new(DisplayRow(3), 3),
  361            0,
  362            gpui::Point::<f32>::default(),
  363            cx,
  364        );
  365    });
  366
  367    assert_eq!(
  368        editor
  369            .update(cx, |view, cx| view.selections.display_ranges(cx))
  370            .unwrap(),
  371        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  372    );
  373
  374    _ = editor.update(cx, |view, cx| {
  375        view.update_selection(
  376            DisplayPoint::new(DisplayRow(1), 1),
  377            0,
  378            gpui::Point::<f32>::default(),
  379            cx,
  380        );
  381    });
  382
  383    assert_eq!(
  384        editor
  385            .update(cx, |view, cx| view.selections.display_ranges(cx))
  386            .unwrap(),
  387        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  388    );
  389
  390    _ = editor.update(cx, |view, cx| {
  391        view.end_selection(cx);
  392        view.update_selection(
  393            DisplayPoint::new(DisplayRow(3), 3),
  394            0,
  395            gpui::Point::<f32>::default(),
  396            cx,
  397        );
  398    });
  399
  400    assert_eq!(
  401        editor
  402            .update(cx, |view, cx| view.selections.display_ranges(cx))
  403            .unwrap(),
  404        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  405    );
  406
  407    _ = editor.update(cx, |view, cx| {
  408        view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
  409        view.update_selection(
  410            DisplayPoint::new(DisplayRow(0), 0),
  411            0,
  412            gpui::Point::<f32>::default(),
  413            cx,
  414        );
  415    });
  416
  417    assert_eq!(
  418        editor
  419            .update(cx, |view, cx| view.selections.display_ranges(cx))
  420            .unwrap(),
  421        [
  422            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  423            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  424        ]
  425    );
  426
  427    _ = editor.update(cx, |view, cx| {
  428        view.end_selection(cx);
  429    });
  430
  431    assert_eq!(
  432        editor
  433            .update(cx, |view, cx| view.selections.display_ranges(cx))
  434            .unwrap(),
  435        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  436    );
  437}
  438
  439#[gpui::test]
  440fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  441    init_test(cx, |_| {});
  442
  443    let editor = cx.add_window(|cx| {
  444        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  445        build_editor(buffer, cx)
  446    });
  447
  448    _ = editor.update(cx, |view, cx| {
  449        view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
  450    });
  451
  452    _ = editor.update(cx, |view, cx| {
  453        view.end_selection(cx);
  454    });
  455
  456    _ = editor.update(cx, |view, cx| {
  457        view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
  458    });
  459
  460    _ = editor.update(cx, |view, cx| {
  461        view.end_selection(cx);
  462    });
  463
  464    assert_eq!(
  465        editor
  466            .update(cx, |view, cx| view.selections.display_ranges(cx))
  467            .unwrap(),
  468        [
  469            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  470            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  471        ]
  472    );
  473
  474    _ = editor.update(cx, |view, cx| {
  475        view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
  476    });
  477
  478    _ = editor.update(cx, |view, cx| {
  479        view.end_selection(cx);
  480    });
  481
  482    assert_eq!(
  483        editor
  484            .update(cx, |view, cx| view.selections.display_ranges(cx))
  485            .unwrap(),
  486        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  487    );
  488}
  489
  490#[gpui::test]
  491fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  492    init_test(cx, |_| {});
  493
  494    let view = cx.add_window(|cx| {
  495        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  496        build_editor(buffer, cx)
  497    });
  498
  499    _ = view.update(cx, |view, cx| {
  500        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
  501        assert_eq!(
  502            view.selections.display_ranges(cx),
  503            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  504        );
  505    });
  506
  507    _ = view.update(cx, |view, cx| {
  508        view.update_selection(
  509            DisplayPoint::new(DisplayRow(3), 3),
  510            0,
  511            gpui::Point::<f32>::default(),
  512            cx,
  513        );
  514        assert_eq!(
  515            view.selections.display_ranges(cx),
  516            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  517        );
  518    });
  519
  520    _ = view.update(cx, |view, cx| {
  521        view.cancel(&Cancel, cx);
  522        view.update_selection(
  523            DisplayPoint::new(DisplayRow(1), 1),
  524            0,
  525            gpui::Point::<f32>::default(),
  526            cx,
  527        );
  528        assert_eq!(
  529            view.selections.display_ranges(cx),
  530            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  531        );
  532    });
  533}
  534
  535#[gpui::test]
  536fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  537    init_test(cx, |_| {});
  538
  539    let view = cx.add_window(|cx| {
  540        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  541        build_editor(buffer, cx)
  542    });
  543
  544    _ = view.update(cx, |view, cx| {
  545        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
  546        assert_eq!(
  547            view.selections.display_ranges(cx),
  548            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  549        );
  550
  551        view.move_down(&Default::default(), cx);
  552        assert_eq!(
  553            view.selections.display_ranges(cx),
  554            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  555        );
  556
  557        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
  558        assert_eq!(
  559            view.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  561        );
  562
  563        view.move_up(&Default::default(), cx);
  564        assert_eq!(
  565            view.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  567        );
  568    });
  569}
  570
  571#[gpui::test]
  572fn test_clone(cx: &mut TestAppContext) {
  573    init_test(cx, |_| {});
  574
  575    let (text, selection_ranges) = marked_text_ranges(
  576        indoc! {"
  577            one
  578            two
  579            threeˇ
  580            four
  581            fiveˇ
  582        "},
  583        true,
  584    );
  585
  586    let editor = cx.add_window(|cx| {
  587        let buffer = MultiBuffer::build_simple(&text, cx);
  588        build_editor(buffer, cx)
  589    });
  590
  591    _ = editor.update(cx, |editor, cx| {
  592        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
  593        editor.fold_ranges(
  594            [
  595                (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  596                (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  597            ],
  598            true,
  599            cx,
  600        );
  601    });
  602
  603    let cloned_editor = editor
  604        .update(cx, |editor, cx| {
  605            cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
  606        })
  607        .unwrap()
  608        .unwrap();
  609
  610    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
  611    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
  612
  613    assert_eq!(
  614        cloned_editor
  615            .update(cx, |e, cx| e.display_text(cx))
  616            .unwrap(),
  617        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
  618    );
  619    assert_eq!(
  620        cloned_snapshot
  621            .folds_in_range(0..text.len())
  622            .collect::<Vec<_>>(),
  623        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  624    );
  625    assert_set_eq!(
  626        cloned_editor
  627            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
  628            .unwrap(),
  629        editor
  630            .update(cx, |editor, cx| editor.selections.ranges(cx))
  631            .unwrap()
  632    );
  633    assert_set_eq!(
  634        cloned_editor
  635            .update(cx, |e, cx| e.selections.display_ranges(cx))
  636            .unwrap(),
  637        editor
  638            .update(cx, |e, cx| e.selections.display_ranges(cx))
  639            .unwrap()
  640    );
  641}
  642
  643#[gpui::test]
  644async fn test_navigation_history(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    use workspace::item::Item;
  648
  649    let fs = FakeFs::new(cx.executor());
  650    let project = Project::test(fs, [], cx).await;
  651    let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
  652    let pane = workspace
  653        .update(cx, |workspace, _| workspace.active_pane().clone())
  654        .unwrap();
  655
  656    _ = workspace.update(cx, |_v, cx| {
  657        cx.new_view(|cx| {
  658            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  659            let mut editor = build_editor(buffer.clone(), cx);
  660            let handle = cx.view();
  661            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  662
  663            fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
  664                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  665            }
  666
  667            // Move the cursor a small distance.
  668            // Nothing is added to the navigation history.
  669            editor.change_selections(None, cx, |s| {
  670                s.select_display_ranges([
  671                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  672                ])
  673            });
  674            editor.change_selections(None, cx, |s| {
  675                s.select_display_ranges([
  676                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  677                ])
  678            });
  679            assert!(pop_history(&mut editor, cx).is_none());
  680
  681            // Move the cursor a large distance.
  682            // The history can jump back to the previous position.
  683            editor.change_selections(None, cx, |s| {
  684                s.select_display_ranges([
  685                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  686                ])
  687            });
  688            let nav_entry = pop_history(&mut editor, cx).unwrap();
  689            editor.navigate(nav_entry.data.unwrap(), cx);
  690            assert_eq!(nav_entry.item.id(), cx.entity_id());
  691            assert_eq!(
  692                editor.selections.display_ranges(cx),
  693                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  694            );
  695            assert!(pop_history(&mut editor, cx).is_none());
  696
  697            // Move the cursor a small distance via the mouse.
  698            // Nothing is added to the navigation history.
  699            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
  700            editor.end_selection(cx);
  701            assert_eq!(
  702                editor.selections.display_ranges(cx),
  703                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  704            );
  705            assert!(pop_history(&mut editor, cx).is_none());
  706
  707            // Move the cursor a large distance via the mouse.
  708            // The history can jump back to the previous position.
  709            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
  710            editor.end_selection(cx);
  711            assert_eq!(
  712                editor.selections.display_ranges(cx),
  713                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  714            );
  715            let nav_entry = pop_history(&mut editor, cx).unwrap();
  716            editor.navigate(nav_entry.data.unwrap(), cx);
  717            assert_eq!(nav_entry.item.id(), cx.entity_id());
  718            assert_eq!(
  719                editor.selections.display_ranges(cx),
  720                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  721            );
  722            assert!(pop_history(&mut editor, cx).is_none());
  723
  724            // Set scroll position to check later
  725            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
  726            let original_scroll_position = editor.scroll_manager.anchor();
  727
  728            // Jump to the end of the document and adjust scroll
  729            editor.move_to_end(&MoveToEnd, cx);
  730            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
  731            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  732
  733            let nav_entry = pop_history(&mut editor, cx).unwrap();
  734            editor.navigate(nav_entry.data.unwrap(), cx);
  735            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  736
  737            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  738            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  739            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  740            let invalid_point = Point::new(9999, 0);
  741            editor.navigate(
  742                Box::new(NavigationData {
  743                    cursor_anchor: invalid_anchor,
  744                    cursor_position: invalid_point,
  745                    scroll_anchor: ScrollAnchor {
  746                        anchor: invalid_anchor,
  747                        offset: Default::default(),
  748                    },
  749                    scroll_top_row: invalid_point.row,
  750                }),
  751                cx,
  752            );
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[editor.max_point(cx)..editor.max_point(cx)]
  756            );
  757            assert_eq!(
  758                editor.scroll_position(cx),
  759                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  760            );
  761
  762            editor
  763        })
  764    });
  765}
  766
  767#[gpui::test]
  768fn test_cancel(cx: &mut TestAppContext) {
  769    init_test(cx, |_| {});
  770
  771    let view = cx.add_window(|cx| {
  772        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  773        build_editor(buffer, cx)
  774    });
  775
  776    _ = view.update(cx, |view, cx| {
  777        view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
  778        view.update_selection(
  779            DisplayPoint::new(DisplayRow(1), 1),
  780            0,
  781            gpui::Point::<f32>::default(),
  782            cx,
  783        );
  784        view.end_selection(cx);
  785
  786        view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
  787        view.update_selection(
  788            DisplayPoint::new(DisplayRow(0), 3),
  789            0,
  790            gpui::Point::<f32>::default(),
  791            cx,
  792        );
  793        view.end_selection(cx);
  794        assert_eq!(
  795            view.selections.display_ranges(cx),
  796            [
  797                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  798                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  799            ]
  800        );
  801    });
  802
  803    _ = view.update(cx, |view, cx| {
  804        view.cancel(&Cancel, cx);
  805        assert_eq!(
  806            view.selections.display_ranges(cx),
  807            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  808        );
  809    });
  810
  811    _ = view.update(cx, |view, cx| {
  812        view.cancel(&Cancel, cx);
  813        assert_eq!(
  814            view.selections.display_ranges(cx),
  815            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  816        );
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_fold_action(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let view = cx.add_window(|cx| {
  825        let buffer = MultiBuffer::build_simple(
  826            &"
  827                impl Foo {
  828                    // Hello!
  829
  830                    fn a() {
  831                        1
  832                    }
  833
  834                    fn b() {
  835                        2
  836                    }
  837
  838                    fn c() {
  839                        3
  840                    }
  841                }
  842            "
  843            .unindent(),
  844            cx,
  845        );
  846        build_editor(buffer.clone(), cx)
  847    });
  848
  849    _ = view.update(cx, |view, cx| {
  850        view.change_selections(None, cx, |s| {
  851            s.select_display_ranges([
  852                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
  853            ]);
  854        });
  855        view.fold(&Fold, cx);
  856        assert_eq!(
  857            view.display_text(cx),
  858            "
  859                impl Foo {
  860                    // Hello!
  861
  862                    fn a() {
  863                        1
  864                    }
  865
  866                    fn b() {⋯
  867                    }
  868
  869                    fn c() {⋯
  870                    }
  871                }
  872            "
  873            .unindent(),
  874        );
  875
  876        view.fold(&Fold, cx);
  877        assert_eq!(
  878            view.display_text(cx),
  879            "
  880                impl Foo {⋯
  881                }
  882            "
  883            .unindent(),
  884        );
  885
  886        view.unfold_lines(&UnfoldLines, cx);
  887        assert_eq!(
  888            view.display_text(cx),
  889            "
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {⋯
  898                    }
  899
  900                    fn c() {⋯
  901                    }
  902                }
  903            "
  904            .unindent(),
  905        );
  906
  907        view.unfold_lines(&UnfoldLines, cx);
  908        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
  909    });
  910}
  911
  912#[gpui::test]
  913fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  914    init_test(cx, |_| {});
  915
  916    let view = cx.add_window(|cx| {
  917        let buffer = MultiBuffer::build_simple(
  918            &"
  919                class Foo:
  920                    # Hello!
  921
  922                    def a():
  923                        print(1)
  924
  925                    def b():
  926                        print(2)
  927
  928                    def c():
  929                        print(3)
  930            "
  931            .unindent(),
  932            cx,
  933        );
  934        build_editor(buffer.clone(), cx)
  935    });
  936
  937    _ = view.update(cx, |view, cx| {
  938        view.change_selections(None, cx, |s| {
  939            s.select_display_ranges([
  940                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
  941            ]);
  942        });
  943        view.fold(&Fold, cx);
  944        assert_eq!(
  945            view.display_text(cx),
  946            "
  947                class Foo:
  948                    # Hello!
  949
  950                    def a():
  951                        print(1)
  952
  953                    def b():⋯
  954
  955                    def c():⋯
  956            "
  957            .unindent(),
  958        );
  959
  960        view.fold(&Fold, cx);
  961        assert_eq!(
  962            view.display_text(cx),
  963            "
  964                class Foo:⋯
  965            "
  966            .unindent(),
  967        );
  968
  969        view.unfold_lines(&UnfoldLines, cx);
  970        assert_eq!(
  971            view.display_text(cx),
  972            "
  973                class Foo:
  974                    # Hello!
  975
  976                    def a():
  977                        print(1)
  978
  979                    def b():⋯
  980
  981                    def c():⋯
  982            "
  983            .unindent(),
  984        );
  985
  986        view.unfold_lines(&UnfoldLines, cx);
  987        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
  988    });
  989}
  990
  991#[gpui::test]
  992fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
  993    init_test(cx, |_| {});
  994
  995    let view = cx.add_window(|cx| {
  996        let buffer = MultiBuffer::build_simple(
  997            &"
  998                class Foo:
  999                    # Hello!
 1000
 1001                    def a():
 1002                        print(1)
 1003
 1004                    def b():
 1005                        print(2)
 1006
 1007
 1008                    def c():
 1009                        print(3)
 1010
 1011
 1012            "
 1013            .unindent(),
 1014            cx,
 1015        );
 1016        build_editor(buffer.clone(), cx)
 1017    });
 1018
 1019    _ = view.update(cx, |view, cx| {
 1020        view.change_selections(None, cx, |s| {
 1021            s.select_display_ranges([
 1022                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1023            ]);
 1024        });
 1025        view.fold(&Fold, cx);
 1026        assert_eq!(
 1027            view.display_text(cx),
 1028            "
 1029                class Foo:
 1030                    # Hello!
 1031
 1032                    def a():
 1033                        print(1)
 1034
 1035                    def b():⋯
 1036
 1037
 1038                    def c():⋯
 1039
 1040
 1041            "
 1042            .unindent(),
 1043        );
 1044
 1045        view.fold(&Fold, cx);
 1046        assert_eq!(
 1047            view.display_text(cx),
 1048            "
 1049                class Foo:⋯
 1050
 1051
 1052            "
 1053            .unindent(),
 1054        );
 1055
 1056        view.unfold_lines(&UnfoldLines, cx);
 1057        assert_eq!(
 1058            view.display_text(cx),
 1059            "
 1060                class Foo:
 1061                    # Hello!
 1062
 1063                    def a():
 1064                        print(1)
 1065
 1066                    def b():⋯
 1067
 1068
 1069                    def c():⋯
 1070
 1071
 1072            "
 1073            .unindent(),
 1074        );
 1075
 1076        view.unfold_lines(&UnfoldLines, cx);
 1077        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
 1078    });
 1079}
 1080
 1081#[gpui::test]
 1082fn test_move_cursor(cx: &mut TestAppContext) {
 1083    init_test(cx, |_| {});
 1084
 1085    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1086    let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 1087
 1088    _ = buffer.update(cx, |buffer, cx| {
 1089        buffer.edit(
 1090            vec![
 1091                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1092                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1093            ],
 1094            None,
 1095            cx,
 1096        );
 1097    });
 1098    _ = view.update(cx, |view, cx| {
 1099        assert_eq!(
 1100            view.selections.display_ranges(cx),
 1101            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1102        );
 1103
 1104        view.move_down(&MoveDown, cx);
 1105        assert_eq!(
 1106            view.selections.display_ranges(cx),
 1107            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1108        );
 1109
 1110        view.move_right(&MoveRight, cx);
 1111        assert_eq!(
 1112            view.selections.display_ranges(cx),
 1113            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1114        );
 1115
 1116        view.move_left(&MoveLeft, cx);
 1117        assert_eq!(
 1118            view.selections.display_ranges(cx),
 1119            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1120        );
 1121
 1122        view.move_up(&MoveUp, cx);
 1123        assert_eq!(
 1124            view.selections.display_ranges(cx),
 1125            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1126        );
 1127
 1128        view.move_to_end(&MoveToEnd, cx);
 1129        assert_eq!(
 1130            view.selections.display_ranges(cx),
 1131            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1132        );
 1133
 1134        view.move_to_beginning(&MoveToBeginning, cx);
 1135        assert_eq!(
 1136            view.selections.display_ranges(cx),
 1137            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1138        );
 1139
 1140        view.change_selections(None, cx, |s| {
 1141            s.select_display_ranges([
 1142                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1143            ]);
 1144        });
 1145        view.select_to_beginning(&SelectToBeginning, cx);
 1146        assert_eq!(
 1147            view.selections.display_ranges(cx),
 1148            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1149        );
 1150
 1151        view.select_to_end(&SelectToEnd, cx);
 1152        assert_eq!(
 1153            view.selections.display_ranges(cx),
 1154            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1155        );
 1156    });
 1157}
 1158
 1159// TODO: Re-enable this test
 1160#[cfg(target_os = "macos")]
 1161#[gpui::test]
 1162fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1163    init_test(cx, |_| {});
 1164
 1165    let view = cx.add_window(|cx| {
 1166        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
 1167        build_editor(buffer.clone(), cx)
 1168    });
 1169
 1170    assert_eq!('ⓐ'.len_utf8(), 3);
 1171    assert_eq!('α'.len_utf8(), 2);
 1172
 1173    _ = view.update(cx, |view, cx| {
 1174        view.fold_ranges(
 1175            vec![
 1176                (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
 1177                (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1178                (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1179            ],
 1180            true,
 1181            cx,
 1182        );
 1183        assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
 1184
 1185        view.move_right(&MoveRight, cx);
 1186        assert_eq!(
 1187            view.selections.display_ranges(cx),
 1188            &[empty_range(0, "".len())]
 1189        );
 1190        view.move_right(&MoveRight, cx);
 1191        assert_eq!(
 1192            view.selections.display_ranges(cx),
 1193            &[empty_range(0, "ⓐⓑ".len())]
 1194        );
 1195        view.move_right(&MoveRight, cx);
 1196        assert_eq!(
 1197            view.selections.display_ranges(cx),
 1198            &[empty_range(0, "ⓐⓑ⋯".len())]
 1199        );
 1200
 1201        view.move_down(&MoveDown, cx);
 1202        assert_eq!(
 1203            view.selections.display_ranges(cx),
 1204            &[empty_range(1, "ab⋯e".len())]
 1205        );
 1206        view.move_left(&MoveLeft, cx);
 1207        assert_eq!(
 1208            view.selections.display_ranges(cx),
 1209            &[empty_range(1, "ab⋯".len())]
 1210        );
 1211        view.move_left(&MoveLeft, cx);
 1212        assert_eq!(
 1213            view.selections.display_ranges(cx),
 1214            &[empty_range(1, "ab".len())]
 1215        );
 1216        view.move_left(&MoveLeft, cx);
 1217        assert_eq!(
 1218            view.selections.display_ranges(cx),
 1219            &[empty_range(1, "a".len())]
 1220        );
 1221
 1222        view.move_down(&MoveDown, cx);
 1223        assert_eq!(
 1224            view.selections.display_ranges(cx),
 1225            &[empty_range(2, "α".len())]
 1226        );
 1227        view.move_right(&MoveRight, cx);
 1228        assert_eq!(
 1229            view.selections.display_ranges(cx),
 1230            &[empty_range(2, "αβ".len())]
 1231        );
 1232        view.move_right(&MoveRight, cx);
 1233        assert_eq!(
 1234            view.selections.display_ranges(cx),
 1235            &[empty_range(2, "αβ⋯".len())]
 1236        );
 1237        view.move_right(&MoveRight, cx);
 1238        assert_eq!(
 1239            view.selections.display_ranges(cx),
 1240            &[empty_range(2, "αβ⋯ε".len())]
 1241        );
 1242
 1243        view.move_up(&MoveUp, cx);
 1244        assert_eq!(
 1245            view.selections.display_ranges(cx),
 1246            &[empty_range(1, "ab⋯e".len())]
 1247        );
 1248        view.move_down(&MoveDown, cx);
 1249        assert_eq!(
 1250            view.selections.display_ranges(cx),
 1251            &[empty_range(2, "αβ⋯ε".len())]
 1252        );
 1253        view.move_up(&MoveUp, cx);
 1254        assert_eq!(
 1255            view.selections.display_ranges(cx),
 1256            &[empty_range(1, "ab⋯e".len())]
 1257        );
 1258
 1259        view.move_up(&MoveUp, cx);
 1260        assert_eq!(
 1261            view.selections.display_ranges(cx),
 1262            &[empty_range(0, "ⓐⓑ".len())]
 1263        );
 1264        view.move_left(&MoveLeft, cx);
 1265        assert_eq!(
 1266            view.selections.display_ranges(cx),
 1267            &[empty_range(0, "".len())]
 1268        );
 1269        view.move_left(&MoveLeft, cx);
 1270        assert_eq!(
 1271            view.selections.display_ranges(cx),
 1272            &[empty_range(0, "".len())]
 1273        );
 1274    });
 1275}
 1276
 1277#[gpui::test]
 1278fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1279    init_test(cx, |_| {});
 1280
 1281    let view = cx.add_window(|cx| {
 1282        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1283        build_editor(buffer.clone(), cx)
 1284    });
 1285    _ = view.update(cx, |view, cx| {
 1286        view.change_selections(None, cx, |s| {
 1287            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1288        });
 1289        view.move_down(&MoveDown, cx);
 1290        assert_eq!(
 1291            view.selections.display_ranges(cx),
 1292            &[empty_range(1, "abcd".len())]
 1293        );
 1294
 1295        view.move_down(&MoveDown, cx);
 1296        assert_eq!(
 1297            view.selections.display_ranges(cx),
 1298            &[empty_range(2, "αβγ".len())]
 1299        );
 1300
 1301        view.move_down(&MoveDown, cx);
 1302        assert_eq!(
 1303            view.selections.display_ranges(cx),
 1304            &[empty_range(3, "abcd".len())]
 1305        );
 1306
 1307        view.move_down(&MoveDown, cx);
 1308        assert_eq!(
 1309            view.selections.display_ranges(cx),
 1310            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1311        );
 1312
 1313        view.move_up(&MoveUp, cx);
 1314        assert_eq!(
 1315            view.selections.display_ranges(cx),
 1316            &[empty_range(3, "abcd".len())]
 1317        );
 1318
 1319        view.move_up(&MoveUp, cx);
 1320        assert_eq!(
 1321            view.selections.display_ranges(cx),
 1322            &[empty_range(2, "αβγ".len())]
 1323        );
 1324    });
 1325}
 1326
 1327#[gpui::test]
 1328fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1329    init_test(cx, |_| {});
 1330    let move_to_beg = MoveToBeginningOfLine {
 1331        stop_at_soft_wraps: true,
 1332    };
 1333
 1334    let move_to_end = MoveToEndOfLine {
 1335        stop_at_soft_wraps: true,
 1336    };
 1337
 1338    let view = cx.add_window(|cx| {
 1339        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1340        build_editor(buffer, cx)
 1341    });
 1342    _ = view.update(cx, |view, cx| {
 1343        view.change_selections(None, cx, |s| {
 1344            s.select_display_ranges([
 1345                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1346                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1347            ]);
 1348        });
 1349    });
 1350
 1351    _ = view.update(cx, |view, cx| {
 1352        view.move_to_beginning_of_line(&move_to_beg, cx);
 1353        assert_eq!(
 1354            view.selections.display_ranges(cx),
 1355            &[
 1356                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1357                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1358            ]
 1359        );
 1360    });
 1361
 1362    _ = view.update(cx, |view, cx| {
 1363        view.move_to_beginning_of_line(&move_to_beg, cx);
 1364        assert_eq!(
 1365            view.selections.display_ranges(cx),
 1366            &[
 1367                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1368                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1369            ]
 1370        );
 1371    });
 1372
 1373    _ = view.update(cx, |view, cx| {
 1374        view.move_to_beginning_of_line(&move_to_beg, cx);
 1375        assert_eq!(
 1376            view.selections.display_ranges(cx),
 1377            &[
 1378                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1379                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1380            ]
 1381        );
 1382    });
 1383
 1384    _ = view.update(cx, |view, cx| {
 1385        view.move_to_end_of_line(&move_to_end, cx);
 1386        assert_eq!(
 1387            view.selections.display_ranges(cx),
 1388            &[
 1389                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1390                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1391            ]
 1392        );
 1393    });
 1394
 1395    // Moving to the end of line again is a no-op.
 1396    _ = view.update(cx, |view, cx| {
 1397        view.move_to_end_of_line(&move_to_end, cx);
 1398        assert_eq!(
 1399            view.selections.display_ranges(cx),
 1400            &[
 1401                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1402                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1403            ]
 1404        );
 1405    });
 1406
 1407    _ = view.update(cx, |view, cx| {
 1408        view.move_left(&MoveLeft, cx);
 1409        view.select_to_beginning_of_line(
 1410            &SelectToBeginningOfLine {
 1411                stop_at_soft_wraps: true,
 1412            },
 1413            cx,
 1414        );
 1415        assert_eq!(
 1416            view.selections.display_ranges(cx),
 1417            &[
 1418                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1419                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1420            ]
 1421        );
 1422    });
 1423
 1424    _ = view.update(cx, |view, cx| {
 1425        view.select_to_beginning_of_line(
 1426            &SelectToBeginningOfLine {
 1427                stop_at_soft_wraps: true,
 1428            },
 1429            cx,
 1430        );
 1431        assert_eq!(
 1432            view.selections.display_ranges(cx),
 1433            &[
 1434                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1435                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1436            ]
 1437        );
 1438    });
 1439
 1440    _ = view.update(cx, |view, cx| {
 1441        view.select_to_beginning_of_line(
 1442            &SelectToBeginningOfLine {
 1443                stop_at_soft_wraps: true,
 1444            },
 1445            cx,
 1446        );
 1447        assert_eq!(
 1448            view.selections.display_ranges(cx),
 1449            &[
 1450                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1451                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1452            ]
 1453        );
 1454    });
 1455
 1456    _ = view.update(cx, |view, cx| {
 1457        view.select_to_end_of_line(
 1458            &SelectToEndOfLine {
 1459                stop_at_soft_wraps: true,
 1460            },
 1461            cx,
 1462        );
 1463        assert_eq!(
 1464            view.selections.display_ranges(cx),
 1465            &[
 1466                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1467                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1468            ]
 1469        );
 1470    });
 1471
 1472    _ = view.update(cx, |view, cx| {
 1473        view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
 1474        assert_eq!(view.display_text(cx), "ab\n  de");
 1475        assert_eq!(
 1476            view.selections.display_ranges(cx),
 1477            &[
 1478                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1479                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1480            ]
 1481        );
 1482    });
 1483
 1484    _ = view.update(cx, |view, cx| {
 1485        view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
 1486        assert_eq!(view.display_text(cx), "\n");
 1487        assert_eq!(
 1488            view.selections.display_ranges(cx),
 1489            &[
 1490                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1491                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1492            ]
 1493        );
 1494    });
 1495}
 1496
 1497#[gpui::test]
 1498fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1499    init_test(cx, |_| {});
 1500    let move_to_beg = MoveToBeginningOfLine {
 1501        stop_at_soft_wraps: false,
 1502    };
 1503
 1504    let move_to_end = MoveToEndOfLine {
 1505        stop_at_soft_wraps: false,
 1506    };
 1507
 1508    let view = cx.add_window(|cx| {
 1509        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1510        build_editor(buffer, cx)
 1511    });
 1512
 1513    _ = view.update(cx, |view, cx| {
 1514        view.set_wrap_width(Some(140.0.into()), cx);
 1515
 1516        // We expect the following lines after wrapping
 1517        // ```
 1518        // thequickbrownfox
 1519        // jumpedoverthelazydo
 1520        // gs
 1521        // ```
 1522        // The final `gs` was soft-wrapped onto a new line.
 1523        assert_eq!(
 1524            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1525            view.display_text(cx),
 1526        );
 1527
 1528        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1529        // Start the cursor at the `k` on the first line
 1530        view.change_selections(None, cx, |s| {
 1531            s.select_display_ranges([
 1532                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1533            ]);
 1534        });
 1535
 1536        // Moving to the beginning of the line should put us at the beginning of the line.
 1537        view.move_to_beginning_of_line(&move_to_beg, cx);
 1538        assert_eq!(
 1539            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1540            view.selections.display_ranges(cx)
 1541        );
 1542
 1543        // Moving to the end of the line should put us at the end of the line.
 1544        view.move_to_end_of_line(&move_to_end, cx);
 1545        assert_eq!(
 1546            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1547            view.selections.display_ranges(cx)
 1548        );
 1549
 1550        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1551        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1552        view.change_selections(None, cx, |s| {
 1553            s.select_display_ranges([
 1554                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1555            ]);
 1556        });
 1557
 1558        // Moving to the beginning of the line should put us at the start of the second line of
 1559        // display text, i.e., the `j`.
 1560        view.move_to_beginning_of_line(&move_to_beg, cx);
 1561        assert_eq!(
 1562            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1563            view.selections.display_ranges(cx)
 1564        );
 1565
 1566        // Moving to the beginning of the line again should be a no-op.
 1567        view.move_to_beginning_of_line(&move_to_beg, cx);
 1568        assert_eq!(
 1569            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1570            view.selections.display_ranges(cx)
 1571        );
 1572
 1573        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1574        // next display line.
 1575        view.move_to_end_of_line(&move_to_end, cx);
 1576        assert_eq!(
 1577            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1578            view.selections.display_ranges(cx)
 1579        );
 1580
 1581        // Moving to the end of the line again should be a no-op.
 1582        view.move_to_end_of_line(&move_to_end, cx);
 1583        assert_eq!(
 1584            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1585            view.selections.display_ranges(cx)
 1586        );
 1587    });
 1588}
 1589
 1590#[gpui::test]
 1591fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1592    init_test(cx, |_| {});
 1593
 1594    let view = cx.add_window(|cx| {
 1595        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1596        build_editor(buffer, cx)
 1597    });
 1598    _ = view.update(cx, |view, cx| {
 1599        view.change_selections(None, cx, |s| {
 1600            s.select_display_ranges([
 1601                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1602                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1603            ])
 1604        });
 1605
 1606        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1607        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
 1608
 1609        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1610        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
 1611
 1612        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1613        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
 1614
 1615        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1616        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
 1617
 1618        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1619        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
 1620
 1621        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1622        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
 1623
 1624        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1625        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
 1626
 1627        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1628        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
 1629
 1630        view.move_right(&MoveRight, cx);
 1631        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
 1632        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
 1633
 1634        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
 1635        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
 1636
 1637        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
 1638        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
 1639    });
 1640}
 1641
 1642#[gpui::test]
 1643fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1644    init_test(cx, |_| {});
 1645
 1646    let view = cx.add_window(|cx| {
 1647        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1648        build_editor(buffer, cx)
 1649    });
 1650
 1651    _ = view.update(cx, |view, cx| {
 1652        view.set_wrap_width(Some(140.0.into()), cx);
 1653        assert_eq!(
 1654            view.display_text(cx),
 1655            "use one::{\n    two::three::\n    four::five\n};"
 1656        );
 1657
 1658        view.change_selections(None, cx, |s| {
 1659            s.select_display_ranges([
 1660                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1661            ]);
 1662        });
 1663
 1664        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1665        assert_eq!(
 1666            view.selections.display_ranges(cx),
 1667            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1668        );
 1669
 1670        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1671        assert_eq!(
 1672            view.selections.display_ranges(cx),
 1673            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1674        );
 1675
 1676        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1677        assert_eq!(
 1678            view.selections.display_ranges(cx),
 1679            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1680        );
 1681
 1682        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1683        assert_eq!(
 1684            view.selections.display_ranges(cx),
 1685            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 1686        );
 1687
 1688        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1689        assert_eq!(
 1690            view.selections.display_ranges(cx),
 1691            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1692        );
 1693
 1694        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1695        assert_eq!(
 1696            view.selections.display_ranges(cx),
 1697            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1698        );
 1699    });
 1700}
 1701
 1702#[gpui::test]
 1703async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
 1704    init_test(cx, |_| {});
 1705    let mut cx = EditorTestContext::new(cx).await;
 1706
 1707    let line_height = cx.editor(|editor, cx| {
 1708        editor
 1709            .style()
 1710            .unwrap()
 1711            .text
 1712            .line_height_in_pixels(cx.rem_size())
 1713    });
 1714    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 1715
 1716    cx.set_state(
 1717        &r#"ˇone
 1718        two
 1719
 1720        three
 1721        fourˇ
 1722        five
 1723
 1724        six"#
 1725            .unindent(),
 1726    );
 1727
 1728    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1729    cx.assert_editor_state(
 1730        &r#"one
 1731        two
 1732        ˇ
 1733        three
 1734        four
 1735        five
 1736        ˇ
 1737        six"#
 1738            .unindent(),
 1739    );
 1740
 1741    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1742    cx.assert_editor_state(
 1743        &r#"one
 1744        two
 1745
 1746        three
 1747        four
 1748        five
 1749        ˇ
 1750        sixˇ"#
 1751            .unindent(),
 1752    );
 1753
 1754    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1755    cx.assert_editor_state(
 1756        &r#"one
 1757        two
 1758
 1759        three
 1760        four
 1761        five
 1762
 1763        sixˇ"#
 1764            .unindent(),
 1765    );
 1766
 1767    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1768    cx.assert_editor_state(
 1769        &r#"one
 1770        two
 1771
 1772        three
 1773        four
 1774        five
 1775        ˇ
 1776        six"#
 1777            .unindent(),
 1778    );
 1779
 1780    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1781    cx.assert_editor_state(
 1782        &r#"one
 1783        two
 1784        ˇ
 1785        three
 1786        four
 1787        five
 1788
 1789        six"#
 1790            .unindent(),
 1791    );
 1792
 1793    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1794    cx.assert_editor_state(
 1795        &r#"ˇone
 1796        two
 1797
 1798        three
 1799        four
 1800        five
 1801
 1802        six"#
 1803            .unindent(),
 1804    );
 1805}
 1806
 1807#[gpui::test]
 1808async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
 1809    init_test(cx, |_| {});
 1810    let mut cx = EditorTestContext::new(cx).await;
 1811    let line_height = cx.editor(|editor, cx| {
 1812        editor
 1813            .style()
 1814            .unwrap()
 1815            .text
 1816            .line_height_in_pixels(cx.rem_size())
 1817    });
 1818    let window = cx.window;
 1819    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 1820
 1821    cx.set_state(
 1822        &r#"ˇone
 1823        two
 1824        three
 1825        four
 1826        five
 1827        six
 1828        seven
 1829        eight
 1830        nine
 1831        ten
 1832        "#,
 1833    );
 1834
 1835    cx.update_editor(|editor, cx| {
 1836        assert_eq!(
 1837            editor.snapshot(cx).scroll_position(),
 1838            gpui::Point::new(0., 0.)
 1839        );
 1840        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 1841        assert_eq!(
 1842            editor.snapshot(cx).scroll_position(),
 1843            gpui::Point::new(0., 3.)
 1844        );
 1845        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 1846        assert_eq!(
 1847            editor.snapshot(cx).scroll_position(),
 1848            gpui::Point::new(0., 6.)
 1849        );
 1850        editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
 1851        assert_eq!(
 1852            editor.snapshot(cx).scroll_position(),
 1853            gpui::Point::new(0., 3.)
 1854        );
 1855
 1856        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
 1857        assert_eq!(
 1858            editor.snapshot(cx).scroll_position(),
 1859            gpui::Point::new(0., 1.)
 1860        );
 1861        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
 1862        assert_eq!(
 1863            editor.snapshot(cx).scroll_position(),
 1864            gpui::Point::new(0., 3.)
 1865        );
 1866    });
 1867}
 1868
 1869#[gpui::test]
 1870async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
 1871    init_test(cx, |_| {});
 1872    let mut cx = EditorTestContext::new(cx).await;
 1873
 1874    let line_height = cx.update_editor(|editor, cx| {
 1875        editor.set_vertical_scroll_margin(2, cx);
 1876        editor
 1877            .style()
 1878            .unwrap()
 1879            .text
 1880            .line_height_in_pixels(cx.rem_size())
 1881    });
 1882    let window = cx.window;
 1883    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 1884
 1885    cx.set_state(
 1886        &r#"ˇone
 1887            two
 1888            three
 1889            four
 1890            five
 1891            six
 1892            seven
 1893            eight
 1894            nine
 1895            ten
 1896        "#,
 1897    );
 1898    cx.update_editor(|editor, cx| {
 1899        assert_eq!(
 1900            editor.snapshot(cx).scroll_position(),
 1901            gpui::Point::new(0., 0.0)
 1902        );
 1903    });
 1904
 1905    // Add a cursor below the visible area. Since both cursors cannot fit
 1906    // on screen, the editor autoscrolls to reveal the newest cursor, and
 1907    // allows the vertical scroll margin below that cursor.
 1908    cx.update_editor(|editor, cx| {
 1909        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
 1910            selections.select_ranges([
 1911                Point::new(0, 0)..Point::new(0, 0),
 1912                Point::new(6, 0)..Point::new(6, 0),
 1913            ]);
 1914        })
 1915    });
 1916    cx.update_editor(|editor, cx| {
 1917        assert_eq!(
 1918            editor.snapshot(cx).scroll_position(),
 1919            gpui::Point::new(0., 3.0)
 1920        );
 1921    });
 1922
 1923    // Move down. The editor cursor scrolls down to track the newest cursor.
 1924    cx.update_editor(|editor, cx| {
 1925        editor.move_down(&Default::default(), cx);
 1926    });
 1927    cx.update_editor(|editor, cx| {
 1928        assert_eq!(
 1929            editor.snapshot(cx).scroll_position(),
 1930            gpui::Point::new(0., 4.0)
 1931        );
 1932    });
 1933
 1934    // Add a cursor above the visible area. Since both cursors fit on screen,
 1935    // the editor scrolls to show both.
 1936    cx.update_editor(|editor, cx| {
 1937        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
 1938            selections.select_ranges([
 1939                Point::new(1, 0)..Point::new(1, 0),
 1940                Point::new(6, 0)..Point::new(6, 0),
 1941            ]);
 1942        })
 1943    });
 1944    cx.update_editor(|editor, cx| {
 1945        assert_eq!(
 1946            editor.snapshot(cx).scroll_position(),
 1947            gpui::Point::new(0., 1.0)
 1948        );
 1949    });
 1950}
 1951
 1952#[gpui::test]
 1953async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
 1954    init_test(cx, |_| {});
 1955    let mut cx = EditorTestContext::new(cx).await;
 1956
 1957    let line_height = cx.editor(|editor, cx| {
 1958        editor
 1959            .style()
 1960            .unwrap()
 1961            .text
 1962            .line_height_in_pixels(cx.rem_size())
 1963    });
 1964    let window = cx.window;
 1965    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 1966    cx.set_state(
 1967        &r#"
 1968        ˇone
 1969        two
 1970        threeˇ
 1971        four
 1972        five
 1973        six
 1974        seven
 1975        eight
 1976        nine
 1977        ten
 1978        "#
 1979        .unindent(),
 1980    );
 1981
 1982    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
 1983    cx.assert_editor_state(
 1984        &r#"
 1985        one
 1986        two
 1987        three
 1988        ˇfour
 1989        five
 1990        sixˇ
 1991        seven
 1992        eight
 1993        nine
 1994        ten
 1995        "#
 1996        .unindent(),
 1997    );
 1998
 1999    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
 2000    cx.assert_editor_state(
 2001        &r#"
 2002        one
 2003        two
 2004        three
 2005        four
 2006        five
 2007        six
 2008        ˇseven
 2009        eight
 2010        nineˇ
 2011        ten
 2012        "#
 2013        .unindent(),
 2014    );
 2015
 2016    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
 2017    cx.assert_editor_state(
 2018        &r#"
 2019        one
 2020        two
 2021        three
 2022        ˇfour
 2023        five
 2024        sixˇ
 2025        seven
 2026        eight
 2027        nine
 2028        ten
 2029        "#
 2030        .unindent(),
 2031    );
 2032
 2033    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
 2034    cx.assert_editor_state(
 2035        &r#"
 2036        ˇone
 2037        two
 2038        threeˇ
 2039        four
 2040        five
 2041        six
 2042        seven
 2043        eight
 2044        nine
 2045        ten
 2046        "#
 2047        .unindent(),
 2048    );
 2049
 2050    // Test select collapsing
 2051    cx.update_editor(|editor, cx| {
 2052        editor.move_page_down(&MovePageDown::default(), cx);
 2053        editor.move_page_down(&MovePageDown::default(), cx);
 2054        editor.move_page_down(&MovePageDown::default(), cx);
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"
 2058        one
 2059        two
 2060        three
 2061        four
 2062        five
 2063        six
 2064        seven
 2065        eight
 2066        nine
 2067        ˇten
 2068        ˇ"#
 2069        .unindent(),
 2070    );
 2071}
 2072
 2073#[gpui::test]
 2074async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
 2075    init_test(cx, |_| {});
 2076    let mut cx = EditorTestContext::new(cx).await;
 2077    cx.set_state("one «two threeˇ» four");
 2078    cx.update_editor(|editor, cx| {
 2079        editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
 2080        assert_eq!(editor.text(cx), " four");
 2081    });
 2082}
 2083
 2084#[gpui::test]
 2085fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2086    init_test(cx, |_| {});
 2087
 2088    let view = cx.add_window(|cx| {
 2089        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2090        build_editor(buffer.clone(), cx)
 2091    });
 2092
 2093    _ = view.update(cx, |view, cx| {
 2094        view.change_selections(None, cx, |s| {
 2095            s.select_display_ranges([
 2096                // an empty selection - the preceding word fragment is deleted
 2097                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2098                // characters selected - they are deleted
 2099                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2100            ])
 2101        });
 2102        view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
 2103        assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
 2104    });
 2105
 2106    _ = view.update(cx, |view, cx| {
 2107        view.change_selections(None, cx, |s| {
 2108            s.select_display_ranges([
 2109                // an empty selection - the following word fragment is deleted
 2110                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2111                // characters selected - they are deleted
 2112                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2113            ])
 2114        });
 2115        view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
 2116        assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
 2117    });
 2118}
 2119
 2120#[gpui::test]
 2121fn test_newline(cx: &mut TestAppContext) {
 2122    init_test(cx, |_| {});
 2123
 2124    let view = cx.add_window(|cx| {
 2125        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2126        build_editor(buffer.clone(), cx)
 2127    });
 2128
 2129    _ = view.update(cx, |view, cx| {
 2130        view.change_selections(None, cx, |s| {
 2131            s.select_display_ranges([
 2132                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2133                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2134                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2135            ])
 2136        });
 2137
 2138        view.newline(&Newline, cx);
 2139        assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2140    });
 2141}
 2142
 2143#[gpui::test]
 2144fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2145    init_test(cx, |_| {});
 2146
 2147    let editor = cx.add_window(|cx| {
 2148        let buffer = MultiBuffer::build_simple(
 2149            "
 2150                a
 2151                b(
 2152                    X
 2153                )
 2154                c(
 2155                    X
 2156                )
 2157            "
 2158            .unindent()
 2159            .as_str(),
 2160            cx,
 2161        );
 2162        let mut editor = build_editor(buffer.clone(), cx);
 2163        editor.change_selections(None, cx, |s| {
 2164            s.select_ranges([
 2165                Point::new(2, 4)..Point::new(2, 5),
 2166                Point::new(5, 4)..Point::new(5, 5),
 2167            ])
 2168        });
 2169        editor
 2170    });
 2171
 2172    _ = editor.update(cx, |editor, cx| {
 2173        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2174        editor.buffer.update(cx, |buffer, cx| {
 2175            buffer.edit(
 2176                [
 2177                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2178                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2179                ],
 2180                None,
 2181                cx,
 2182            );
 2183            assert_eq!(
 2184                buffer.read(cx).text(),
 2185                "
 2186                    a
 2187                    b()
 2188                    c()
 2189                "
 2190                .unindent()
 2191            );
 2192        });
 2193        assert_eq!(
 2194            editor.selections.ranges(cx),
 2195            &[
 2196                Point::new(1, 2)..Point::new(1, 2),
 2197                Point::new(2, 2)..Point::new(2, 2),
 2198            ],
 2199        );
 2200
 2201        editor.newline(&Newline, cx);
 2202        assert_eq!(
 2203            editor.text(cx),
 2204            "
 2205                a
 2206                b(
 2207                )
 2208                c(
 2209                )
 2210            "
 2211            .unindent()
 2212        );
 2213
 2214        // The selections are moved after the inserted newlines
 2215        assert_eq!(
 2216            editor.selections.ranges(cx),
 2217            &[
 2218                Point::new(2, 0)..Point::new(2, 0),
 2219                Point::new(4, 0)..Point::new(4, 0),
 2220            ],
 2221        );
 2222    });
 2223}
 2224
 2225#[gpui::test]
 2226async fn test_newline_above(cx: &mut gpui::TestAppContext) {
 2227    init_test(cx, |settings| {
 2228        settings.defaults.tab_size = NonZeroU32::new(4)
 2229    });
 2230
 2231    let language = Arc::new(
 2232        Language::new(
 2233            LanguageConfig::default(),
 2234            Some(tree_sitter_rust::language()),
 2235        )
 2236        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2237        .unwrap(),
 2238    );
 2239
 2240    let mut cx = EditorTestContext::new(cx).await;
 2241    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2242    cx.set_state(indoc! {"
 2243        const a: ˇA = (
 2244 2245                «const_functionˇ»(ˇ),
 2246                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2247 2248        ˇ);ˇ
 2249    "});
 2250
 2251    cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
 2252    cx.assert_editor_state(indoc! {"
 2253        ˇ
 2254        const a: A = (
 2255            ˇ
 2256            (
 2257                ˇ
 2258                ˇ
 2259                const_function(),
 2260                ˇ
 2261                ˇ
 2262                ˇ
 2263                ˇ
 2264                something_else,
 2265                ˇ
 2266            )
 2267            ˇ
 2268            ˇ
 2269        );
 2270    "});
 2271}
 2272
 2273#[gpui::test]
 2274async fn test_newline_below(cx: &mut gpui::TestAppContext) {
 2275    init_test(cx, |settings| {
 2276        settings.defaults.tab_size = NonZeroU32::new(4)
 2277    });
 2278
 2279    let language = Arc::new(
 2280        Language::new(
 2281            LanguageConfig::default(),
 2282            Some(tree_sitter_rust::language()),
 2283        )
 2284        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2285        .unwrap(),
 2286    );
 2287
 2288    let mut cx = EditorTestContext::new(cx).await;
 2289    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2290    cx.set_state(indoc! {"
 2291        const a: ˇA = (
 2292 2293                «const_functionˇ»(ˇ),
 2294                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2295 2296        ˇ);ˇ
 2297    "});
 2298
 2299    cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
 2300    cx.assert_editor_state(indoc! {"
 2301        const a: A = (
 2302            ˇ
 2303            (
 2304                ˇ
 2305                const_function(),
 2306                ˇ
 2307                ˇ
 2308                something_else,
 2309                ˇ
 2310                ˇ
 2311                ˇ
 2312                ˇ
 2313            )
 2314            ˇ
 2315        );
 2316        ˇ
 2317        ˇ
 2318    "});
 2319}
 2320
 2321#[gpui::test]
 2322async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
 2323    init_test(cx, |settings| {
 2324        settings.defaults.tab_size = NonZeroU32::new(4)
 2325    });
 2326
 2327    let language = Arc::new(Language::new(
 2328        LanguageConfig {
 2329            line_comments: vec!["//".into()],
 2330            ..LanguageConfig::default()
 2331        },
 2332        None,
 2333    ));
 2334    {
 2335        let mut cx = EditorTestContext::new(cx).await;
 2336        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2337        cx.set_state(indoc! {"
 2338        // Fooˇ
 2339    "});
 2340
 2341        cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2342        cx.assert_editor_state(indoc! {"
 2343        // Foo
 2344        //ˇ
 2345    "});
 2346        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2347        cx.set_state(indoc! {"
 2348        ˇ// Foo
 2349    "});
 2350        cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2351        cx.assert_editor_state(indoc! {"
 2352
 2353        ˇ// Foo
 2354    "});
 2355    }
 2356    // Ensure that comment continuations can be disabled.
 2357    update_test_language_settings(cx, |settings| {
 2358        settings.defaults.extend_comment_on_newline = Some(false);
 2359    });
 2360    let mut cx = EditorTestContext::new(cx).await;
 2361    cx.set_state(indoc! {"
 2362        // Fooˇ
 2363    "});
 2364    cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2365    cx.assert_editor_state(indoc! {"
 2366        // Foo
 2367        ˇ
 2368    "});
 2369}
 2370
 2371#[gpui::test]
 2372fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2373    init_test(cx, |_| {});
 2374
 2375    let editor = cx.add_window(|cx| {
 2376        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2377        let mut editor = build_editor(buffer.clone(), cx);
 2378        editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
 2379        editor
 2380    });
 2381
 2382    _ = editor.update(cx, |editor, cx| {
 2383        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2384        editor.buffer.update(cx, |buffer, cx| {
 2385            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2386            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2387        });
 2388        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2389
 2390        editor.insert("Z", cx);
 2391        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2392
 2393        // The selections are moved after the inserted characters
 2394        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2395    });
 2396}
 2397
 2398#[gpui::test]
 2399async fn test_tab(cx: &mut gpui::TestAppContext) {
 2400    init_test(cx, |settings| {
 2401        settings.defaults.tab_size = NonZeroU32::new(3)
 2402    });
 2403
 2404    let mut cx = EditorTestContext::new(cx).await;
 2405    cx.set_state(indoc! {"
 2406        ˇabˇc
 2407        ˇ🏀ˇ🏀ˇefg
 2408 2409    "});
 2410    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2411    cx.assert_editor_state(indoc! {"
 2412           ˇab ˇc
 2413           ˇ🏀  ˇ🏀  ˇefg
 2414        d  ˇ
 2415    "});
 2416
 2417    cx.set_state(indoc! {"
 2418        a
 2419        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2420    "});
 2421    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2422    cx.assert_editor_state(indoc! {"
 2423        a
 2424           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2425    "});
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
 2430    init_test(cx, |_| {});
 2431
 2432    let mut cx = EditorTestContext::new(cx).await;
 2433    let language = Arc::new(
 2434        Language::new(
 2435            LanguageConfig::default(),
 2436            Some(tree_sitter_rust::language()),
 2437        )
 2438        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2439        .unwrap(),
 2440    );
 2441    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2442
 2443    // cursors that are already at the suggested indent level insert
 2444    // a soft tab. cursors that are to the left of the suggested indent
 2445    // auto-indent their line.
 2446    cx.set_state(indoc! {"
 2447        ˇ
 2448        const a: B = (
 2449            c(
 2450                d(
 2451        ˇ
 2452                )
 2453        ˇ
 2454        ˇ    )
 2455        );
 2456    "});
 2457    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2458    cx.assert_editor_state(indoc! {"
 2459            ˇ
 2460        const a: B = (
 2461            c(
 2462                d(
 2463                    ˇ
 2464                )
 2465                ˇ
 2466            ˇ)
 2467        );
 2468    "});
 2469
 2470    // handle auto-indent when there are multiple cursors on the same line
 2471    cx.set_state(indoc! {"
 2472        const a: B = (
 2473            c(
 2474        ˇ    ˇ
 2475        ˇ    )
 2476        );
 2477    "});
 2478    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2479    cx.assert_editor_state(indoc! {"
 2480        const a: B = (
 2481            c(
 2482                ˇ
 2483            ˇ)
 2484        );
 2485    "});
 2486}
 2487
 2488#[gpui::test]
 2489async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
 2490    init_test(cx, |settings| {
 2491        settings.defaults.tab_size = NonZeroU32::new(4)
 2492    });
 2493
 2494    let language = Arc::new(
 2495        Language::new(
 2496            LanguageConfig::default(),
 2497            Some(tree_sitter_rust::language()),
 2498        )
 2499        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 2500        .unwrap(),
 2501    );
 2502
 2503    let mut cx = EditorTestContext::new(cx).await;
 2504    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2505    cx.set_state(indoc! {"
 2506        fn a() {
 2507            if b {
 2508        \t ˇc
 2509            }
 2510        }
 2511    "});
 2512
 2513    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2514    cx.assert_editor_state(indoc! {"
 2515        fn a() {
 2516            if b {
 2517                ˇc
 2518            }
 2519        }
 2520    "});
 2521}
 2522
 2523#[gpui::test]
 2524async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 2525    init_test(cx, |settings| {
 2526        settings.defaults.tab_size = NonZeroU32::new(4);
 2527    });
 2528
 2529    let mut cx = EditorTestContext::new(cx).await;
 2530
 2531    cx.set_state(indoc! {"
 2532          «oneˇ» «twoˇ»
 2533        three
 2534         four
 2535    "});
 2536    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2537    cx.assert_editor_state(indoc! {"
 2538            «oneˇ» «twoˇ»
 2539        three
 2540         four
 2541    "});
 2542
 2543    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2544    cx.assert_editor_state(indoc! {"
 2545        «oneˇ» «twoˇ»
 2546        three
 2547         four
 2548    "});
 2549
 2550    // select across line ending
 2551    cx.set_state(indoc! {"
 2552        one two
 2553        t«hree
 2554        ˇ» four
 2555    "});
 2556    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2557    cx.assert_editor_state(indoc! {"
 2558        one two
 2559            t«hree
 2560        ˇ» four
 2561    "});
 2562
 2563    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2564    cx.assert_editor_state(indoc! {"
 2565        one two
 2566        t«hree
 2567        ˇ» four
 2568    "});
 2569
 2570    // Ensure that indenting/outdenting works when the cursor is at column 0.
 2571    cx.set_state(indoc! {"
 2572        one two
 2573        ˇthree
 2574            four
 2575    "});
 2576    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2577    cx.assert_editor_state(indoc! {"
 2578        one two
 2579            ˇthree
 2580            four
 2581    "});
 2582
 2583    cx.set_state(indoc! {"
 2584        one two
 2585        ˇ    three
 2586            four
 2587    "});
 2588    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2589    cx.assert_editor_state(indoc! {"
 2590        one two
 2591        ˇthree
 2592            four
 2593    "});
 2594}
 2595
 2596#[gpui::test]
 2597async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
 2598    init_test(cx, |settings| {
 2599        settings.defaults.hard_tabs = Some(true);
 2600    });
 2601
 2602    let mut cx = EditorTestContext::new(cx).await;
 2603
 2604    // select two ranges on one line
 2605    cx.set_state(indoc! {"
 2606        «oneˇ» «twoˇ»
 2607        three
 2608        four
 2609    "});
 2610    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2611    cx.assert_editor_state(indoc! {"
 2612        \t«oneˇ» «twoˇ»
 2613        three
 2614        four
 2615    "});
 2616    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2617    cx.assert_editor_state(indoc! {"
 2618        \t\t«oneˇ» «twoˇ»
 2619        three
 2620        four
 2621    "});
 2622    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2623    cx.assert_editor_state(indoc! {"
 2624        \t«oneˇ» «twoˇ»
 2625        three
 2626        four
 2627    "});
 2628    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2629    cx.assert_editor_state(indoc! {"
 2630        «oneˇ» «twoˇ»
 2631        three
 2632        four
 2633    "});
 2634
 2635    // select across a line ending
 2636    cx.set_state(indoc! {"
 2637        one two
 2638        t«hree
 2639        ˇ»four
 2640    "});
 2641    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2642    cx.assert_editor_state(indoc! {"
 2643        one two
 2644        \tt«hree
 2645        ˇ»four
 2646    "});
 2647    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2648    cx.assert_editor_state(indoc! {"
 2649        one two
 2650        \t\tt«hree
 2651        ˇ»four
 2652    "});
 2653    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2654    cx.assert_editor_state(indoc! {"
 2655        one two
 2656        \tt«hree
 2657        ˇ»four
 2658    "});
 2659    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2660    cx.assert_editor_state(indoc! {"
 2661        one two
 2662        t«hree
 2663        ˇ»four
 2664    "});
 2665
 2666    // Ensure that indenting/outdenting works when the cursor is at column 0.
 2667    cx.set_state(indoc! {"
 2668        one two
 2669        ˇthree
 2670        four
 2671    "});
 2672    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2673    cx.assert_editor_state(indoc! {"
 2674        one two
 2675        ˇthree
 2676        four
 2677    "});
 2678    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2679    cx.assert_editor_state(indoc! {"
 2680        one two
 2681        \tˇthree
 2682        four
 2683    "});
 2684    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2685    cx.assert_editor_state(indoc! {"
 2686        one two
 2687        ˇthree
 2688        four
 2689    "});
 2690}
 2691
 2692#[gpui::test]
 2693fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 2694    init_test(cx, |settings| {
 2695        settings.languages.extend([
 2696            (
 2697                "TOML".into(),
 2698                LanguageSettingsContent {
 2699                    tab_size: NonZeroU32::new(2),
 2700                    ..Default::default()
 2701                },
 2702            ),
 2703            (
 2704                "Rust".into(),
 2705                LanguageSettingsContent {
 2706                    tab_size: NonZeroU32::new(4),
 2707                    ..Default::default()
 2708                },
 2709            ),
 2710        ]);
 2711    });
 2712
 2713    let toml_language = Arc::new(Language::new(
 2714        LanguageConfig {
 2715            name: "TOML".into(),
 2716            ..Default::default()
 2717        },
 2718        None,
 2719    ));
 2720    let rust_language = Arc::new(Language::new(
 2721        LanguageConfig {
 2722            name: "Rust".into(),
 2723            ..Default::default()
 2724        },
 2725        None,
 2726    ));
 2727
 2728    let toml_buffer =
 2729        cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 2730    let rust_buffer = cx.new_model(|cx| {
 2731        Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
 2732    });
 2733    let multibuffer = cx.new_model(|cx| {
 2734        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 2735        multibuffer.push_excerpts(
 2736            toml_buffer.clone(),
 2737            [ExcerptRange {
 2738                context: Point::new(0, 0)..Point::new(2, 0),
 2739                primary: None,
 2740            }],
 2741            cx,
 2742        );
 2743        multibuffer.push_excerpts(
 2744            rust_buffer.clone(),
 2745            [ExcerptRange {
 2746                context: Point::new(0, 0)..Point::new(1, 0),
 2747                primary: None,
 2748            }],
 2749            cx,
 2750        );
 2751        multibuffer
 2752    });
 2753
 2754    cx.add_window(|cx| {
 2755        let mut editor = build_editor(multibuffer, cx);
 2756
 2757        assert_eq!(
 2758            editor.text(cx),
 2759            indoc! {"
 2760                a = 1
 2761                b = 2
 2762
 2763                const c: usize = 3;
 2764            "}
 2765        );
 2766
 2767        select_ranges(
 2768            &mut editor,
 2769            indoc! {"
 2770                «aˇ» = 1
 2771                b = 2
 2772
 2773                «const c:ˇ» usize = 3;
 2774            "},
 2775            cx,
 2776        );
 2777
 2778        editor.tab(&Tab, cx);
 2779        assert_text_with_selections(
 2780            &mut editor,
 2781            indoc! {"
 2782                  «aˇ» = 1
 2783                b = 2
 2784
 2785                    «const c:ˇ» usize = 3;
 2786            "},
 2787            cx,
 2788        );
 2789        editor.tab_prev(&TabPrev, cx);
 2790        assert_text_with_selections(
 2791            &mut editor,
 2792            indoc! {"
 2793                «aˇ» = 1
 2794                b = 2
 2795
 2796                «const c:ˇ» usize = 3;
 2797            "},
 2798            cx,
 2799        );
 2800
 2801        editor
 2802    });
 2803}
 2804
 2805#[gpui::test]
 2806async fn test_backspace(cx: &mut gpui::TestAppContext) {
 2807    init_test(cx, |_| {});
 2808
 2809    let mut cx = EditorTestContext::new(cx).await;
 2810
 2811    // Basic backspace
 2812    cx.set_state(indoc! {"
 2813        onˇe two three
 2814        fou«rˇ» five six
 2815        seven «ˇeight nine
 2816        »ten
 2817    "});
 2818    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2819    cx.assert_editor_state(indoc! {"
 2820        oˇe two three
 2821        fouˇ five six
 2822        seven ˇten
 2823    "});
 2824
 2825    // Test backspace inside and around indents
 2826    cx.set_state(indoc! {"
 2827        zero
 2828            ˇone
 2829                ˇtwo
 2830            ˇ ˇ ˇ  three
 2831        ˇ  ˇ  four
 2832    "});
 2833    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2834    cx.assert_editor_state(indoc! {"
 2835        zero
 2836        ˇone
 2837            ˇtwo
 2838        ˇ  threeˇ  four
 2839    "});
 2840
 2841    // Test backspace with line_mode set to true
 2842    cx.update_editor(|e, _| e.selections.line_mode = true);
 2843    cx.set_state(indoc! {"
 2844        The ˇquick ˇbrown
 2845        fox jumps over
 2846        the lazy dog
 2847        ˇThe qu«ick bˇ»rown"});
 2848    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2849    cx.assert_editor_state(indoc! {"
 2850        ˇfox jumps over
 2851        the lazy dogˇ"});
 2852}
 2853
 2854#[gpui::test]
 2855async fn test_delete(cx: &mut gpui::TestAppContext) {
 2856    init_test(cx, |_| {});
 2857
 2858    let mut cx = EditorTestContext::new(cx).await;
 2859    cx.set_state(indoc! {"
 2860        onˇe two three
 2861        fou«rˇ» five six
 2862        seven «ˇeight nine
 2863        »ten
 2864    "});
 2865    cx.update_editor(|e, cx| e.delete(&Delete, cx));
 2866    cx.assert_editor_state(indoc! {"
 2867        onˇ two three
 2868        fouˇ five six
 2869        seven ˇten
 2870    "});
 2871
 2872    // Test backspace with line_mode set to true
 2873    cx.update_editor(|e, _| e.selections.line_mode = true);
 2874    cx.set_state(indoc! {"
 2875        The ˇquick ˇbrown
 2876        fox «ˇjum»ps over
 2877        the lazy dog
 2878        ˇThe qu«ick bˇ»rown"});
 2879    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2880    cx.assert_editor_state("ˇthe lazy dogˇ");
 2881}
 2882
 2883#[gpui::test]
 2884fn test_delete_line(cx: &mut TestAppContext) {
 2885    init_test(cx, |_| {});
 2886
 2887    let view = cx.add_window(|cx| {
 2888        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 2889        build_editor(buffer, cx)
 2890    });
 2891    _ = view.update(cx, |view, cx| {
 2892        view.change_selections(None, cx, |s| {
 2893            s.select_display_ranges([
 2894                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2895                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 2896                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 2897            ])
 2898        });
 2899        view.delete_line(&DeleteLine, cx);
 2900        assert_eq!(view.display_text(cx), "ghi");
 2901        assert_eq!(
 2902            view.selections.display_ranges(cx),
 2903            vec![
 2904                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2905                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 2906            ]
 2907        );
 2908    });
 2909
 2910    let view = cx.add_window(|cx| {
 2911        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 2912        build_editor(buffer, cx)
 2913    });
 2914    _ = view.update(cx, |view, cx| {
 2915        view.change_selections(None, cx, |s| {
 2916            s.select_display_ranges([
 2917                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 2918            ])
 2919        });
 2920        view.delete_line(&DeleteLine, cx);
 2921        assert_eq!(view.display_text(cx), "ghi\n");
 2922        assert_eq!(
 2923            view.selections.display_ranges(cx),
 2924            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 2925        );
 2926    });
 2927}
 2928
 2929#[gpui::test]
 2930fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 2931    init_test(cx, |_| {});
 2932
 2933    cx.add_window(|cx| {
 2934        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 2935        let mut editor = build_editor(buffer.clone(), cx);
 2936        let buffer = buffer.read(cx).as_singleton().unwrap();
 2937
 2938        assert_eq!(
 2939            editor.selections.ranges::<Point>(cx),
 2940            &[Point::new(0, 0)..Point::new(0, 0)]
 2941        );
 2942
 2943        // When on single line, replace newline at end by space
 2944        editor.join_lines(&JoinLines, cx);
 2945        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 2946        assert_eq!(
 2947            editor.selections.ranges::<Point>(cx),
 2948            &[Point::new(0, 3)..Point::new(0, 3)]
 2949        );
 2950
 2951        // When multiple lines are selected, remove newlines that are spanned by the selection
 2952        editor.change_selections(None, cx, |s| {
 2953            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 2954        });
 2955        editor.join_lines(&JoinLines, cx);
 2956        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 2957        assert_eq!(
 2958            editor.selections.ranges::<Point>(cx),
 2959            &[Point::new(0, 11)..Point::new(0, 11)]
 2960        );
 2961
 2962        // Undo should be transactional
 2963        editor.undo(&Undo, cx);
 2964        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 2965        assert_eq!(
 2966            editor.selections.ranges::<Point>(cx),
 2967            &[Point::new(0, 5)..Point::new(2, 2)]
 2968        );
 2969
 2970        // When joining an empty line don't insert a space
 2971        editor.change_selections(None, cx, |s| {
 2972            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 2973        });
 2974        editor.join_lines(&JoinLines, cx);
 2975        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 2976        assert_eq!(
 2977            editor.selections.ranges::<Point>(cx),
 2978            [Point::new(2, 3)..Point::new(2, 3)]
 2979        );
 2980
 2981        // We can remove trailing newlines
 2982        editor.join_lines(&JoinLines, cx);
 2983        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 2984        assert_eq!(
 2985            editor.selections.ranges::<Point>(cx),
 2986            [Point::new(2, 3)..Point::new(2, 3)]
 2987        );
 2988
 2989        // We don't blow up on the last line
 2990        editor.join_lines(&JoinLines, cx);
 2991        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 2992        assert_eq!(
 2993            editor.selections.ranges::<Point>(cx),
 2994            [Point::new(2, 3)..Point::new(2, 3)]
 2995        );
 2996
 2997        // reset to test indentation
 2998        editor.buffer.update(cx, |buffer, cx| {
 2999            buffer.edit(
 3000                [
 3001                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3002                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3003                ],
 3004                None,
 3005                cx,
 3006            )
 3007        });
 3008
 3009        // We remove any leading spaces
 3010        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3011        editor.change_selections(None, cx, |s| {
 3012            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3013        });
 3014        editor.join_lines(&JoinLines, cx);
 3015        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3016
 3017        // We don't insert a space for a line containing only spaces
 3018        editor.join_lines(&JoinLines, cx);
 3019        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3020
 3021        // We ignore any leading tabs
 3022        editor.join_lines(&JoinLines, cx);
 3023        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3024
 3025        editor
 3026    });
 3027}
 3028
 3029#[gpui::test]
 3030fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    cx.add_window(|cx| {
 3034        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3035        let mut editor = build_editor(buffer.clone(), cx);
 3036        let buffer = buffer.read(cx).as_singleton().unwrap();
 3037
 3038        editor.change_selections(None, cx, |s| {
 3039            s.select_ranges([
 3040                Point::new(0, 2)..Point::new(1, 1),
 3041                Point::new(1, 2)..Point::new(1, 2),
 3042                Point::new(3, 1)..Point::new(3, 2),
 3043            ])
 3044        });
 3045
 3046        editor.join_lines(&JoinLines, cx);
 3047        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3048
 3049        assert_eq!(
 3050            editor.selections.ranges::<Point>(cx),
 3051            [
 3052                Point::new(0, 7)..Point::new(0, 7),
 3053                Point::new(1, 3)..Point::new(1, 3)
 3054            ]
 3055        );
 3056        editor
 3057    });
 3058}
 3059
 3060#[gpui::test]
 3061async fn test_join_lines_with_git_diff_base(
 3062    executor: BackgroundExecutor,
 3063    cx: &mut gpui::TestAppContext,
 3064) {
 3065    init_test(cx, |_| {});
 3066
 3067    let mut cx = EditorTestContext::new(cx).await;
 3068
 3069    let diff_base = r#"
 3070        Line 0
 3071        Line 1
 3072        Line 2
 3073        Line 3
 3074        "#
 3075    .unindent();
 3076
 3077    cx.set_state(
 3078        &r#"
 3079        ˇLine 0
 3080        Line 1
 3081        Line 2
 3082        Line 3
 3083        "#
 3084        .unindent(),
 3085    );
 3086
 3087    cx.set_diff_base(Some(&diff_base));
 3088    executor.run_until_parked();
 3089
 3090    // Join lines
 3091    cx.update_editor(|editor, cx| {
 3092        editor.join_lines(&JoinLines, cx);
 3093    });
 3094    executor.run_until_parked();
 3095
 3096    cx.assert_editor_state(
 3097        &r#"
 3098        Line 0ˇ Line 1
 3099        Line 2
 3100        Line 3
 3101        "#
 3102        .unindent(),
 3103    );
 3104    // Join again
 3105    cx.update_editor(|editor, cx| {
 3106        editor.join_lines(&JoinLines, cx);
 3107    });
 3108    executor.run_until_parked();
 3109
 3110    cx.assert_editor_state(
 3111        &r#"
 3112        Line 0 Line 1ˇ Line 2
 3113        Line 3
 3114        "#
 3115        .unindent(),
 3116    );
 3117}
 3118
 3119#[gpui::test]
 3120async fn test_custom_newlines_cause_no_false_positive_diffs(
 3121    executor: BackgroundExecutor,
 3122    cx: &mut gpui::TestAppContext,
 3123) {
 3124    init_test(cx, |_| {});
 3125    let mut cx = EditorTestContext::new(cx).await;
 3126    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3127    cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
 3128    executor.run_until_parked();
 3129
 3130    cx.update_editor(|editor, cx| {
 3131        assert_eq!(
 3132            editor
 3133                .buffer()
 3134                .read(cx)
 3135                .snapshot(cx)
 3136                .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
 3137                .collect::<Vec<_>>(),
 3138            Vec::new(),
 3139            "Should not have any diffs for files with custom newlines"
 3140        );
 3141    });
 3142}
 3143
 3144#[gpui::test]
 3145async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3146    init_test(cx, |_| {});
 3147
 3148    let mut cx = EditorTestContext::new(cx).await;
 3149
 3150    // Test sort_lines_case_insensitive()
 3151    cx.set_state(indoc! {"
 3152        «z
 3153        y
 3154        x
 3155        Z
 3156        Y
 3157        Xˇ»
 3158    "});
 3159    cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
 3160    cx.assert_editor_state(indoc! {"
 3161        «x
 3162        X
 3163        y
 3164        Y
 3165        z
 3166        Zˇ»
 3167    "});
 3168
 3169    // Test reverse_lines()
 3170    cx.set_state(indoc! {"
 3171        «5
 3172        4
 3173        3
 3174        2
 3175        1ˇ»
 3176    "});
 3177    cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
 3178    cx.assert_editor_state(indoc! {"
 3179        «1
 3180        2
 3181        3
 3182        4
 3183        5ˇ»
 3184    "});
 3185
 3186    // Skip testing shuffle_line()
 3187
 3188    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3189    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3190
 3191    // Don't manipulate when cursor is on single line, but expand the selection
 3192    cx.set_state(indoc! {"
 3193        ddˇdd
 3194        ccc
 3195        bb
 3196        a
 3197    "});
 3198    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3199    cx.assert_editor_state(indoc! {"
 3200        «ddddˇ»
 3201        ccc
 3202        bb
 3203        a
 3204    "});
 3205
 3206    // Basic manipulate case
 3207    // Start selection moves to column 0
 3208    // End of selection shrinks to fit shorter line
 3209    cx.set_state(indoc! {"
 3210        dd«d
 3211        ccc
 3212        bb
 3213        aaaaaˇ»
 3214    "});
 3215    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217        «aaaaa
 3218        bb
 3219        ccc
 3220        dddˇ»
 3221    "});
 3222
 3223    // Manipulate case with newlines
 3224    cx.set_state(indoc! {"
 3225        dd«d
 3226        ccc
 3227
 3228        bb
 3229        aaaaa
 3230
 3231        ˇ»
 3232    "});
 3233    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3234    cx.assert_editor_state(indoc! {"
 3235        «
 3236
 3237        aaaaa
 3238        bb
 3239        ccc
 3240        dddˇ»
 3241
 3242    "});
 3243
 3244    // Adding new line
 3245    cx.set_state(indoc! {"
 3246        aa«a
 3247        bbˇ»b
 3248    "});
 3249    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
 3250    cx.assert_editor_state(indoc! {"
 3251        «aaa
 3252        bbb
 3253        added_lineˇ»
 3254    "});
 3255
 3256    // Removing line
 3257    cx.set_state(indoc! {"
 3258        aa«a
 3259        bbbˇ»
 3260    "});
 3261    cx.update_editor(|e, cx| {
 3262        e.manipulate_lines(cx, |lines| {
 3263            lines.pop();
 3264        })
 3265    });
 3266    cx.assert_editor_state(indoc! {"
 3267        «aaaˇ»
 3268    "});
 3269
 3270    // Removing all lines
 3271    cx.set_state(indoc! {"
 3272        aa«a
 3273        bbbˇ»
 3274    "});
 3275    cx.update_editor(|e, cx| {
 3276        e.manipulate_lines(cx, |lines| {
 3277            lines.drain(..);
 3278        })
 3279    });
 3280    cx.assert_editor_state(indoc! {"
 3281        ˇ
 3282    "});
 3283}
 3284
 3285#[gpui::test]
 3286async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 3287    init_test(cx, |_| {});
 3288
 3289    let mut cx = EditorTestContext::new(cx).await;
 3290
 3291    // Consider continuous selection as single selection
 3292    cx.set_state(indoc! {"
 3293        Aaa«aa
 3294        cˇ»c«c
 3295        bb
 3296        aaaˇ»aa
 3297    "});
 3298    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 3299    cx.assert_editor_state(indoc! {"
 3300        «Aaaaa
 3301        ccc
 3302        bb
 3303        aaaaaˇ»
 3304    "});
 3305
 3306    cx.set_state(indoc! {"
 3307        Aaa«aa
 3308        cˇ»c«c
 3309        bb
 3310        aaaˇ»aa
 3311    "});
 3312    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
 3313    cx.assert_editor_state(indoc! {"
 3314        «Aaaaa
 3315        ccc
 3316        bbˇ»
 3317    "});
 3318
 3319    // Consider non continuous selection as distinct dedup operations
 3320    cx.set_state(indoc! {"
 3321        «aaaaa
 3322        bb
 3323        aaaaa
 3324        aaaaaˇ»
 3325
 3326        aaa«aaˇ»
 3327    "});
 3328    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        «aaaaa
 3331        bbˇ»
 3332
 3333        «aaaaaˇ»
 3334    "});
 3335}
 3336
 3337#[gpui::test]
 3338async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 3339    init_test(cx, |_| {});
 3340
 3341    let mut cx = EditorTestContext::new(cx).await;
 3342
 3343    cx.set_state(indoc! {"
 3344        «Aaa
 3345        aAa
 3346        Aaaˇ»
 3347    "});
 3348    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 3349    cx.assert_editor_state(indoc! {"
 3350        «Aaa
 3351        aAaˇ»
 3352    "});
 3353
 3354    cx.set_state(indoc! {"
 3355        «Aaa
 3356        aAa
 3357        aaAˇ»
 3358    "});
 3359    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
 3360    cx.assert_editor_state(indoc! {"
 3361        «Aaaˇ»
 3362    "});
 3363}
 3364
 3365#[gpui::test]
 3366async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 3367    init_test(cx, |_| {});
 3368
 3369    let mut cx = EditorTestContext::new(cx).await;
 3370
 3371    // Manipulate with multiple selections on a single line
 3372    cx.set_state(indoc! {"
 3373        dd«dd
 3374        cˇ»c«c
 3375        bb
 3376        aaaˇ»aa
 3377    "});
 3378    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3379    cx.assert_editor_state(indoc! {"
 3380        «aaaaa
 3381        bb
 3382        ccc
 3383        ddddˇ»
 3384    "});
 3385
 3386    // Manipulate with multiple disjoin selections
 3387    cx.set_state(indoc! {"
 3388 3389        4
 3390        3
 3391        2
 3392        1ˇ»
 3393
 3394        dd«dd
 3395        ccc
 3396        bb
 3397        aaaˇ»aa
 3398    "});
 3399    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3400    cx.assert_editor_state(indoc! {"
 3401        «1
 3402        2
 3403        3
 3404        4
 3405        5ˇ»
 3406
 3407        «aaaaa
 3408        bb
 3409        ccc
 3410        ddddˇ»
 3411    "});
 3412
 3413    // Adding lines on each selection
 3414    cx.set_state(indoc! {"
 3415 3416        1ˇ»
 3417
 3418        bb«bb
 3419        aaaˇ»aa
 3420    "});
 3421    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
 3422    cx.assert_editor_state(indoc! {"
 3423        «2
 3424        1
 3425        added lineˇ»
 3426
 3427        «bbbb
 3428        aaaaa
 3429        added lineˇ»
 3430    "});
 3431
 3432    // Removing lines on each selection
 3433    cx.set_state(indoc! {"
 3434 3435        1ˇ»
 3436
 3437        bb«bb
 3438        aaaˇ»aa
 3439    "});
 3440    cx.update_editor(|e, cx| {
 3441        e.manipulate_lines(cx, |lines| {
 3442            lines.pop();
 3443        })
 3444    });
 3445    cx.assert_editor_state(indoc! {"
 3446        «2ˇ»
 3447
 3448        «bbbbˇ»
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_manipulate_text(cx: &mut TestAppContext) {
 3454    init_test(cx, |_| {});
 3455
 3456    let mut cx = EditorTestContext::new(cx).await;
 3457
 3458    // Test convert_to_upper_case()
 3459    cx.set_state(indoc! {"
 3460        «hello worldˇ»
 3461    "});
 3462    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «HELLO WORLDˇ»
 3465    "});
 3466
 3467    // Test convert_to_lower_case()
 3468    cx.set_state(indoc! {"
 3469        «HELLO WORLDˇ»
 3470    "});
 3471    cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
 3472    cx.assert_editor_state(indoc! {"
 3473        «hello worldˇ»
 3474    "});
 3475
 3476    // Test multiple line, single selection case
 3477    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
 3478    cx.set_state(indoc! {"
 3479        «The quick brown
 3480        fox jumps over
 3481        the lazy dogˇ»
 3482    "});
 3483    cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
 3484    cx.assert_editor_state(indoc! {"
 3485        «The Quick Brown
 3486        Fox Jumps Over
 3487        The Lazy Dogˇ»
 3488    "});
 3489
 3490    // Test multiple line, single selection case
 3491    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
 3492    cx.set_state(indoc! {"
 3493        «The quick brown
 3494        fox jumps over
 3495        the lazy dogˇ»
 3496    "});
 3497    cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        «TheQuickBrown
 3500        FoxJumpsOver
 3501        TheLazyDogˇ»
 3502    "});
 3503
 3504    // From here on out, test more complex cases of manipulate_text()
 3505
 3506    // Test no selection case - should affect words cursors are in
 3507    // Cursor at beginning, middle, and end of word
 3508    cx.set_state(indoc! {"
 3509        ˇhello big beauˇtiful worldˇ
 3510    "});
 3511    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3512    cx.assert_editor_state(indoc! {"
 3513        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 3514    "});
 3515
 3516    // Test multiple selections on a single line and across multiple lines
 3517    cx.set_state(indoc! {"
 3518        «Theˇ» quick «brown
 3519        foxˇ» jumps «overˇ»
 3520        the «lazyˇ» dog
 3521    "});
 3522    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        «THEˇ» quick «BROWN
 3525        FOXˇ» jumps «OVERˇ»
 3526        the «LAZYˇ» dog
 3527    "});
 3528
 3529    // Test case where text length grows
 3530    cx.set_state(indoc! {"
 3531        «tschüߡ»
 3532    "});
 3533    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3534    cx.assert_editor_state(indoc! {"
 3535        «TSCHÜSSˇ»
 3536    "});
 3537
 3538    // Test to make sure we don't crash when text shrinks
 3539    cx.set_state(indoc! {"
 3540        aaa_bbbˇ
 3541    "});
 3542    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
 3543    cx.assert_editor_state(indoc! {"
 3544        «aaaBbbˇ»
 3545    "});
 3546
 3547    // Test to make sure we all aware of the fact that each word can grow and shrink
 3548    // Final selections should be aware of this fact
 3549    cx.set_state(indoc! {"
 3550        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 3551    "});
 3552    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
 3553    cx.assert_editor_state(indoc! {"
 3554        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 3555    "});
 3556
 3557    cx.set_state(indoc! {"
 3558        «hElLo, WoRld!ˇ»
 3559    "});
 3560    cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
 3561    cx.assert_editor_state(indoc! {"
 3562        «HeLlO, wOrLD!ˇ»
 3563    "});
 3564}
 3565
 3566#[gpui::test]
 3567fn test_duplicate_line(cx: &mut TestAppContext) {
 3568    init_test(cx, |_| {});
 3569
 3570    let view = cx.add_window(|cx| {
 3571        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3572        build_editor(buffer, cx)
 3573    });
 3574    _ = view.update(cx, |view, cx| {
 3575        view.change_selections(None, cx, |s| {
 3576            s.select_display_ranges([
 3577                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 3578                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3579                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 3580                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3581            ])
 3582        });
 3583        view.duplicate_line_down(&DuplicateLineDown, cx);
 3584        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3585        assert_eq!(
 3586            view.selections.display_ranges(cx),
 3587            vec![
 3588                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3589                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3590                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3591                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 3592            ]
 3593        );
 3594    });
 3595
 3596    let view = cx.add_window(|cx| {
 3597        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3598        build_editor(buffer, cx)
 3599    });
 3600    _ = view.update(cx, |view, cx| {
 3601        view.change_selections(None, cx, |s| {
 3602            s.select_display_ranges([
 3603                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 3604                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 3605            ])
 3606        });
 3607        view.duplicate_line_down(&DuplicateLineDown, cx);
 3608        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3609        assert_eq!(
 3610            view.selections.display_ranges(cx),
 3611            vec![
 3612                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 3613                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 3614            ]
 3615        );
 3616    });
 3617
 3618    // With `move_upwards` the selections stay in place, except for
 3619    // the lines inserted above them
 3620    let view = cx.add_window(|cx| {
 3621        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3622        build_editor(buffer, cx)
 3623    });
 3624    _ = view.update(cx, |view, cx| {
 3625        view.change_selections(None, cx, |s| {
 3626            s.select_display_ranges([
 3627                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 3628                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3629                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 3630                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3631            ])
 3632        });
 3633        view.duplicate_line_up(&DuplicateLineUp, cx);
 3634        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3635        assert_eq!(
 3636            view.selections.display_ranges(cx),
 3637            vec![
 3638                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 3639                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3640                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 3641                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 3642            ]
 3643        );
 3644    });
 3645
 3646    let view = cx.add_window(|cx| {
 3647        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3648        build_editor(buffer, cx)
 3649    });
 3650    _ = view.update(cx, |view, cx| {
 3651        view.change_selections(None, cx, |s| {
 3652            s.select_display_ranges([
 3653                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 3654                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 3655            ])
 3656        });
 3657        view.duplicate_line_up(&DuplicateLineUp, cx);
 3658        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3659        assert_eq!(
 3660            view.selections.display_ranges(cx),
 3661            vec![
 3662                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 3663                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 3664            ]
 3665        );
 3666    });
 3667}
 3668
 3669#[gpui::test]
 3670fn test_move_line_up_down(cx: &mut TestAppContext) {
 3671    init_test(cx, |_| {});
 3672
 3673    let view = cx.add_window(|cx| {
 3674        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3675        build_editor(buffer, cx)
 3676    });
 3677    _ = view.update(cx, |view, cx| {
 3678        view.fold_ranges(
 3679            vec![
 3680                (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 3681                (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 3682                (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 3683            ],
 3684            true,
 3685            cx,
 3686        );
 3687        view.change_selections(None, cx, |s| {
 3688            s.select_display_ranges([
 3689                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3690                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 3691                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 3692                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 3693            ])
 3694        });
 3695        assert_eq!(
 3696            view.display_text(cx),
 3697            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 3698        );
 3699
 3700        view.move_line_up(&MoveLineUp, cx);
 3701        assert_eq!(
 3702            view.display_text(cx),
 3703            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 3704        );
 3705        assert_eq!(
 3706            view.selections.display_ranges(cx),
 3707            vec![
 3708                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3709                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 3710                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 3711                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 3712            ]
 3713        );
 3714    });
 3715
 3716    _ = view.update(cx, |view, cx| {
 3717        view.move_line_down(&MoveLineDown, cx);
 3718        assert_eq!(
 3719            view.display_text(cx),
 3720            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 3721        );
 3722        assert_eq!(
 3723            view.selections.display_ranges(cx),
 3724            vec![
 3725                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 3726                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 3727                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 3728                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 3729            ]
 3730        );
 3731    });
 3732
 3733    _ = view.update(cx, |view, cx| {
 3734        view.move_line_down(&MoveLineDown, cx);
 3735        assert_eq!(
 3736            view.display_text(cx),
 3737            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 3738        );
 3739        assert_eq!(
 3740            view.selections.display_ranges(cx),
 3741            vec![
 3742                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 3743                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 3744                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 3745                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 3746            ]
 3747        );
 3748    });
 3749
 3750    _ = view.update(cx, |view, cx| {
 3751        view.move_line_up(&MoveLineUp, cx);
 3752        assert_eq!(
 3753            view.display_text(cx),
 3754            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 3755        );
 3756        assert_eq!(
 3757            view.selections.display_ranges(cx),
 3758            vec![
 3759                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 3760                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 3761                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 3762                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 3763            ]
 3764        );
 3765    });
 3766}
 3767
 3768#[gpui::test]
 3769fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 3770    init_test(cx, |_| {});
 3771
 3772    let editor = cx.add_window(|cx| {
 3773        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3774        build_editor(buffer, cx)
 3775    });
 3776    _ = editor.update(cx, |editor, cx| {
 3777        let snapshot = editor.buffer.read(cx).snapshot(cx);
 3778        editor.insert_blocks(
 3779            [BlockProperties {
 3780                style: BlockStyle::Fixed,
 3781                position: snapshot.anchor_after(Point::new(2, 0)),
 3782                disposition: BlockDisposition::Below,
 3783                height: 1,
 3784                render: Box::new(|_| div().into_any()),
 3785            }],
 3786            Some(Autoscroll::fit()),
 3787            cx,
 3788        );
 3789        editor.change_selections(None, cx, |s| {
 3790            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 3791        });
 3792        editor.move_line_down(&MoveLineDown, cx);
 3793    });
 3794}
 3795
 3796#[gpui::test]
 3797fn test_transpose(cx: &mut TestAppContext) {
 3798    init_test(cx, |_| {});
 3799
 3800    _ = cx.add_window(|cx| {
 3801        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 3802        editor.set_style(EditorStyle::default(), cx);
 3803        editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
 3804        editor.transpose(&Default::default(), cx);
 3805        assert_eq!(editor.text(cx), "bac");
 3806        assert_eq!(editor.selections.ranges(cx), [2..2]);
 3807
 3808        editor.transpose(&Default::default(), cx);
 3809        assert_eq!(editor.text(cx), "bca");
 3810        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3811
 3812        editor.transpose(&Default::default(), cx);
 3813        assert_eq!(editor.text(cx), "bac");
 3814        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3815
 3816        editor
 3817    });
 3818
 3819    _ = cx.add_window(|cx| {
 3820        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3821        editor.set_style(EditorStyle::default(), cx);
 3822        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
 3823        editor.transpose(&Default::default(), cx);
 3824        assert_eq!(editor.text(cx), "acb\nde");
 3825        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3826
 3827        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3828        editor.transpose(&Default::default(), cx);
 3829        assert_eq!(editor.text(cx), "acbd\ne");
 3830        assert_eq!(editor.selections.ranges(cx), [5..5]);
 3831
 3832        editor.transpose(&Default::default(), cx);
 3833        assert_eq!(editor.text(cx), "acbde\n");
 3834        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3835
 3836        editor.transpose(&Default::default(), cx);
 3837        assert_eq!(editor.text(cx), "acbd\ne");
 3838        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3839
 3840        editor
 3841    });
 3842
 3843    _ = cx.add_window(|cx| {
 3844        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3845        editor.set_style(EditorStyle::default(), cx);
 3846        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 3847        editor.transpose(&Default::default(), cx);
 3848        assert_eq!(editor.text(cx), "bacd\ne");
 3849        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 3850
 3851        editor.transpose(&Default::default(), cx);
 3852        assert_eq!(editor.text(cx), "bcade\n");
 3853        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 3854
 3855        editor.transpose(&Default::default(), cx);
 3856        assert_eq!(editor.text(cx), "bcda\ne");
 3857        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3858
 3859        editor.transpose(&Default::default(), cx);
 3860        assert_eq!(editor.text(cx), "bcade\n");
 3861        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3862
 3863        editor.transpose(&Default::default(), cx);
 3864        assert_eq!(editor.text(cx), "bcaed\n");
 3865        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 3866
 3867        editor
 3868    });
 3869
 3870    _ = cx.add_window(|cx| {
 3871        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
 3872        editor.set_style(EditorStyle::default(), cx);
 3873        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3874        editor.transpose(&Default::default(), cx);
 3875        assert_eq!(editor.text(cx), "🏀🍐✋");
 3876        assert_eq!(editor.selections.ranges(cx), [8..8]);
 3877
 3878        editor.transpose(&Default::default(), cx);
 3879        assert_eq!(editor.text(cx), "🏀✋🍐");
 3880        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3881
 3882        editor.transpose(&Default::default(), cx);
 3883        assert_eq!(editor.text(cx), "🏀🍐✋");
 3884        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3885
 3886        editor
 3887    });
 3888}
 3889
 3890#[gpui::test]
 3891async fn test_clipboard(cx: &mut gpui::TestAppContext) {
 3892    init_test(cx, |_| {});
 3893
 3894    let mut cx = EditorTestContext::new(cx).await;
 3895
 3896    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 3897    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3898    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 3899
 3900    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 3901    cx.set_state("two ˇfour ˇsix ˇ");
 3902    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3903    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 3904
 3905    // Paste again but with only two cursors. Since the number of cursors doesn't
 3906    // match the number of slices in the clipboard, the entire clipboard text
 3907    // is pasted at each cursor.
 3908    cx.set_state("ˇtwo one✅ four three six five ˇ");
 3909    cx.update_editor(|e, cx| {
 3910        e.handle_input("( ", cx);
 3911        e.paste(&Paste, cx);
 3912        e.handle_input(") ", cx);
 3913    });
 3914    cx.assert_editor_state(
 3915        &([
 3916            "( one✅ ",
 3917            "three ",
 3918            "five ) ˇtwo one✅ four three six five ( one✅ ",
 3919            "three ",
 3920            "five ) ˇ",
 3921        ]
 3922        .join("\n")),
 3923    );
 3924
 3925    // Cut with three selections, one of which is full-line.
 3926    cx.set_state(indoc! {"
 3927        1«2ˇ»3
 3928        4ˇ567
 3929        «8ˇ»9"});
 3930    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3931    cx.assert_editor_state(indoc! {"
 3932        1ˇ3
 3933        ˇ9"});
 3934
 3935    // Paste with three selections, noticing how the copied selection that was full-line
 3936    // gets inserted before the second cursor.
 3937    cx.set_state(indoc! {"
 3938        1ˇ3
 3939 3940        «oˇ»ne"});
 3941    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3942    cx.assert_editor_state(indoc! {"
 3943        12ˇ3
 3944        4567
 3945 3946        8ˇne"});
 3947
 3948    // Copy with a single cursor only, which writes the whole line into the clipboard.
 3949    cx.set_state(indoc! {"
 3950        The quick brown
 3951        fox juˇmps over
 3952        the lazy dog"});
 3953    cx.update_editor(|e, cx| e.copy(&Copy, cx));
 3954    assert_eq!(
 3955        cx.read_from_clipboard().map(|item| item.text().to_owned()),
 3956        Some("fox jumps over\n".to_owned())
 3957    );
 3958
 3959    // Paste with three selections, noticing how the copied full-line selection is inserted
 3960    // before the empty selections but replaces the selection that is non-empty.
 3961    cx.set_state(indoc! {"
 3962        Tˇhe quick brown
 3963        «foˇ»x jumps over
 3964        tˇhe lazy dog"});
 3965    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3966    cx.assert_editor_state(indoc! {"
 3967        fox jumps over
 3968        Tˇhe quick brown
 3969        fox jumps over
 3970        ˇx jumps over
 3971        fox jumps over
 3972        tˇhe lazy dog"});
 3973}
 3974
 3975#[gpui::test]
 3976async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 3977    init_test(cx, |_| {});
 3978
 3979    let mut cx = EditorTestContext::new(cx).await;
 3980    let language = Arc::new(Language::new(
 3981        LanguageConfig::default(),
 3982        Some(tree_sitter_rust::language()),
 3983    ));
 3984    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3985
 3986    // Cut an indented block, without the leading whitespace.
 3987    cx.set_state(indoc! {"
 3988        const a: B = (
 3989            c(),
 3990            «d(
 3991                e,
 3992                f
 3993            )ˇ»
 3994        );
 3995    "});
 3996    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3997    cx.assert_editor_state(indoc! {"
 3998        const a: B = (
 3999            c(),
 4000            ˇ
 4001        );
 4002    "});
 4003
 4004    // Paste it at the same position.
 4005    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        const a: B = (
 4008            c(),
 4009            d(
 4010                e,
 4011                f
 4012 4013        );
 4014    "});
 4015
 4016    // Paste it at a line with a lower indent level.
 4017    cx.set_state(indoc! {"
 4018        ˇ
 4019        const a: B = (
 4020            c(),
 4021        );
 4022    "});
 4023    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        d(
 4026            e,
 4027            f
 4028 4029        const a: B = (
 4030            c(),
 4031        );
 4032    "});
 4033
 4034    // Cut an indented block, with the leading whitespace.
 4035    cx.set_state(indoc! {"
 4036        const a: B = (
 4037            c(),
 4038        «    d(
 4039                e,
 4040                f
 4041            )
 4042        ˇ»);
 4043    "});
 4044    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 4045    cx.assert_editor_state(indoc! {"
 4046        const a: B = (
 4047            c(),
 4048        ˇ);
 4049    "});
 4050
 4051    // Paste it at the same position.
 4052    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 4053    cx.assert_editor_state(indoc! {"
 4054        const a: B = (
 4055            c(),
 4056            d(
 4057                e,
 4058                f
 4059            )
 4060        ˇ);
 4061    "});
 4062
 4063    // Paste it at a line with a higher indent level.
 4064    cx.set_state(indoc! {"
 4065        const a: B = (
 4066            c(),
 4067            d(
 4068                e,
 4069 4070            )
 4071        );
 4072    "});
 4073    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 4074    cx.assert_editor_state(indoc! {"
 4075        const a: B = (
 4076            c(),
 4077            d(
 4078                e,
 4079                f    d(
 4080                    e,
 4081                    f
 4082                )
 4083        ˇ
 4084            )
 4085        );
 4086    "});
 4087}
 4088
 4089#[gpui::test]
 4090fn test_select_all(cx: &mut TestAppContext) {
 4091    init_test(cx, |_| {});
 4092
 4093    let view = cx.add_window(|cx| {
 4094        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 4095        build_editor(buffer, cx)
 4096    });
 4097    _ = view.update(cx, |view, cx| {
 4098        view.select_all(&SelectAll, cx);
 4099        assert_eq!(
 4100            view.selections.display_ranges(cx),
 4101            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 4102        );
 4103    });
 4104}
 4105
 4106#[gpui::test]
 4107fn test_select_line(cx: &mut TestAppContext) {
 4108    init_test(cx, |_| {});
 4109
 4110    let view = cx.add_window(|cx| {
 4111        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 4112        build_editor(buffer, cx)
 4113    });
 4114    _ = view.update(cx, |view, cx| {
 4115        view.change_selections(None, cx, |s| {
 4116            s.select_display_ranges([
 4117                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4118                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4119                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4120                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 4121            ])
 4122        });
 4123        view.select_line(&SelectLine, cx);
 4124        assert_eq!(
 4125            view.selections.display_ranges(cx),
 4126            vec![
 4127                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4128                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 4129            ]
 4130        );
 4131    });
 4132
 4133    _ = view.update(cx, |view, cx| {
 4134        view.select_line(&SelectLine, cx);
 4135        assert_eq!(
 4136            view.selections.display_ranges(cx),
 4137            vec![
 4138                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4139                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 4140            ]
 4141        );
 4142    });
 4143
 4144    _ = view.update(cx, |view, cx| {
 4145        view.select_line(&SelectLine, cx);
 4146        assert_eq!(
 4147            view.selections.display_ranges(cx),
 4148            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 4149        );
 4150    });
 4151}
 4152
 4153#[gpui::test]
 4154fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 4155    init_test(cx, |_| {});
 4156
 4157    let view = cx.add_window(|cx| {
 4158        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 4159        build_editor(buffer, cx)
 4160    });
 4161    _ = view.update(cx, |view, cx| {
 4162        view.fold_ranges(
 4163            vec![
 4164                (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4165                (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4166                (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4167            ],
 4168            true,
 4169            cx,
 4170        );
 4171        view.change_selections(None, cx, |s| {
 4172            s.select_display_ranges([
 4173                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4174                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4175                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4176                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 4177            ])
 4178        });
 4179        assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
 4180    });
 4181
 4182    _ = view.update(cx, |view, cx| {
 4183        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 4184        assert_eq!(
 4185            view.display_text(cx),
 4186            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 4187        );
 4188        assert_eq!(
 4189            view.selections.display_ranges(cx),
 4190            [
 4191                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4192                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4193                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4194                DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
 4195            ]
 4196        );
 4197    });
 4198
 4199    _ = view.update(cx, |view, cx| {
 4200        view.change_selections(None, cx, |s| {
 4201            s.select_display_ranges([
 4202                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4203            ])
 4204        });
 4205        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 4206        assert_eq!(
 4207            view.display_text(cx),
 4208            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 4209        );
 4210        assert_eq!(
 4211            view.selections.display_ranges(cx),
 4212            [
 4213                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 4214                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 4215                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 4216                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 4217                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 4218                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 4219                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
 4220                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
 4221            ]
 4222        );
 4223    });
 4224}
 4225
 4226#[gpui::test]
 4227async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 4228    init_test(cx, |_| {});
 4229
 4230    let mut cx = EditorTestContext::new(cx).await;
 4231
 4232    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
 4233    cx.set_state(indoc!(
 4234        r#"abc
 4235           defˇghi
 4236
 4237           jk
 4238           nlmo
 4239           "#
 4240    ));
 4241
 4242    cx.update_editor(|editor, cx| {
 4243        editor.add_selection_above(&Default::default(), cx);
 4244    });
 4245
 4246    cx.assert_editor_state(indoc!(
 4247        r#"abcˇ
 4248           defˇghi
 4249
 4250           jk
 4251           nlmo
 4252           "#
 4253    ));
 4254
 4255    cx.update_editor(|editor, cx| {
 4256        editor.add_selection_above(&Default::default(), cx);
 4257    });
 4258
 4259    cx.assert_editor_state(indoc!(
 4260        r#"abcˇ
 4261            defˇghi
 4262
 4263            jk
 4264            nlmo
 4265            "#
 4266    ));
 4267
 4268    cx.update_editor(|view, cx| {
 4269        view.add_selection_below(&Default::default(), cx);
 4270    });
 4271
 4272    cx.assert_editor_state(indoc!(
 4273        r#"abc
 4274           defˇghi
 4275
 4276           jk
 4277           nlmo
 4278           "#
 4279    ));
 4280
 4281    cx.update_editor(|view, cx| {
 4282        view.undo_selection(&Default::default(), cx);
 4283    });
 4284
 4285    cx.assert_editor_state(indoc!(
 4286        r#"abcˇ
 4287           defˇghi
 4288
 4289           jk
 4290           nlmo
 4291           "#
 4292    ));
 4293
 4294    cx.update_editor(|view, cx| {
 4295        view.redo_selection(&Default::default(), cx);
 4296    });
 4297
 4298    cx.assert_editor_state(indoc!(
 4299        r#"abc
 4300           defˇghi
 4301
 4302           jk
 4303           nlmo
 4304           "#
 4305    ));
 4306
 4307    cx.update_editor(|view, cx| {
 4308        view.add_selection_below(&Default::default(), cx);
 4309    });
 4310
 4311    cx.assert_editor_state(indoc!(
 4312        r#"abc
 4313           defˇghi
 4314
 4315           jk
 4316           nlmˇo
 4317           "#
 4318    ));
 4319
 4320    cx.update_editor(|view, cx| {
 4321        view.add_selection_below(&Default::default(), cx);
 4322    });
 4323
 4324    cx.assert_editor_state(indoc!(
 4325        r#"abc
 4326           defˇghi
 4327
 4328           jk
 4329           nlmˇo
 4330           "#
 4331    ));
 4332
 4333    // change selections
 4334    cx.set_state(indoc!(
 4335        r#"abc
 4336           def«ˇg»hi
 4337
 4338           jk
 4339           nlmo
 4340           "#
 4341    ));
 4342
 4343    cx.update_editor(|view, cx| {
 4344        view.add_selection_below(&Default::default(), cx);
 4345    });
 4346
 4347    cx.assert_editor_state(indoc!(
 4348        r#"abc
 4349           def«ˇg»hi
 4350
 4351           jk
 4352           nlm«ˇo»
 4353           "#
 4354    ));
 4355
 4356    cx.update_editor(|view, cx| {
 4357        view.add_selection_below(&Default::default(), cx);
 4358    });
 4359
 4360    cx.assert_editor_state(indoc!(
 4361        r#"abc
 4362           def«ˇg»hi
 4363
 4364           jk
 4365           nlm«ˇo»
 4366           "#
 4367    ));
 4368
 4369    cx.update_editor(|view, cx| {
 4370        view.add_selection_above(&Default::default(), cx);
 4371    });
 4372
 4373    cx.assert_editor_state(indoc!(
 4374        r#"abc
 4375           def«ˇg»hi
 4376
 4377           jk
 4378           nlmo
 4379           "#
 4380    ));
 4381
 4382    cx.update_editor(|view, cx| {
 4383        view.add_selection_above(&Default::default(), cx);
 4384    });
 4385
 4386    cx.assert_editor_state(indoc!(
 4387        r#"abc
 4388           def«ˇg»hi
 4389
 4390           jk
 4391           nlmo
 4392           "#
 4393    ));
 4394
 4395    // Change selections again
 4396    cx.set_state(indoc!(
 4397        r#"a«bc
 4398           defgˇ»hi
 4399
 4400           jk
 4401           nlmo
 4402           "#
 4403    ));
 4404
 4405    cx.update_editor(|view, cx| {
 4406        view.add_selection_below(&Default::default(), cx);
 4407    });
 4408
 4409    cx.assert_editor_state(indoc!(
 4410        r#"a«bcˇ»
 4411           d«efgˇ»hi
 4412
 4413           j«kˇ»
 4414           nlmo
 4415           "#
 4416    ));
 4417
 4418    cx.update_editor(|view, cx| {
 4419        view.add_selection_below(&Default::default(), cx);
 4420    });
 4421    cx.assert_editor_state(indoc!(
 4422        r#"a«bcˇ»
 4423           d«efgˇ»hi
 4424
 4425           j«kˇ»
 4426           n«lmoˇ»
 4427           "#
 4428    ));
 4429    cx.update_editor(|view, cx| {
 4430        view.add_selection_above(&Default::default(), cx);
 4431    });
 4432
 4433    cx.assert_editor_state(indoc!(
 4434        r#"a«bcˇ»
 4435           d«efgˇ»hi
 4436
 4437           j«kˇ»
 4438           nlmo
 4439           "#
 4440    ));
 4441
 4442    // Change selections again
 4443    cx.set_state(indoc!(
 4444        r#"abc
 4445           d«ˇefghi
 4446
 4447           jk
 4448           nlm»o
 4449           "#
 4450    ));
 4451
 4452    cx.update_editor(|view, cx| {
 4453        view.add_selection_above(&Default::default(), cx);
 4454    });
 4455
 4456    cx.assert_editor_state(indoc!(
 4457        r#"a«ˇbc»
 4458           d«ˇef»ghi
 4459
 4460           j«ˇk»
 4461           n«ˇlm»o
 4462           "#
 4463    ));
 4464
 4465    cx.update_editor(|view, cx| {
 4466        view.add_selection_below(&Default::default(), cx);
 4467    });
 4468
 4469    cx.assert_editor_state(indoc!(
 4470        r#"abc
 4471           d«ˇef»ghi
 4472
 4473           j«ˇk»
 4474           n«ˇlm»o
 4475           "#
 4476    ));
 4477}
 4478
 4479#[gpui::test]
 4480async fn test_select_next(cx: &mut gpui::TestAppContext) {
 4481    init_test(cx, |_| {});
 4482
 4483    let mut cx = EditorTestContext::new(cx).await;
 4484    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4485
 4486    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4487        .unwrap();
 4488    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4489
 4490    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4491        .unwrap();
 4492    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4493
 4494    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4495    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4496
 4497    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4498    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4499
 4500    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4501        .unwrap();
 4502    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4503
 4504    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4505        .unwrap();
 4506    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4507}
 4508
 4509#[gpui::test]
 4510async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
 4511    init_test(cx, |_| {});
 4512
 4513    let mut cx = EditorTestContext::new(cx).await;
 4514    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4515
 4516    cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
 4517        .unwrap();
 4518    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4519}
 4520
 4521#[gpui::test]
 4522async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4523    init_test(cx, |_| {});
 4524
 4525    let mut cx = EditorTestContext::new(cx).await;
 4526    cx.set_state(
 4527        r#"let foo = 2;
 4528lˇet foo = 2;
 4529let fooˇ = 2;
 4530let foo = 2;
 4531let foo = ˇ2;"#,
 4532    );
 4533
 4534    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4535        .unwrap();
 4536    cx.assert_editor_state(
 4537        r#"let foo = 2;
 4538«letˇ» foo = 2;
 4539let «fooˇ» = 2;
 4540let foo = 2;
 4541let foo = «2ˇ»;"#,
 4542    );
 4543
 4544    // noop for multiple selections with different contents
 4545    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4546        .unwrap();
 4547    cx.assert_editor_state(
 4548        r#"let foo = 2;
 4549«letˇ» foo = 2;
 4550let «fooˇ» = 2;
 4551let foo = 2;
 4552let foo = «2ˇ»;"#,
 4553    );
 4554}
 4555
 4556#[gpui::test]
 4557async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
 4558    init_test(cx, |_| {});
 4559
 4560    let mut cx = EditorTestContext::new_multibuffer(
 4561        cx,
 4562        [
 4563            &indoc! {
 4564                "aaa\n«bbb\nccc\n»ddd"
 4565            },
 4566            &indoc! {
 4567                "aaa\n«bbb\nccc\n»ddd"
 4568            },
 4569        ],
 4570    );
 4571
 4572    cx.assert_editor_state(indoc! {"
 4573        ˇbbb
 4574        ccc
 4575
 4576        bbb
 4577        ccc
 4578        "});
 4579    cx.dispatch_action(SelectPrevious::default());
 4580    cx.assert_editor_state(indoc! {"
 4581                «bbbˇ»
 4582                ccc
 4583
 4584                bbb
 4585                ccc
 4586                "});
 4587    cx.dispatch_action(SelectPrevious::default());
 4588    cx.assert_editor_state(indoc! {"
 4589                «bbbˇ»
 4590                ccc
 4591
 4592                «bbbˇ»
 4593                ccc
 4594                "});
 4595}
 4596
 4597#[gpui::test]
 4598async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
 4599    init_test(cx, |_| {});
 4600
 4601    let mut cx = EditorTestContext::new(cx).await;
 4602    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4603
 4604    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4605        .unwrap();
 4606    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4607
 4608    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4609        .unwrap();
 4610    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4611
 4612    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4613    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4614
 4615    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4616    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4617
 4618    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4619        .unwrap();
 4620    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 4621
 4622    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4623        .unwrap();
 4624    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 4625
 4626    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4627        .unwrap();
 4628    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4629}
 4630
 4631#[gpui::test]
 4632async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4633    init_test(cx, |_| {});
 4634
 4635    let mut cx = EditorTestContext::new(cx).await;
 4636    cx.set_state(
 4637        r#"let foo = 2;
 4638lˇet foo = 2;
 4639let fooˇ = 2;
 4640let foo = 2;
 4641let foo = ˇ2;"#,
 4642    );
 4643
 4644    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4645        .unwrap();
 4646    cx.assert_editor_state(
 4647        r#"let foo = 2;
 4648«letˇ» foo = 2;
 4649let «fooˇ» = 2;
 4650let foo = 2;
 4651let foo = «2ˇ»;"#,
 4652    );
 4653
 4654    // noop for multiple selections with different contents
 4655    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4656        .unwrap();
 4657    cx.assert_editor_state(
 4658        r#"let foo = 2;
 4659«letˇ» foo = 2;
 4660let «fooˇ» = 2;
 4661let foo = 2;
 4662let foo = «2ˇ»;"#,
 4663    );
 4664}
 4665
 4666#[gpui::test]
 4667async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
 4668    init_test(cx, |_| {});
 4669
 4670    let mut cx = EditorTestContext::new(cx).await;
 4671    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 4672
 4673    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4674        .unwrap();
 4675    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4676
 4677    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4678        .unwrap();
 4679    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4680
 4681    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4682    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4683
 4684    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4685    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4686
 4687    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4688        .unwrap();
 4689    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 4690
 4691    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4692        .unwrap();
 4693    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4694}
 4695
 4696#[gpui::test]
 4697async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
 4698    init_test(cx, |_| {});
 4699
 4700    let language = Arc::new(Language::new(
 4701        LanguageConfig::default(),
 4702        Some(tree_sitter_rust::language()),
 4703    ));
 4704
 4705    let text = r#"
 4706        use mod1::mod2::{mod3, mod4};
 4707
 4708        fn fn_1(param1: bool, param2: &str) {
 4709            let var1 = "text";
 4710        }
 4711    "#
 4712    .unindent();
 4713
 4714    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4715    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4716    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4717
 4718    view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 4719        .await;
 4720
 4721    _ = view.update(cx, |view, cx| {
 4722        view.change_selections(None, cx, |s| {
 4723            s.select_display_ranges([
 4724                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 4725                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 4726                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 4727            ]);
 4728        });
 4729        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4730    });
 4731    assert_eq!(
 4732        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
 4733        &[
 4734            DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
 4735            DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
 4736            DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
 4737        ]
 4738    );
 4739
 4740    _ = view.update(cx, |view, cx| {
 4741        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4742    });
 4743    assert_eq!(
 4744        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4745        &[
 4746            DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
 4747            DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
 4748        ]
 4749    );
 4750
 4751    _ = view.update(cx, |view, cx| {
 4752        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4753    });
 4754    assert_eq!(
 4755        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4756        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4757    );
 4758
 4759    // Trying to expand the selected syntax node one more time has no effect.
 4760    _ = view.update(cx, |view, cx| {
 4761        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4762    });
 4763    assert_eq!(
 4764        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4765        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4766    );
 4767
 4768    _ = view.update(cx, |view, cx| {
 4769        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4770    });
 4771    assert_eq!(
 4772        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4773        &[
 4774            DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
 4775            DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
 4776        ]
 4777    );
 4778
 4779    _ = view.update(cx, |view, cx| {
 4780        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4781    });
 4782    assert_eq!(
 4783        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4784        &[
 4785            DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
 4786            DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
 4787            DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
 4788        ]
 4789    );
 4790
 4791    _ = view.update(cx, |view, cx| {
 4792        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4793    });
 4794    assert_eq!(
 4795        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4796        &[
 4797            DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 4798            DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 4799            DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 4800        ]
 4801    );
 4802
 4803    // Trying to shrink the selected syntax node one more time has no effect.
 4804    _ = view.update(cx, |view, cx| {
 4805        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4806    });
 4807    assert_eq!(
 4808        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4809        &[
 4810            DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 4811            DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 4812            DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 4813        ]
 4814    );
 4815
 4816    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 4817    // a fold.
 4818    _ = view.update(cx, |view, cx| {
 4819        view.fold_ranges(
 4820            vec![
 4821                (
 4822                    Point::new(0, 21)..Point::new(0, 24),
 4823                    FoldPlaceholder::test(),
 4824                ),
 4825                (
 4826                    Point::new(3, 20)..Point::new(3, 22),
 4827                    FoldPlaceholder::test(),
 4828                ),
 4829            ],
 4830            true,
 4831            cx,
 4832        );
 4833        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4834    });
 4835    assert_eq!(
 4836        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4837        &[
 4838            DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
 4839            DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
 4840            DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(3), 23),
 4841        ]
 4842    );
 4843}
 4844
 4845#[gpui::test]
 4846async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 4847    init_test(cx, |_| {});
 4848
 4849    let language = Arc::new(
 4850        Language::new(
 4851            LanguageConfig {
 4852                brackets: BracketPairConfig {
 4853                    pairs: vec![
 4854                        BracketPair {
 4855                            start: "{".to_string(),
 4856                            end: "}".to_string(),
 4857                            close: false,
 4858                            surround: false,
 4859                            newline: true,
 4860                        },
 4861                        BracketPair {
 4862                            start: "(".to_string(),
 4863                            end: ")".to_string(),
 4864                            close: false,
 4865                            surround: false,
 4866                            newline: true,
 4867                        },
 4868                    ],
 4869                    ..Default::default()
 4870                },
 4871                ..Default::default()
 4872            },
 4873            Some(tree_sitter_rust::language()),
 4874        )
 4875        .with_indents_query(
 4876            r#"
 4877                (_ "(" ")" @end) @indent
 4878                (_ "{" "}" @end) @indent
 4879            "#,
 4880        )
 4881        .unwrap(),
 4882    );
 4883
 4884    let text = "fn a() {}";
 4885
 4886    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4887    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4888    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4889    editor
 4890        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 4891        .await;
 4892
 4893    _ = editor.update(cx, |editor, cx| {
 4894        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 4895        editor.newline(&Newline, cx);
 4896        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 4897        assert_eq!(
 4898            editor.selections.ranges(cx),
 4899            &[
 4900                Point::new(1, 4)..Point::new(1, 4),
 4901                Point::new(3, 4)..Point::new(3, 4),
 4902                Point::new(5, 0)..Point::new(5, 0)
 4903            ]
 4904        );
 4905    });
 4906}
 4907
 4908#[gpui::test]
 4909async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
 4910    init_test(cx, |_| {});
 4911
 4912    let mut cx = EditorTestContext::new(cx).await;
 4913
 4914    let language = Arc::new(Language::new(
 4915        LanguageConfig {
 4916            brackets: BracketPairConfig {
 4917                pairs: vec![
 4918                    BracketPair {
 4919                        start: "{".to_string(),
 4920                        end: "}".to_string(),
 4921                        close: true,
 4922                        surround: true,
 4923                        newline: true,
 4924                    },
 4925                    BracketPair {
 4926                        start: "(".to_string(),
 4927                        end: ")".to_string(),
 4928                        close: true,
 4929                        surround: true,
 4930                        newline: true,
 4931                    },
 4932                    BracketPair {
 4933                        start: "/*".to_string(),
 4934                        end: " */".to_string(),
 4935                        close: true,
 4936                        surround: true,
 4937                        newline: true,
 4938                    },
 4939                    BracketPair {
 4940                        start: "[".to_string(),
 4941                        end: "]".to_string(),
 4942                        close: false,
 4943                        surround: false,
 4944                        newline: true,
 4945                    },
 4946                    BracketPair {
 4947                        start: "\"".to_string(),
 4948                        end: "\"".to_string(),
 4949                        close: true,
 4950                        surround: true,
 4951                        newline: false,
 4952                    },
 4953                    BracketPair {
 4954                        start: "<".to_string(),
 4955                        end: ">".to_string(),
 4956                        close: false,
 4957                        surround: true,
 4958                        newline: true,
 4959                    },
 4960                ],
 4961                ..Default::default()
 4962            },
 4963            autoclose_before: "})]".to_string(),
 4964            ..Default::default()
 4965        },
 4966        Some(tree_sitter_rust::language()),
 4967    ));
 4968
 4969    cx.language_registry().add(language.clone());
 4970    cx.update_buffer(|buffer, cx| {
 4971        buffer.set_language(Some(language), cx);
 4972    });
 4973
 4974    cx.set_state(
 4975        &r#"
 4976            🏀ˇ
 4977            εˇ
 4978            ❤️ˇ
 4979        "#
 4980        .unindent(),
 4981    );
 4982
 4983    // autoclose multiple nested brackets at multiple cursors
 4984    cx.update_editor(|view, cx| {
 4985        view.handle_input("{", cx);
 4986        view.handle_input("{", cx);
 4987        view.handle_input("{", cx);
 4988    });
 4989    cx.assert_editor_state(
 4990        &"
 4991            🏀{{{ˇ}}}
 4992            ε{{{ˇ}}}
 4993            ❤️{{{ˇ}}}
 4994        "
 4995        .unindent(),
 4996    );
 4997
 4998    // insert a different closing bracket
 4999    cx.update_editor(|view, cx| {
 5000        view.handle_input(")", cx);
 5001    });
 5002    cx.assert_editor_state(
 5003        &"
 5004            🏀{{{)ˇ}}}
 5005            ε{{{)ˇ}}}
 5006            ❤️{{{)ˇ}}}
 5007        "
 5008        .unindent(),
 5009    );
 5010
 5011    // skip over the auto-closed brackets when typing a closing bracket
 5012    cx.update_editor(|view, cx| {
 5013        view.move_right(&MoveRight, cx);
 5014        view.handle_input("}", cx);
 5015        view.handle_input("}", cx);
 5016        view.handle_input("}", cx);
 5017    });
 5018    cx.assert_editor_state(
 5019        &"
 5020            🏀{{{)}}}}ˇ
 5021            ε{{{)}}}}ˇ
 5022            ❤️{{{)}}}}ˇ
 5023        "
 5024        .unindent(),
 5025    );
 5026
 5027    // autoclose multi-character pairs
 5028    cx.set_state(
 5029        &"
 5030            ˇ
 5031            ˇ
 5032        "
 5033        .unindent(),
 5034    );
 5035    cx.update_editor(|view, cx| {
 5036        view.handle_input("/", cx);
 5037        view.handle_input("*", cx);
 5038    });
 5039    cx.assert_editor_state(
 5040        &"
 5041            /*ˇ */
 5042            /*ˇ */
 5043        "
 5044        .unindent(),
 5045    );
 5046
 5047    // one cursor autocloses a multi-character pair, one cursor
 5048    // does not autoclose.
 5049    cx.set_state(
 5050        &"
 5051 5052            ˇ
 5053        "
 5054        .unindent(),
 5055    );
 5056    cx.update_editor(|view, cx| view.handle_input("*", cx));
 5057    cx.assert_editor_state(
 5058        &"
 5059            /*ˇ */
 5060 5061        "
 5062        .unindent(),
 5063    );
 5064
 5065    // Don't autoclose if the next character isn't whitespace and isn't
 5066    // listed in the language's "autoclose_before" section.
 5067    cx.set_state("ˇa b");
 5068    cx.update_editor(|view, cx| view.handle_input("{", cx));
 5069    cx.assert_editor_state("{ˇa b");
 5070
 5071    // Don't autoclose if `close` is false for the bracket pair
 5072    cx.set_state("ˇ");
 5073    cx.update_editor(|view, cx| view.handle_input("[", cx));
 5074    cx.assert_editor_state("");
 5075
 5076    // Surround with brackets if text is selected
 5077    cx.set_state("«aˇ» b");
 5078    cx.update_editor(|view, cx| view.handle_input("{", cx));
 5079    cx.assert_editor_state("{«aˇ»} b");
 5080
 5081    // Autclose pair where the start and end characters are the same
 5082    cx.set_state("");
 5083    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 5084    cx.assert_editor_state("a\"ˇ\"");
 5085    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 5086    cx.assert_editor_state("a\"\"ˇ");
 5087
 5088    // Don't autoclose pair if autoclose is disabled
 5089    cx.set_state("ˇ");
 5090    cx.update_editor(|view, cx| view.handle_input("<", cx));
 5091    cx.assert_editor_state("");
 5092
 5093    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 5094    cx.set_state("«aˇ» b");
 5095    cx.update_editor(|view, cx| view.handle_input("<", cx));
 5096    cx.assert_editor_state("<«aˇ»> b");
 5097}
 5098
 5099#[gpui::test]
 5100async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
 5101    init_test(cx, |settings| {
 5102        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 5103    });
 5104
 5105    let mut cx = EditorTestContext::new(cx).await;
 5106
 5107    let language = Arc::new(Language::new(
 5108        LanguageConfig {
 5109            brackets: BracketPairConfig {
 5110                pairs: vec![
 5111                    BracketPair {
 5112                        start: "{".to_string(),
 5113                        end: "}".to_string(),
 5114                        close: true,
 5115                        surround: true,
 5116                        newline: true,
 5117                    },
 5118                    BracketPair {
 5119                        start: "(".to_string(),
 5120                        end: ")".to_string(),
 5121                        close: true,
 5122                        surround: true,
 5123                        newline: true,
 5124                    },
 5125                    BracketPair {
 5126                        start: "[".to_string(),
 5127                        end: "]".to_string(),
 5128                        close: false,
 5129                        surround: false,
 5130                        newline: true,
 5131                    },
 5132                ],
 5133                ..Default::default()
 5134            },
 5135            autoclose_before: "})]".to_string(),
 5136            ..Default::default()
 5137        },
 5138        Some(tree_sitter_rust::language()),
 5139    ));
 5140
 5141    cx.language_registry().add(language.clone());
 5142    cx.update_buffer(|buffer, cx| {
 5143        buffer.set_language(Some(language), cx);
 5144    });
 5145
 5146    cx.set_state(
 5147        &"
 5148            ˇ
 5149            ˇ
 5150            ˇ
 5151        "
 5152        .unindent(),
 5153    );
 5154
 5155    // ensure only matching closing brackets are skipped over
 5156    cx.update_editor(|view, cx| {
 5157        view.handle_input("}", cx);
 5158        view.move_left(&MoveLeft, cx);
 5159        view.handle_input(")", cx);
 5160        view.move_left(&MoveLeft, cx);
 5161    });
 5162    cx.assert_editor_state(
 5163        &"
 5164            ˇ)}
 5165            ˇ)}
 5166            ˇ)}
 5167        "
 5168        .unindent(),
 5169    );
 5170
 5171    // skip-over closing brackets at multiple cursors
 5172    cx.update_editor(|view, cx| {
 5173        view.handle_input(")", cx);
 5174        view.handle_input("}", cx);
 5175    });
 5176    cx.assert_editor_state(
 5177        &"
 5178            )}ˇ
 5179            )}ˇ
 5180            )}ˇ
 5181        "
 5182        .unindent(),
 5183    );
 5184
 5185    // ignore non-close brackets
 5186    cx.update_editor(|view, cx| {
 5187        view.handle_input("]", cx);
 5188        view.move_left(&MoveLeft, cx);
 5189        view.handle_input("]", cx);
 5190    });
 5191    cx.assert_editor_state(
 5192        &"
 5193            )}]ˇ]
 5194            )}]ˇ]
 5195            )}]ˇ]
 5196        "
 5197        .unindent(),
 5198    );
 5199}
 5200
 5201#[gpui::test]
 5202async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
 5203    init_test(cx, |_| {});
 5204
 5205    let mut cx = EditorTestContext::new(cx).await;
 5206
 5207    let html_language = Arc::new(
 5208        Language::new(
 5209            LanguageConfig {
 5210                name: "HTML".into(),
 5211                brackets: BracketPairConfig {
 5212                    pairs: vec![
 5213                        BracketPair {
 5214                            start: "<".into(),
 5215                            end: ">".into(),
 5216                            close: true,
 5217                            ..Default::default()
 5218                        },
 5219                        BracketPair {
 5220                            start: "{".into(),
 5221                            end: "}".into(),
 5222                            close: true,
 5223                            ..Default::default()
 5224                        },
 5225                        BracketPair {
 5226                            start: "(".into(),
 5227                            end: ")".into(),
 5228                            close: true,
 5229                            ..Default::default()
 5230                        },
 5231                    ],
 5232                    ..Default::default()
 5233                },
 5234                autoclose_before: "})]>".into(),
 5235                ..Default::default()
 5236            },
 5237            Some(tree_sitter_html::language()),
 5238        )
 5239        .with_injection_query(
 5240            r#"
 5241            (script_element
 5242                (raw_text) @content
 5243                (#set! "language" "javascript"))
 5244            "#,
 5245        )
 5246        .unwrap(),
 5247    );
 5248
 5249    let javascript_language = Arc::new(Language::new(
 5250        LanguageConfig {
 5251            name: "JavaScript".into(),
 5252            brackets: BracketPairConfig {
 5253                pairs: vec![
 5254                    BracketPair {
 5255                        start: "/*".into(),
 5256                        end: " */".into(),
 5257                        close: true,
 5258                        ..Default::default()
 5259                    },
 5260                    BracketPair {
 5261                        start: "{".into(),
 5262                        end: "}".into(),
 5263                        close: true,
 5264                        ..Default::default()
 5265                    },
 5266                    BracketPair {
 5267                        start: "(".into(),
 5268                        end: ")".into(),
 5269                        close: true,
 5270                        ..Default::default()
 5271                    },
 5272                ],
 5273                ..Default::default()
 5274            },
 5275            autoclose_before: "})]>".into(),
 5276            ..Default::default()
 5277        },
 5278        Some(tree_sitter_typescript::language_tsx()),
 5279    ));
 5280
 5281    cx.language_registry().add(html_language.clone());
 5282    cx.language_registry().add(javascript_language.clone());
 5283
 5284    cx.update_buffer(|buffer, cx| {
 5285        buffer.set_language(Some(html_language), cx);
 5286    });
 5287
 5288    cx.set_state(
 5289        &r#"
 5290            <body>ˇ
 5291                <script>
 5292                    var x = 1;ˇ
 5293                </script>
 5294            </body>ˇ
 5295        "#
 5296        .unindent(),
 5297    );
 5298
 5299    // Precondition: different languages are active at different locations.
 5300    cx.update_editor(|editor, cx| {
 5301        let snapshot = editor.snapshot(cx);
 5302        let cursors = editor.selections.ranges::<usize>(cx);
 5303        let languages = cursors
 5304            .iter()
 5305            .map(|c| snapshot.language_at(c.start).unwrap().name())
 5306            .collect::<Vec<_>>();
 5307        assert_eq!(
 5308            languages,
 5309            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 5310        );
 5311    });
 5312
 5313    // Angle brackets autoclose in HTML, but not JavaScript.
 5314    cx.update_editor(|editor, cx| {
 5315        editor.handle_input("<", cx);
 5316        editor.handle_input("a", cx);
 5317    });
 5318    cx.assert_editor_state(
 5319        &r#"
 5320            <body><aˇ>
 5321                <script>
 5322                    var x = 1;<aˇ
 5323                </script>
 5324            </body><aˇ>
 5325        "#
 5326        .unindent(),
 5327    );
 5328
 5329    // Curly braces and parens autoclose in both HTML and JavaScript.
 5330    cx.update_editor(|editor, cx| {
 5331        editor.handle_input(" b=", cx);
 5332        editor.handle_input("{", cx);
 5333        editor.handle_input("c", cx);
 5334        editor.handle_input("(", cx);
 5335    });
 5336    cx.assert_editor_state(
 5337        &r#"
 5338            <body><a b={c(ˇ)}>
 5339                <script>
 5340                    var x = 1;<a b={c(ˇ)}
 5341                </script>
 5342            </body><a b={c(ˇ)}>
 5343        "#
 5344        .unindent(),
 5345    );
 5346
 5347    // Brackets that were already autoclosed are skipped.
 5348    cx.update_editor(|editor, cx| {
 5349        editor.handle_input(")", cx);
 5350        editor.handle_input("d", cx);
 5351        editor.handle_input("}", cx);
 5352    });
 5353    cx.assert_editor_state(
 5354        &r#"
 5355            <body><a b={c()d}ˇ>
 5356                <script>
 5357                    var x = 1;<a b={c()d}ˇ
 5358                </script>
 5359            </body><a b={c()d}ˇ>
 5360        "#
 5361        .unindent(),
 5362    );
 5363    cx.update_editor(|editor, cx| {
 5364        editor.handle_input(">", cx);
 5365    });
 5366    cx.assert_editor_state(
 5367        &r#"
 5368            <body><a b={c()d}>ˇ
 5369                <script>
 5370                    var x = 1;<a b={c()d}>ˇ
 5371                </script>
 5372            </body><a b={c()d}>ˇ
 5373        "#
 5374        .unindent(),
 5375    );
 5376
 5377    // Reset
 5378    cx.set_state(
 5379        &r#"
 5380            <body>ˇ
 5381                <script>
 5382                    var x = 1;ˇ
 5383                </script>
 5384            </body>ˇ
 5385        "#
 5386        .unindent(),
 5387    );
 5388
 5389    cx.update_editor(|editor, cx| {
 5390        editor.handle_input("<", cx);
 5391    });
 5392    cx.assert_editor_state(
 5393        &r#"
 5394            <body><ˇ>
 5395                <script>
 5396                    var x = 1;<ˇ
 5397                </script>
 5398            </body><ˇ>
 5399        "#
 5400        .unindent(),
 5401    );
 5402
 5403    // When backspacing, the closing angle brackets are removed.
 5404    cx.update_editor(|editor, cx| {
 5405        editor.backspace(&Backspace, cx);
 5406    });
 5407    cx.assert_editor_state(
 5408        &r#"
 5409            <body>ˇ
 5410                <script>
 5411                    var x = 1;ˇ
 5412                </script>
 5413            </body>ˇ
 5414        "#
 5415        .unindent(),
 5416    );
 5417
 5418    // Block comments autoclose in JavaScript, but not HTML.
 5419    cx.update_editor(|editor, cx| {
 5420        editor.handle_input("/", cx);
 5421        editor.handle_input("*", cx);
 5422    });
 5423    cx.assert_editor_state(
 5424        &r#"
 5425            <body>/*ˇ
 5426                <script>
 5427                    var x = 1;/*ˇ */
 5428                </script>
 5429            </body>/*ˇ
 5430        "#
 5431        .unindent(),
 5432    );
 5433}
 5434
 5435#[gpui::test]
 5436async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
 5437    init_test(cx, |_| {});
 5438
 5439    let mut cx = EditorTestContext::new(cx).await;
 5440
 5441    let rust_language = Arc::new(
 5442        Language::new(
 5443            LanguageConfig {
 5444                name: "Rust".into(),
 5445                brackets: serde_json::from_value(json!([
 5446                    { "start": "{", "end": "}", "close": true, "newline": true },
 5447                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 5448                ]))
 5449                .unwrap(),
 5450                autoclose_before: "})]>".into(),
 5451                ..Default::default()
 5452            },
 5453            Some(tree_sitter_rust::language()),
 5454        )
 5455        .with_override_query("(string_literal) @string")
 5456        .unwrap(),
 5457    );
 5458
 5459    cx.language_registry().add(rust_language.clone());
 5460    cx.update_buffer(|buffer, cx| {
 5461        buffer.set_language(Some(rust_language), cx);
 5462    });
 5463
 5464    cx.set_state(
 5465        &r#"
 5466            let x = ˇ
 5467        "#
 5468        .unindent(),
 5469    );
 5470
 5471    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 5472    cx.update_editor(|editor, cx| {
 5473        editor.handle_input("\"", cx);
 5474    });
 5475    cx.assert_editor_state(
 5476        &r#"
 5477            let x = "ˇ"
 5478        "#
 5479        .unindent(),
 5480    );
 5481
 5482    // Inserting another quotation mark. The cursor moves across the existing
 5483    // automatically-inserted quotation mark.
 5484    cx.update_editor(|editor, cx| {
 5485        editor.handle_input("\"", cx);
 5486    });
 5487    cx.assert_editor_state(
 5488        &r#"
 5489            let x = ""ˇ
 5490        "#
 5491        .unindent(),
 5492    );
 5493
 5494    // Reset
 5495    cx.set_state(
 5496        &r#"
 5497            let x = ˇ
 5498        "#
 5499        .unindent(),
 5500    );
 5501
 5502    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 5503    cx.update_editor(|editor, cx| {
 5504        editor.handle_input("\"", cx);
 5505        editor.handle_input(" ", cx);
 5506        editor.move_left(&Default::default(), cx);
 5507        editor.handle_input("\\", cx);
 5508        editor.handle_input("\"", cx);
 5509    });
 5510    cx.assert_editor_state(
 5511        &r#"
 5512            let x = "\"ˇ "
 5513        "#
 5514        .unindent(),
 5515    );
 5516
 5517    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 5518    // mark. Nothing is inserted.
 5519    cx.update_editor(|editor, cx| {
 5520        editor.move_right(&Default::default(), cx);
 5521        editor.handle_input("\"", cx);
 5522    });
 5523    cx.assert_editor_state(
 5524        &r#"
 5525            let x = "\" "ˇ
 5526        "#
 5527        .unindent(),
 5528    );
 5529}
 5530
 5531#[gpui::test]
 5532async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
 5533    init_test(cx, |_| {});
 5534
 5535    let language = Arc::new(Language::new(
 5536        LanguageConfig {
 5537            brackets: BracketPairConfig {
 5538                pairs: vec![
 5539                    BracketPair {
 5540                        start: "{".to_string(),
 5541                        end: "}".to_string(),
 5542                        close: true,
 5543                        surround: true,
 5544                        newline: true,
 5545                    },
 5546                    BracketPair {
 5547                        start: "/* ".to_string(),
 5548                        end: "*/".to_string(),
 5549                        close: true,
 5550                        surround: true,
 5551                        ..Default::default()
 5552                    },
 5553                ],
 5554                ..Default::default()
 5555            },
 5556            ..Default::default()
 5557        },
 5558        Some(tree_sitter_rust::language()),
 5559    ));
 5560
 5561    let text = r#"
 5562        a
 5563        b
 5564        c
 5565    "#
 5566    .unindent();
 5567
 5568    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5569    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5570    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5571    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5572        .await;
 5573
 5574    _ = view.update(cx, |view, cx| {
 5575        view.change_selections(None, cx, |s| {
 5576            s.select_display_ranges([
 5577                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5578                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5579                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 5580            ])
 5581        });
 5582
 5583        view.handle_input("{", cx);
 5584        view.handle_input("{", cx);
 5585        view.handle_input("{", cx);
 5586        assert_eq!(
 5587            view.text(cx),
 5588            "
 5589                {{{a}}}
 5590                {{{b}}}
 5591                {{{c}}}
 5592            "
 5593            .unindent()
 5594        );
 5595        assert_eq!(
 5596            view.selections.display_ranges(cx),
 5597            [
 5598                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 5599                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 5600                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 5601            ]
 5602        );
 5603
 5604        view.undo(&Undo, cx);
 5605        view.undo(&Undo, cx);
 5606        view.undo(&Undo, cx);
 5607        assert_eq!(
 5608            view.text(cx),
 5609            "
 5610                a
 5611                b
 5612                c
 5613            "
 5614            .unindent()
 5615        );
 5616        assert_eq!(
 5617            view.selections.display_ranges(cx),
 5618            [
 5619                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5620                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5621                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 5622            ]
 5623        );
 5624
 5625        // Ensure inserting the first character of a multi-byte bracket pair
 5626        // doesn't surround the selections with the bracket.
 5627        view.handle_input("/", cx);
 5628        assert_eq!(
 5629            view.text(cx),
 5630            "
 5631                /
 5632                /
 5633                /
 5634            "
 5635            .unindent()
 5636        );
 5637        assert_eq!(
 5638            view.selections.display_ranges(cx),
 5639            [
 5640                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5641                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5642                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 5643            ]
 5644        );
 5645
 5646        view.undo(&Undo, cx);
 5647        assert_eq!(
 5648            view.text(cx),
 5649            "
 5650                a
 5651                b
 5652                c
 5653            "
 5654            .unindent()
 5655        );
 5656        assert_eq!(
 5657            view.selections.display_ranges(cx),
 5658            [
 5659                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5660                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5661                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 5662            ]
 5663        );
 5664
 5665        // Ensure inserting the last character of a multi-byte bracket pair
 5666        // doesn't surround the selections with the bracket.
 5667        view.handle_input("*", cx);
 5668        assert_eq!(
 5669            view.text(cx),
 5670            "
 5671                *
 5672                *
 5673                *
 5674            "
 5675            .unindent()
 5676        );
 5677        assert_eq!(
 5678            view.selections.display_ranges(cx),
 5679            [
 5680                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5681                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5682                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 5683            ]
 5684        );
 5685    });
 5686}
 5687
 5688#[gpui::test]
 5689async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
 5690    init_test(cx, |_| {});
 5691
 5692    let language = Arc::new(Language::new(
 5693        LanguageConfig {
 5694            brackets: BracketPairConfig {
 5695                pairs: vec![BracketPair {
 5696                    start: "{".to_string(),
 5697                    end: "}".to_string(),
 5698                    close: true,
 5699                    surround: true,
 5700                    newline: true,
 5701                }],
 5702                ..Default::default()
 5703            },
 5704            autoclose_before: "}".to_string(),
 5705            ..Default::default()
 5706        },
 5707        Some(tree_sitter_rust::language()),
 5708    ));
 5709
 5710    let text = r#"
 5711        a
 5712        b
 5713        c
 5714    "#
 5715    .unindent();
 5716
 5717    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5718    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5719    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5720    editor
 5721        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5722        .await;
 5723
 5724    _ = editor.update(cx, |editor, cx| {
 5725        editor.change_selections(None, cx, |s| {
 5726            s.select_ranges([
 5727                Point::new(0, 1)..Point::new(0, 1),
 5728                Point::new(1, 1)..Point::new(1, 1),
 5729                Point::new(2, 1)..Point::new(2, 1),
 5730            ])
 5731        });
 5732
 5733        editor.handle_input("{", cx);
 5734        editor.handle_input("{", cx);
 5735        editor.handle_input("_", cx);
 5736        assert_eq!(
 5737            editor.text(cx),
 5738            "
 5739                a{{_}}
 5740                b{{_}}
 5741                c{{_}}
 5742            "
 5743            .unindent()
 5744        );
 5745        assert_eq!(
 5746            editor.selections.ranges::<Point>(cx),
 5747            [
 5748                Point::new(0, 4)..Point::new(0, 4),
 5749                Point::new(1, 4)..Point::new(1, 4),
 5750                Point::new(2, 4)..Point::new(2, 4)
 5751            ]
 5752        );
 5753
 5754        editor.backspace(&Default::default(), cx);
 5755        editor.backspace(&Default::default(), cx);
 5756        assert_eq!(
 5757            editor.text(cx),
 5758            "
 5759                a{}
 5760                b{}
 5761                c{}
 5762            "
 5763            .unindent()
 5764        );
 5765        assert_eq!(
 5766            editor.selections.ranges::<Point>(cx),
 5767            [
 5768                Point::new(0, 2)..Point::new(0, 2),
 5769                Point::new(1, 2)..Point::new(1, 2),
 5770                Point::new(2, 2)..Point::new(2, 2)
 5771            ]
 5772        );
 5773
 5774        editor.delete_to_previous_word_start(&Default::default(), cx);
 5775        assert_eq!(
 5776            editor.text(cx),
 5777            "
 5778                a
 5779                b
 5780                c
 5781            "
 5782            .unindent()
 5783        );
 5784        assert_eq!(
 5785            editor.selections.ranges::<Point>(cx),
 5786            [
 5787                Point::new(0, 1)..Point::new(0, 1),
 5788                Point::new(1, 1)..Point::new(1, 1),
 5789                Point::new(2, 1)..Point::new(2, 1)
 5790            ]
 5791        );
 5792    });
 5793}
 5794
 5795#[gpui::test]
 5796async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
 5797    init_test(cx, |settings| {
 5798        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 5799    });
 5800
 5801    let mut cx = EditorTestContext::new(cx).await;
 5802
 5803    let language = Arc::new(Language::new(
 5804        LanguageConfig {
 5805            brackets: BracketPairConfig {
 5806                pairs: vec![
 5807                    BracketPair {
 5808                        start: "{".to_string(),
 5809                        end: "}".to_string(),
 5810                        close: true,
 5811                        surround: true,
 5812                        newline: true,
 5813                    },
 5814                    BracketPair {
 5815                        start: "(".to_string(),
 5816                        end: ")".to_string(),
 5817                        close: true,
 5818                        surround: true,
 5819                        newline: true,
 5820                    },
 5821                    BracketPair {
 5822                        start: "[".to_string(),
 5823                        end: "]".to_string(),
 5824                        close: false,
 5825                        surround: true,
 5826                        newline: true,
 5827                    },
 5828                ],
 5829                ..Default::default()
 5830            },
 5831            autoclose_before: "})]".to_string(),
 5832            ..Default::default()
 5833        },
 5834        Some(tree_sitter_rust::language()),
 5835    ));
 5836
 5837    cx.language_registry().add(language.clone());
 5838    cx.update_buffer(|buffer, cx| {
 5839        buffer.set_language(Some(language), cx);
 5840    });
 5841
 5842    cx.set_state(
 5843        &"
 5844            {(ˇ)}
 5845            [[ˇ]]
 5846            {(ˇ)}
 5847        "
 5848        .unindent(),
 5849    );
 5850
 5851    cx.update_editor(|view, cx| {
 5852        view.backspace(&Default::default(), cx);
 5853        view.backspace(&Default::default(), cx);
 5854    });
 5855
 5856    cx.assert_editor_state(
 5857        &"
 5858            ˇ
 5859            ˇ]]
 5860            ˇ
 5861        "
 5862        .unindent(),
 5863    );
 5864
 5865    cx.update_editor(|view, cx| {
 5866        view.handle_input("{", cx);
 5867        view.handle_input("{", cx);
 5868        view.move_right(&MoveRight, cx);
 5869        view.move_right(&MoveRight, cx);
 5870        view.move_left(&MoveLeft, cx);
 5871        view.move_left(&MoveLeft, cx);
 5872        view.backspace(&Default::default(), cx);
 5873    });
 5874
 5875    cx.assert_editor_state(
 5876        &"
 5877            {ˇ}
 5878            {ˇ}]]
 5879            {ˇ}
 5880        "
 5881        .unindent(),
 5882    );
 5883
 5884    cx.update_editor(|view, cx| {
 5885        view.backspace(&Default::default(), cx);
 5886    });
 5887
 5888    cx.assert_editor_state(
 5889        &"
 5890            ˇ
 5891            ˇ]]
 5892            ˇ
 5893        "
 5894        .unindent(),
 5895    );
 5896}
 5897
 5898#[gpui::test]
 5899async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
 5900    init_test(cx, |_| {});
 5901
 5902    let language = Arc::new(Language::new(
 5903        LanguageConfig::default(),
 5904        Some(tree_sitter_rust::language()),
 5905    ));
 5906
 5907    let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
 5908    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5909    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5910    editor
 5911        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5912        .await;
 5913
 5914    _ = editor.update(cx, |editor, cx| {
 5915        editor.set_auto_replace_emoji_shortcode(true);
 5916
 5917        editor.handle_input("Hello ", cx);
 5918        editor.handle_input(":wave", cx);
 5919        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 5920
 5921        editor.handle_input(":", cx);
 5922        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 5923
 5924        editor.handle_input(" :smile", cx);
 5925        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 5926
 5927        editor.handle_input(":", cx);
 5928        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 5929
 5930        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 5931        editor.handle_input(":wave", cx);
 5932        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 5933
 5934        editor.handle_input(":", cx);
 5935        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 5936
 5937        editor.handle_input(":1", cx);
 5938        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 5939
 5940        editor.handle_input(":", cx);
 5941        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 5942
 5943        // Ensure shortcode does not get replaced when it is part of a word
 5944        editor.handle_input(" Test:wave", cx);
 5945        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 5946
 5947        editor.handle_input(":", cx);
 5948        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 5949
 5950        editor.set_auto_replace_emoji_shortcode(false);
 5951
 5952        // Ensure shortcode does not get replaced when auto replace is off
 5953        editor.handle_input(" :wave", cx);
 5954        assert_eq!(
 5955            editor.text(cx),
 5956            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 5957        );
 5958
 5959        editor.handle_input(":", cx);
 5960        assert_eq!(
 5961            editor.text(cx),
 5962            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 5963        );
 5964    });
 5965}
 5966
 5967#[gpui::test]
 5968async fn test_snippets(cx: &mut gpui::TestAppContext) {
 5969    init_test(cx, |_| {});
 5970
 5971    let (text, insertion_ranges) = marked_text_ranges(
 5972        indoc! {"
 5973            a.ˇ b
 5974            a.ˇ b
 5975            a.ˇ b
 5976        "},
 5977        false,
 5978    );
 5979
 5980    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 5981    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5982
 5983    _ = editor.update(cx, |editor, cx| {
 5984        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 5985
 5986        editor
 5987            .insert_snippet(&insertion_ranges, snippet, cx)
 5988            .unwrap();
 5989
 5990        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
 5991            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 5992            assert_eq!(editor.text(cx), expected_text);
 5993            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 5994        }
 5995
 5996        assert(
 5997            editor,
 5998            cx,
 5999            indoc! {"
 6000                a.f(«one», two, «three») b
 6001                a.f(«one», two, «three») b
 6002                a.f(«one», two, «three») b
 6003            "},
 6004        );
 6005
 6006        // Can't move earlier than the first tab stop
 6007        assert!(!editor.move_to_prev_snippet_tabstop(cx));
 6008        assert(
 6009            editor,
 6010            cx,
 6011            indoc! {"
 6012                a.f(«one», two, «three») b
 6013                a.f(«one», two, «three») b
 6014                a.f(«one», two, «three») b
 6015            "},
 6016        );
 6017
 6018        assert!(editor.move_to_next_snippet_tabstop(cx));
 6019        assert(
 6020            editor,
 6021            cx,
 6022            indoc! {"
 6023                a.f(one, «two», three) b
 6024                a.f(one, «two», three) b
 6025                a.f(one, «two», three) b
 6026            "},
 6027        );
 6028
 6029        editor.move_to_prev_snippet_tabstop(cx);
 6030        assert(
 6031            editor,
 6032            cx,
 6033            indoc! {"
 6034                a.f(«one», two, «three») b
 6035                a.f(«one», two, «three») b
 6036                a.f(«one», two, «three») b
 6037            "},
 6038        );
 6039
 6040        assert!(editor.move_to_next_snippet_tabstop(cx));
 6041        assert(
 6042            editor,
 6043            cx,
 6044            indoc! {"
 6045                a.f(one, «two», three) b
 6046                a.f(one, «two», three) b
 6047                a.f(one, «two», three) b
 6048            "},
 6049        );
 6050        assert!(editor.move_to_next_snippet_tabstop(cx));
 6051        assert(
 6052            editor,
 6053            cx,
 6054            indoc! {"
 6055                a.f(one, two, three)ˇ b
 6056                a.f(one, two, three)ˇ b
 6057                a.f(one, two, three)ˇ b
 6058            "},
 6059        );
 6060
 6061        // As soon as the last tab stop is reached, snippet state is gone
 6062        editor.move_to_prev_snippet_tabstop(cx);
 6063        assert(
 6064            editor,
 6065            cx,
 6066            indoc! {"
 6067                a.f(one, two, three)ˇ b
 6068                a.f(one, two, three)ˇ b
 6069                a.f(one, two, three)ˇ b
 6070            "},
 6071        );
 6072    });
 6073}
 6074
 6075#[gpui::test]
 6076async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
 6077    init_test(cx, |_| {});
 6078
 6079    let fs = FakeFs::new(cx.executor());
 6080    fs.insert_file("/file.rs", Default::default()).await;
 6081
 6082    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6083
 6084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6085    language_registry.add(rust_lang());
 6086    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6087        "Rust",
 6088        FakeLspAdapter {
 6089            capabilities: lsp::ServerCapabilities {
 6090                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6091                ..Default::default()
 6092            },
 6093            ..Default::default()
 6094        },
 6095    );
 6096
 6097    let buffer = project
 6098        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6099        .await
 6100        .unwrap();
 6101
 6102    cx.executor().start_waiting();
 6103    let fake_server = fake_servers.next().await.unwrap();
 6104
 6105    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6106    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6107    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6108    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6109
 6110    let save = editor
 6111        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6112        .unwrap();
 6113    fake_server
 6114        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6115            assert_eq!(
 6116                params.text_document.uri,
 6117                lsp::Url::from_file_path("/file.rs").unwrap()
 6118            );
 6119            assert_eq!(params.options.tab_size, 4);
 6120            Ok(Some(vec![lsp::TextEdit::new(
 6121                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6122                ", ".to_string(),
 6123            )]))
 6124        })
 6125        .next()
 6126        .await;
 6127    cx.executor().start_waiting();
 6128    save.await;
 6129
 6130    assert_eq!(
 6131        editor.update(cx, |editor, cx| editor.text(cx)),
 6132        "one, two\nthree\n"
 6133    );
 6134    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6135
 6136    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6137    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6138
 6139    // Ensure we can still save even if formatting hangs.
 6140    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6141        assert_eq!(
 6142            params.text_document.uri,
 6143            lsp::Url::from_file_path("/file.rs").unwrap()
 6144        );
 6145        futures::future::pending::<()>().await;
 6146        unreachable!()
 6147    });
 6148    let save = editor
 6149        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6150        .unwrap();
 6151    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6152    cx.executor().start_waiting();
 6153    save.await;
 6154    assert_eq!(
 6155        editor.update(cx, |editor, cx| editor.text(cx)),
 6156        "one\ntwo\nthree\n"
 6157    );
 6158    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6159
 6160    // For non-dirty buffer, no formatting request should be sent
 6161    let save = editor
 6162        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6163        .unwrap();
 6164    let _pending_format_request = fake_server
 6165        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 6166            panic!("Should not be invoked on non-dirty buffer");
 6167        })
 6168        .next();
 6169    cx.executor().start_waiting();
 6170    save.await;
 6171
 6172    // Set rust language override and assert overridden tabsize is sent to language server
 6173    update_test_language_settings(cx, |settings| {
 6174        settings.languages.insert(
 6175            "Rust".into(),
 6176            LanguageSettingsContent {
 6177                tab_size: NonZeroU32::new(8),
 6178                ..Default::default()
 6179            },
 6180        );
 6181    });
 6182
 6183    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 6184    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6185    let save = editor
 6186        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6187        .unwrap();
 6188    fake_server
 6189        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6190            assert_eq!(
 6191                params.text_document.uri,
 6192                lsp::Url::from_file_path("/file.rs").unwrap()
 6193            );
 6194            assert_eq!(params.options.tab_size, 8);
 6195            Ok(Some(vec![]))
 6196        })
 6197        .next()
 6198        .await;
 6199    cx.executor().start_waiting();
 6200    save.await;
 6201}
 6202
 6203#[gpui::test]
 6204async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
 6205    init_test(cx, |_| {});
 6206
 6207    let cols = 4;
 6208    let rows = 10;
 6209    let sample_text_1 = sample_text(rows, cols, 'a');
 6210    assert_eq!(
 6211        sample_text_1,
 6212        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 6213    );
 6214    let sample_text_2 = sample_text(rows, cols, 'l');
 6215    assert_eq!(
 6216        sample_text_2,
 6217        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 6218    );
 6219    let sample_text_3 = sample_text(rows, cols, 'v');
 6220    assert_eq!(
 6221        sample_text_3,
 6222        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 6223    );
 6224
 6225    let fs = FakeFs::new(cx.executor());
 6226    fs.insert_tree(
 6227        "/a",
 6228        json!({
 6229            "main.rs": sample_text_1,
 6230            "other.rs": sample_text_2,
 6231            "lib.rs": sample_text_3,
 6232        }),
 6233    )
 6234    .await;
 6235
 6236    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 6237    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 6238    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 6239
 6240    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6241    language_registry.add(rust_lang());
 6242    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6243        "Rust",
 6244        FakeLspAdapter {
 6245            capabilities: lsp::ServerCapabilities {
 6246                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6247                ..Default::default()
 6248            },
 6249            ..Default::default()
 6250        },
 6251    );
 6252
 6253    let worktree = project.update(cx, |project, _| {
 6254        let mut worktrees = project.worktrees().collect::<Vec<_>>();
 6255        assert_eq!(worktrees.len(), 1);
 6256        worktrees.pop().unwrap()
 6257    });
 6258    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 6259
 6260    let buffer_1 = project
 6261        .update(cx, |project, cx| {
 6262            project.open_buffer((worktree_id, "main.rs"), cx)
 6263        })
 6264        .await
 6265        .unwrap();
 6266    let buffer_2 = project
 6267        .update(cx, |project, cx| {
 6268            project.open_buffer((worktree_id, "other.rs"), cx)
 6269        })
 6270        .await
 6271        .unwrap();
 6272    let buffer_3 = project
 6273        .update(cx, |project, cx| {
 6274            project.open_buffer((worktree_id, "lib.rs"), cx)
 6275        })
 6276        .await
 6277        .unwrap();
 6278
 6279    let multi_buffer = cx.new_model(|cx| {
 6280        let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
 6281        multi_buffer.push_excerpts(
 6282            buffer_1.clone(),
 6283            [
 6284                ExcerptRange {
 6285                    context: Point::new(0, 0)..Point::new(3, 0),
 6286                    primary: None,
 6287                },
 6288                ExcerptRange {
 6289                    context: Point::new(5, 0)..Point::new(7, 0),
 6290                    primary: None,
 6291                },
 6292                ExcerptRange {
 6293                    context: Point::new(9, 0)..Point::new(10, 4),
 6294                    primary: None,
 6295                },
 6296            ],
 6297            cx,
 6298        );
 6299        multi_buffer.push_excerpts(
 6300            buffer_2.clone(),
 6301            [
 6302                ExcerptRange {
 6303                    context: Point::new(0, 0)..Point::new(3, 0),
 6304                    primary: None,
 6305                },
 6306                ExcerptRange {
 6307                    context: Point::new(5, 0)..Point::new(7, 0),
 6308                    primary: None,
 6309                },
 6310                ExcerptRange {
 6311                    context: Point::new(9, 0)..Point::new(10, 4),
 6312                    primary: None,
 6313                },
 6314            ],
 6315            cx,
 6316        );
 6317        multi_buffer.push_excerpts(
 6318            buffer_3.clone(),
 6319            [
 6320                ExcerptRange {
 6321                    context: Point::new(0, 0)..Point::new(3, 0),
 6322                    primary: None,
 6323                },
 6324                ExcerptRange {
 6325                    context: Point::new(5, 0)..Point::new(7, 0),
 6326                    primary: None,
 6327                },
 6328                ExcerptRange {
 6329                    context: Point::new(9, 0)..Point::new(10, 4),
 6330                    primary: None,
 6331                },
 6332            ],
 6333            cx,
 6334        );
 6335        multi_buffer
 6336    });
 6337    let multi_buffer_editor = cx.new_view(|cx| {
 6338        Editor::new(
 6339            EditorMode::Full,
 6340            multi_buffer,
 6341            Some(project.clone()),
 6342            true,
 6343            cx,
 6344        )
 6345    });
 6346
 6347    multi_buffer_editor.update(cx, |editor, cx| {
 6348        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 6349        editor.insert("|one|two|three|", cx);
 6350    });
 6351    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 6352    multi_buffer_editor.update(cx, |editor, cx| {
 6353        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 6354            s.select_ranges(Some(60..70))
 6355        });
 6356        editor.insert("|four|five|six|", cx);
 6357    });
 6358    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 6359
 6360    // First two buffers should be edited, but not the third one.
 6361    assert_eq!(
 6362        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 6363        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 6364    );
 6365    buffer_1.update(cx, |buffer, _| {
 6366        assert!(buffer.is_dirty());
 6367        assert_eq!(
 6368            buffer.text(),
 6369            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 6370        )
 6371    });
 6372    buffer_2.update(cx, |buffer, _| {
 6373        assert!(buffer.is_dirty());
 6374        assert_eq!(
 6375            buffer.text(),
 6376            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 6377        )
 6378    });
 6379    buffer_3.update(cx, |buffer, _| {
 6380        assert!(!buffer.is_dirty());
 6381        assert_eq!(buffer.text(), sample_text_3,)
 6382    });
 6383
 6384    cx.executor().start_waiting();
 6385    let save = multi_buffer_editor
 6386        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6387        .unwrap();
 6388
 6389    let fake_server = fake_servers.next().await.unwrap();
 6390    fake_server
 6391        .server
 6392        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6393            Ok(Some(vec![lsp::TextEdit::new(
 6394                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6395                format!("[{} formatted]", params.text_document.uri),
 6396            )]))
 6397        })
 6398        .detach();
 6399    save.await;
 6400
 6401    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 6402    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 6403    assert_eq!(
 6404        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 6405        "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 6406    );
 6407    buffer_1.update(cx, |buffer, _| {
 6408        assert!(!buffer.is_dirty());
 6409        assert_eq!(
 6410            buffer.text(),
 6411            "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
 6412        )
 6413    });
 6414    buffer_2.update(cx, |buffer, _| {
 6415        assert!(!buffer.is_dirty());
 6416        assert_eq!(
 6417            buffer.text(),
 6418            "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
 6419        )
 6420    });
 6421    buffer_3.update(cx, |buffer, _| {
 6422        assert!(!buffer.is_dirty());
 6423        assert_eq!(buffer.text(), sample_text_3,)
 6424    });
 6425}
 6426
 6427#[gpui::test]
 6428async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
 6429    init_test(cx, |_| {});
 6430
 6431    let fs = FakeFs::new(cx.executor());
 6432    fs.insert_file("/file.rs", Default::default()).await;
 6433
 6434    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6435
 6436    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6437    language_registry.add(rust_lang());
 6438    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6439        "Rust",
 6440        FakeLspAdapter {
 6441            capabilities: lsp::ServerCapabilities {
 6442                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 6443                ..Default::default()
 6444            },
 6445            ..Default::default()
 6446        },
 6447    );
 6448
 6449    let buffer = project
 6450        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6451        .await
 6452        .unwrap();
 6453
 6454    cx.executor().start_waiting();
 6455    let fake_server = fake_servers.next().await.unwrap();
 6456
 6457    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6458    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6459    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6460    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6461
 6462    let save = editor
 6463        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6464        .unwrap();
 6465    fake_server
 6466        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6467            assert_eq!(
 6468                params.text_document.uri,
 6469                lsp::Url::from_file_path("/file.rs").unwrap()
 6470            );
 6471            assert_eq!(params.options.tab_size, 4);
 6472            Ok(Some(vec![lsp::TextEdit::new(
 6473                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6474                ", ".to_string(),
 6475            )]))
 6476        })
 6477        .next()
 6478        .await;
 6479    cx.executor().start_waiting();
 6480    save.await;
 6481    assert_eq!(
 6482        editor.update(cx, |editor, cx| editor.text(cx)),
 6483        "one, two\nthree\n"
 6484    );
 6485    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6486
 6487    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6488    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6489
 6490    // Ensure we can still save even if formatting hangs.
 6491    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
 6492        move |params, _| async move {
 6493            assert_eq!(
 6494                params.text_document.uri,
 6495                lsp::Url::from_file_path("/file.rs").unwrap()
 6496            );
 6497            futures::future::pending::<()>().await;
 6498            unreachable!()
 6499        },
 6500    );
 6501    let save = editor
 6502        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6503        .unwrap();
 6504    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6505    cx.executor().start_waiting();
 6506    save.await;
 6507    assert_eq!(
 6508        editor.update(cx, |editor, cx| editor.text(cx)),
 6509        "one\ntwo\nthree\n"
 6510    );
 6511    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6512
 6513    // For non-dirty buffer, no formatting request should be sent
 6514    let save = editor
 6515        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6516        .unwrap();
 6517    let _pending_format_request = fake_server
 6518        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 6519            panic!("Should not be invoked on non-dirty buffer");
 6520        })
 6521        .next();
 6522    cx.executor().start_waiting();
 6523    save.await;
 6524
 6525    // Set Rust language override and assert overridden tabsize is sent to language server
 6526    update_test_language_settings(cx, |settings| {
 6527        settings.languages.insert(
 6528            "Rust".into(),
 6529            LanguageSettingsContent {
 6530                tab_size: NonZeroU32::new(8),
 6531                ..Default::default()
 6532            },
 6533        );
 6534    });
 6535
 6536    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 6537    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6538    let save = editor
 6539        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6540        .unwrap();
 6541    fake_server
 6542        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6543            assert_eq!(
 6544                params.text_document.uri,
 6545                lsp::Url::from_file_path("/file.rs").unwrap()
 6546            );
 6547            assert_eq!(params.options.tab_size, 8);
 6548            Ok(Some(vec![]))
 6549        })
 6550        .next()
 6551        .await;
 6552    cx.executor().start_waiting();
 6553    save.await;
 6554}
 6555
 6556#[gpui::test]
 6557async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
 6558    init_test(cx, |settings| {
 6559        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
 6560    });
 6561
 6562    let fs = FakeFs::new(cx.executor());
 6563    fs.insert_file("/file.rs", Default::default()).await;
 6564
 6565    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6566
 6567    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6568    language_registry.add(Arc::new(Language::new(
 6569        LanguageConfig {
 6570            name: "Rust".into(),
 6571            matcher: LanguageMatcher {
 6572                path_suffixes: vec!["rs".to_string()],
 6573                ..Default::default()
 6574            },
 6575            ..LanguageConfig::default()
 6576        },
 6577        Some(tree_sitter_rust::language()),
 6578    )));
 6579    update_test_language_settings(cx, |settings| {
 6580        // Enable Prettier formatting for the same buffer, and ensure
 6581        // LSP is called instead of Prettier.
 6582        settings.defaults.prettier = Some(PrettierSettings {
 6583            allowed: true,
 6584            ..PrettierSettings::default()
 6585        });
 6586    });
 6587    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6588        "Rust",
 6589        FakeLspAdapter {
 6590            capabilities: lsp::ServerCapabilities {
 6591                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6592                ..Default::default()
 6593            },
 6594            ..Default::default()
 6595        },
 6596    );
 6597
 6598    let buffer = project
 6599        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6600        .await
 6601        .unwrap();
 6602
 6603    cx.executor().start_waiting();
 6604    let fake_server = fake_servers.next().await.unwrap();
 6605
 6606    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6607    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6608    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6609
 6610    let format = editor
 6611        .update(cx, |editor, cx| {
 6612            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 6613        })
 6614        .unwrap();
 6615    fake_server
 6616        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6617            assert_eq!(
 6618                params.text_document.uri,
 6619                lsp::Url::from_file_path("/file.rs").unwrap()
 6620            );
 6621            assert_eq!(params.options.tab_size, 4);
 6622            Ok(Some(vec![lsp::TextEdit::new(
 6623                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6624                ", ".to_string(),
 6625            )]))
 6626        })
 6627        .next()
 6628        .await;
 6629    cx.executor().start_waiting();
 6630    format.await;
 6631    assert_eq!(
 6632        editor.update(cx, |editor, cx| editor.text(cx)),
 6633        "one, two\nthree\n"
 6634    );
 6635
 6636    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6637    // Ensure we don't lock if formatting hangs.
 6638    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6639        assert_eq!(
 6640            params.text_document.uri,
 6641            lsp::Url::from_file_path("/file.rs").unwrap()
 6642        );
 6643        futures::future::pending::<()>().await;
 6644        unreachable!()
 6645    });
 6646    let format = editor
 6647        .update(cx, |editor, cx| {
 6648            editor.perform_format(project, FormatTrigger::Manual, cx)
 6649        })
 6650        .unwrap();
 6651    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6652    cx.executor().start_waiting();
 6653    format.await;
 6654    assert_eq!(
 6655        editor.update(cx, |editor, cx| editor.text(cx)),
 6656        "one\ntwo\nthree\n"
 6657    );
 6658}
 6659
 6660#[gpui::test]
 6661async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
 6662    init_test(cx, |_| {});
 6663
 6664    let mut cx = EditorLspTestContext::new_rust(
 6665        lsp::ServerCapabilities {
 6666            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6667            ..Default::default()
 6668        },
 6669        cx,
 6670    )
 6671    .await;
 6672
 6673    cx.set_state(indoc! {"
 6674        one.twoˇ
 6675    "});
 6676
 6677    // The format request takes a long time. When it completes, it inserts
 6678    // a newline and an indent before the `.`
 6679    cx.lsp
 6680        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
 6681            let executor = cx.background_executor().clone();
 6682            async move {
 6683                executor.timer(Duration::from_millis(100)).await;
 6684                Ok(Some(vec![lsp::TextEdit {
 6685                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 6686                    new_text: "\n    ".into(),
 6687                }]))
 6688            }
 6689        });
 6690
 6691    // Submit a format request.
 6692    let format_1 = cx
 6693        .update_editor(|editor, cx| editor.format(&Format, cx))
 6694        .unwrap();
 6695    cx.executor().run_until_parked();
 6696
 6697    // Submit a second format request.
 6698    let format_2 = cx
 6699        .update_editor(|editor, cx| editor.format(&Format, cx))
 6700        .unwrap();
 6701    cx.executor().run_until_parked();
 6702
 6703    // Wait for both format requests to complete
 6704    cx.executor().advance_clock(Duration::from_millis(200));
 6705    cx.executor().start_waiting();
 6706    format_1.await.unwrap();
 6707    cx.executor().start_waiting();
 6708    format_2.await.unwrap();
 6709
 6710    // The formatting edits only happens once.
 6711    cx.assert_editor_state(indoc! {"
 6712        one
 6713            .twoˇ
 6714    "});
 6715}
 6716
 6717#[gpui::test]
 6718async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
 6719    init_test(cx, |settings| {
 6720        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 6721    });
 6722
 6723    let mut cx = EditorLspTestContext::new_rust(
 6724        lsp::ServerCapabilities {
 6725            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6726            ..Default::default()
 6727        },
 6728        cx,
 6729    )
 6730    .await;
 6731
 6732    // Set up a buffer white some trailing whitespace and no trailing newline.
 6733    cx.set_state(
 6734        &[
 6735            "one ",   //
 6736            "twoˇ",   //
 6737            "three ", //
 6738            "four",   //
 6739        ]
 6740        .join("\n"),
 6741    );
 6742
 6743    // Submit a format request.
 6744    let format = cx
 6745        .update_editor(|editor, cx| editor.format(&Format, cx))
 6746        .unwrap();
 6747
 6748    // Record which buffer changes have been sent to the language server
 6749    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 6750    cx.lsp
 6751        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 6752            let buffer_changes = buffer_changes.clone();
 6753            move |params, _| {
 6754                buffer_changes.lock().extend(
 6755                    params
 6756                        .content_changes
 6757                        .into_iter()
 6758                        .map(|e| (e.range.unwrap(), e.text)),
 6759                );
 6760            }
 6761        });
 6762
 6763    // Handle formatting requests to the language server.
 6764    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
 6765        let buffer_changes = buffer_changes.clone();
 6766        move |_, _| {
 6767            // When formatting is requested, trailing whitespace has already been stripped,
 6768            // and the trailing newline has already been added.
 6769            assert_eq!(
 6770                &buffer_changes.lock()[1..],
 6771                &[
 6772                    (
 6773                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 6774                        "".into()
 6775                    ),
 6776                    (
 6777                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 6778                        "".into()
 6779                    ),
 6780                    (
 6781                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 6782                        "\n".into()
 6783                    ),
 6784                ]
 6785            );
 6786
 6787            // Insert blank lines between each line of the buffer.
 6788            async move {
 6789                Ok(Some(vec![
 6790                    lsp::TextEdit {
 6791                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
 6792                        new_text: "\n".into(),
 6793                    },
 6794                    lsp::TextEdit {
 6795                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
 6796                        new_text: "\n".into(),
 6797                    },
 6798                ]))
 6799            }
 6800        }
 6801    });
 6802
 6803    // After formatting the buffer, the trailing whitespace is stripped,
 6804    // a newline is appended, and the edits provided by the language server
 6805    // have been applied.
 6806    format.await.unwrap();
 6807    cx.assert_editor_state(
 6808        &[
 6809            "one",   //
 6810            "",      //
 6811            "twoˇ",  //
 6812            "",      //
 6813            "three", //
 6814            "four",  //
 6815            "",      //
 6816        ]
 6817        .join("\n"),
 6818    );
 6819
 6820    // Undoing the formatting undoes the trailing whitespace removal, the
 6821    // trailing newline, and the LSP edits.
 6822    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 6823    cx.assert_editor_state(
 6824        &[
 6825            "one ",   //
 6826            "twoˇ",   //
 6827            "three ", //
 6828            "four",   //
 6829        ]
 6830        .join("\n"),
 6831    );
 6832}
 6833
 6834#[gpui::test]
 6835async fn test_completion(cx: &mut gpui::TestAppContext) {
 6836    init_test(cx, |_| {});
 6837
 6838    let mut cx = EditorLspTestContext::new_rust(
 6839        lsp::ServerCapabilities {
 6840            completion_provider: Some(lsp::CompletionOptions {
 6841                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 6842                resolve_provider: Some(true),
 6843                ..Default::default()
 6844            }),
 6845            ..Default::default()
 6846        },
 6847        cx,
 6848    )
 6849    .await;
 6850    let counter = Arc::new(AtomicUsize::new(0));
 6851
 6852    cx.set_state(indoc! {"
 6853        oneˇ
 6854        two
 6855        three
 6856    "});
 6857    cx.simulate_keystroke(".");
 6858    handle_completion_request(
 6859        &mut cx,
 6860        indoc! {"
 6861            one.|<>
 6862            two
 6863            three
 6864        "},
 6865        vec!["first_completion", "second_completion"],
 6866        counter.clone(),
 6867    )
 6868    .await;
 6869    cx.condition(|editor, _| editor.context_menu_visible())
 6870        .await;
 6871    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 6872
 6873    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6874        editor.context_menu_next(&Default::default(), cx);
 6875        editor
 6876            .confirm_completion(&ConfirmCompletion::default(), cx)
 6877            .unwrap()
 6878    });
 6879    cx.assert_editor_state(indoc! {"
 6880        one.second_completionˇ
 6881        two
 6882        three
 6883    "});
 6884
 6885    handle_resolve_completion_request(
 6886        &mut cx,
 6887        Some(vec![
 6888            (
 6889                //This overlaps with the primary completion edit which is
 6890                //misbehavior from the LSP spec, test that we filter it out
 6891                indoc! {"
 6892                    one.second_ˇcompletion
 6893                    two
 6894                    threeˇ
 6895                "},
 6896                "overlapping additional edit",
 6897            ),
 6898            (
 6899                indoc! {"
 6900                    one.second_completion
 6901                    two
 6902                    threeˇ
 6903                "},
 6904                "\nadditional edit",
 6905            ),
 6906        ]),
 6907    )
 6908    .await;
 6909    apply_additional_edits.await.unwrap();
 6910    cx.assert_editor_state(indoc! {"
 6911        one.second_completionˇ
 6912        two
 6913        three
 6914        additional edit
 6915    "});
 6916
 6917    cx.set_state(indoc! {"
 6918        one.second_completion
 6919        twoˇ
 6920        threeˇ
 6921        additional edit
 6922    "});
 6923    cx.simulate_keystroke(" ");
 6924    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6925    cx.simulate_keystroke("s");
 6926    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6927
 6928    cx.assert_editor_state(indoc! {"
 6929        one.second_completion
 6930        two sˇ
 6931        three sˇ
 6932        additional edit
 6933    "});
 6934    handle_completion_request(
 6935        &mut cx,
 6936        indoc! {"
 6937            one.second_completion
 6938            two s
 6939            three <s|>
 6940            additional edit
 6941        "},
 6942        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6943        counter.clone(),
 6944    )
 6945    .await;
 6946    cx.condition(|editor, _| editor.context_menu_visible())
 6947        .await;
 6948    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
 6949
 6950    cx.simulate_keystroke("i");
 6951
 6952    handle_completion_request(
 6953        &mut cx,
 6954        indoc! {"
 6955            one.second_completion
 6956            two si
 6957            three <si|>
 6958            additional edit
 6959        "},
 6960        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6961        counter.clone(),
 6962    )
 6963    .await;
 6964    cx.condition(|editor, _| editor.context_menu_visible())
 6965        .await;
 6966    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
 6967
 6968    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6969        editor
 6970            .confirm_completion(&ConfirmCompletion::default(), cx)
 6971            .unwrap()
 6972    });
 6973    cx.assert_editor_state(indoc! {"
 6974        one.second_completion
 6975        two sixth_completionˇ
 6976        three sixth_completionˇ
 6977        additional edit
 6978    "});
 6979
 6980    handle_resolve_completion_request(&mut cx, None).await;
 6981    apply_additional_edits.await.unwrap();
 6982
 6983    _ = cx.update(|cx| {
 6984        cx.update_global::<SettingsStore, _>(|settings, cx| {
 6985            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 6986                settings.show_completions_on_input = Some(false);
 6987            });
 6988        })
 6989    });
 6990    cx.set_state("editorˇ");
 6991    cx.simulate_keystroke(".");
 6992    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6993    cx.simulate_keystroke("c");
 6994    cx.simulate_keystroke("l");
 6995    cx.simulate_keystroke("o");
 6996    cx.assert_editor_state("editor.cloˇ");
 6997    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6998    cx.update_editor(|editor, cx| {
 6999        editor.show_completions(&ShowCompletions { trigger: None }, cx);
 7000    });
 7001    handle_completion_request(
 7002        &mut cx,
 7003        "editor.<clo|>",
 7004        vec!["close", "clobber"],
 7005        counter.clone(),
 7006    )
 7007    .await;
 7008    cx.condition(|editor, _| editor.context_menu_visible())
 7009        .await;
 7010    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
 7011
 7012    let apply_additional_edits = cx.update_editor(|editor, cx| {
 7013        editor
 7014            .confirm_completion(&ConfirmCompletion::default(), cx)
 7015            .unwrap()
 7016    });
 7017    cx.assert_editor_state("editor.closeˇ");
 7018    handle_resolve_completion_request(&mut cx, None).await;
 7019    apply_additional_edits.await.unwrap();
 7020}
 7021
 7022#[gpui::test]
 7023async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
 7024    init_test(cx, |_| {});
 7025
 7026    let mut cx = EditorLspTestContext::new_rust(
 7027        lsp::ServerCapabilities {
 7028            completion_provider: Some(lsp::CompletionOptions {
 7029                trigger_characters: Some(vec![".".to_string()]),
 7030                resolve_provider: Some(true),
 7031                ..Default::default()
 7032            }),
 7033            ..Default::default()
 7034        },
 7035        cx,
 7036    )
 7037    .await;
 7038
 7039    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 7040    cx.simulate_keystroke(".");
 7041    let completion_item = lsp::CompletionItem {
 7042        label: "Some".into(),
 7043        kind: Some(lsp::CompletionItemKind::SNIPPET),
 7044        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 7045        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 7046            kind: lsp::MarkupKind::Markdown,
 7047            value: "```rust\nSome(2)\n```".to_string(),
 7048        })),
 7049        deprecated: Some(false),
 7050        sort_text: Some("Some".to_string()),
 7051        filter_text: Some("Some".to_string()),
 7052        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 7053        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 7054            range: lsp::Range {
 7055                start: lsp::Position {
 7056                    line: 0,
 7057                    character: 22,
 7058                },
 7059                end: lsp::Position {
 7060                    line: 0,
 7061                    character: 22,
 7062                },
 7063            },
 7064            new_text: "Some(2)".to_string(),
 7065        })),
 7066        additional_text_edits: Some(vec![lsp::TextEdit {
 7067            range: lsp::Range {
 7068                start: lsp::Position {
 7069                    line: 0,
 7070                    character: 20,
 7071                },
 7072                end: lsp::Position {
 7073                    line: 0,
 7074                    character: 22,
 7075                },
 7076            },
 7077            new_text: "".to_string(),
 7078        }]),
 7079        ..Default::default()
 7080    };
 7081
 7082    let closure_completion_item = completion_item.clone();
 7083    let counter = Arc::new(AtomicUsize::new(0));
 7084    let counter_clone = counter.clone();
 7085    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 7086        let task_completion_item = closure_completion_item.clone();
 7087        counter_clone.fetch_add(1, atomic::Ordering::Release);
 7088        async move {
 7089            Ok(Some(lsp::CompletionResponse::Array(vec![
 7090                task_completion_item,
 7091            ])))
 7092        }
 7093    });
 7094
 7095    cx.condition(|editor, _| editor.context_menu_visible())
 7096        .await;
 7097    cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
 7098    assert!(request.next().await.is_some());
 7099    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 7100
 7101    cx.simulate_keystroke("S");
 7102    cx.simulate_keystroke("o");
 7103    cx.simulate_keystroke("m");
 7104    cx.condition(|editor, _| editor.context_menu_visible())
 7105        .await;
 7106    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
 7107    assert!(request.next().await.is_some());
 7108    assert!(request.next().await.is_some());
 7109    assert!(request.next().await.is_some());
 7110    request.close();
 7111    assert!(request.next().await.is_none());
 7112    assert_eq!(
 7113        counter.load(atomic::Ordering::Acquire),
 7114        4,
 7115        "With the completions menu open, only one LSP request should happen per input"
 7116    );
 7117}
 7118
 7119#[gpui::test]
 7120async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
 7121    init_test(cx, |_| {});
 7122    let mut cx = EditorTestContext::new(cx).await;
 7123    let language = Arc::new(Language::new(
 7124        LanguageConfig {
 7125            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
 7126            ..Default::default()
 7127        },
 7128        Some(tree_sitter_rust::language()),
 7129    ));
 7130    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7131
 7132    // If multiple selections intersect a line, the line is only toggled once.
 7133    cx.set_state(indoc! {"
 7134        fn a() {
 7135            «//b();
 7136            ˇ»// «c();
 7137            //ˇ»  d();
 7138        }
 7139    "});
 7140
 7141    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7142
 7143    cx.assert_editor_state(indoc! {"
 7144        fn a() {
 7145            «b();
 7146            c();
 7147            ˇ» d();
 7148        }
 7149    "});
 7150
 7151    // The comment prefix is inserted at the same column for every line in a
 7152    // selection.
 7153    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7154
 7155    cx.assert_editor_state(indoc! {"
 7156        fn a() {
 7157            // «b();
 7158            // c();
 7159            ˇ»//  d();
 7160        }
 7161    "});
 7162
 7163    // If a selection ends at the beginning of a line, that line is not toggled.
 7164    cx.set_selections_state(indoc! {"
 7165        fn a() {
 7166            // b();
 7167            «// c();
 7168        ˇ»    //  d();
 7169        }
 7170    "});
 7171
 7172    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7173
 7174    cx.assert_editor_state(indoc! {"
 7175        fn a() {
 7176            // b();
 7177            «c();
 7178        ˇ»    //  d();
 7179        }
 7180    "});
 7181
 7182    // If a selection span a single line and is empty, the line is toggled.
 7183    cx.set_state(indoc! {"
 7184        fn a() {
 7185            a();
 7186            b();
 7187        ˇ
 7188        }
 7189    "});
 7190
 7191    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7192
 7193    cx.assert_editor_state(indoc! {"
 7194        fn a() {
 7195            a();
 7196            b();
 7197        //•ˇ
 7198        }
 7199    "});
 7200
 7201    // If a selection span multiple lines, empty lines are not toggled.
 7202    cx.set_state(indoc! {"
 7203        fn a() {
 7204            «a();
 7205
 7206            c();ˇ»
 7207        }
 7208    "});
 7209
 7210    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7211
 7212    cx.assert_editor_state(indoc! {"
 7213        fn a() {
 7214            // «a();
 7215
 7216            // c();ˇ»
 7217        }
 7218    "});
 7219
 7220    // If a selection includes multiple comment prefixes, all lines are uncommented.
 7221    cx.set_state(indoc! {"
 7222        fn a() {
 7223            «// a();
 7224            /// b();
 7225            //! c();ˇ»
 7226        }
 7227    "});
 7228
 7229    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7230
 7231    cx.assert_editor_state(indoc! {"
 7232        fn a() {
 7233            «a();
 7234            b();
 7235            c();ˇ»
 7236        }
 7237    "});
 7238}
 7239
 7240#[gpui::test]
 7241async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
 7242    init_test(cx, |_| {});
 7243
 7244    let language = Arc::new(Language::new(
 7245        LanguageConfig {
 7246            line_comments: vec!["// ".into()],
 7247            ..Default::default()
 7248        },
 7249        Some(tree_sitter_rust::language()),
 7250    ));
 7251
 7252    let mut cx = EditorTestContext::new(cx).await;
 7253
 7254    cx.language_registry().add(language.clone());
 7255    cx.update_buffer(|buffer, cx| {
 7256        buffer.set_language(Some(language), cx);
 7257    });
 7258
 7259    let toggle_comments = &ToggleComments {
 7260        advance_downwards: true,
 7261    };
 7262
 7263    // Single cursor on one line -> advance
 7264    // Cursor moves horizontally 3 characters as well on non-blank line
 7265    cx.set_state(indoc!(
 7266        "fn a() {
 7267             ˇdog();
 7268             cat();
 7269        }"
 7270    ));
 7271    cx.update_editor(|editor, cx| {
 7272        editor.toggle_comments(toggle_comments, cx);
 7273    });
 7274    cx.assert_editor_state(indoc!(
 7275        "fn a() {
 7276             // dog();
 7277             catˇ();
 7278        }"
 7279    ));
 7280
 7281    // Single selection on one line -> don't advance
 7282    cx.set_state(indoc!(
 7283        "fn a() {
 7284             «dog()ˇ»;
 7285             cat();
 7286        }"
 7287    ));
 7288    cx.update_editor(|editor, cx| {
 7289        editor.toggle_comments(toggle_comments, cx);
 7290    });
 7291    cx.assert_editor_state(indoc!(
 7292        "fn a() {
 7293             // «dog()ˇ»;
 7294             cat();
 7295        }"
 7296    ));
 7297
 7298    // Multiple cursors on one line -> advance
 7299    cx.set_state(indoc!(
 7300        "fn a() {
 7301             ˇdˇog();
 7302             cat();
 7303        }"
 7304    ));
 7305    cx.update_editor(|editor, cx| {
 7306        editor.toggle_comments(toggle_comments, cx);
 7307    });
 7308    cx.assert_editor_state(indoc!(
 7309        "fn a() {
 7310             // dog();
 7311             catˇ(ˇ);
 7312        }"
 7313    ));
 7314
 7315    // Multiple cursors on one line, with selection -> don't advance
 7316    cx.set_state(indoc!(
 7317        "fn a() {
 7318             ˇdˇog«()ˇ»;
 7319             cat();
 7320        }"
 7321    ));
 7322    cx.update_editor(|editor, cx| {
 7323        editor.toggle_comments(toggle_comments, cx);
 7324    });
 7325    cx.assert_editor_state(indoc!(
 7326        "fn a() {
 7327             // ˇdˇog«()ˇ»;
 7328             cat();
 7329        }"
 7330    ));
 7331
 7332    // Single cursor on one line -> advance
 7333    // Cursor moves to column 0 on blank line
 7334    cx.set_state(indoc!(
 7335        "fn a() {
 7336             ˇdog();
 7337
 7338             cat();
 7339        }"
 7340    ));
 7341    cx.update_editor(|editor, cx| {
 7342        editor.toggle_comments(toggle_comments, cx);
 7343    });
 7344    cx.assert_editor_state(indoc!(
 7345        "fn a() {
 7346             // dog();
 7347        ˇ
 7348             cat();
 7349        }"
 7350    ));
 7351
 7352    // Single cursor on one line -> advance
 7353    // Cursor starts and ends at column 0
 7354    cx.set_state(indoc!(
 7355        "fn a() {
 7356         ˇ    dog();
 7357             cat();
 7358        }"
 7359    ));
 7360    cx.update_editor(|editor, cx| {
 7361        editor.toggle_comments(toggle_comments, cx);
 7362    });
 7363    cx.assert_editor_state(indoc!(
 7364        "fn a() {
 7365             // dog();
 7366         ˇ    cat();
 7367        }"
 7368    ));
 7369}
 7370
 7371#[gpui::test]
 7372async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 7373    init_test(cx, |_| {});
 7374
 7375    let mut cx = EditorTestContext::new(cx).await;
 7376
 7377    let html_language = Arc::new(
 7378        Language::new(
 7379            LanguageConfig {
 7380                name: "HTML".into(),
 7381                block_comment: Some(("<!-- ".into(), " -->".into())),
 7382                ..Default::default()
 7383            },
 7384            Some(tree_sitter_html::language()),
 7385        )
 7386        .with_injection_query(
 7387            r#"
 7388            (script_element
 7389                (raw_text) @content
 7390                (#set! "language" "javascript"))
 7391            "#,
 7392        )
 7393        .unwrap(),
 7394    );
 7395
 7396    let javascript_language = Arc::new(Language::new(
 7397        LanguageConfig {
 7398            name: "JavaScript".into(),
 7399            line_comments: vec!["// ".into()],
 7400            ..Default::default()
 7401        },
 7402        Some(tree_sitter_typescript::language_tsx()),
 7403    ));
 7404
 7405    cx.language_registry().add(html_language.clone());
 7406    cx.language_registry().add(javascript_language.clone());
 7407    cx.update_buffer(|buffer, cx| {
 7408        buffer.set_language(Some(html_language), cx);
 7409    });
 7410
 7411    // Toggle comments for empty selections
 7412    cx.set_state(
 7413        &r#"
 7414            <p>A</p>ˇ
 7415            <p>B</p>ˇ
 7416            <p>C</p>ˇ
 7417        "#
 7418        .unindent(),
 7419    );
 7420    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7421    cx.assert_editor_state(
 7422        &r#"
 7423            <!-- <p>A</p>ˇ -->
 7424            <!-- <p>B</p>ˇ -->
 7425            <!-- <p>C</p>ˇ -->
 7426        "#
 7427        .unindent(),
 7428    );
 7429    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7430    cx.assert_editor_state(
 7431        &r#"
 7432            <p>A</p>ˇ
 7433            <p>B</p>ˇ
 7434            <p>C</p>ˇ
 7435        "#
 7436        .unindent(),
 7437    );
 7438
 7439    // Toggle comments for mixture of empty and non-empty selections, where
 7440    // multiple selections occupy a given line.
 7441    cx.set_state(
 7442        &r#"
 7443            <p>A«</p>
 7444            <p>ˇ»B</p>ˇ
 7445            <p>C«</p>
 7446            <p>ˇ»D</p>ˇ
 7447        "#
 7448        .unindent(),
 7449    );
 7450
 7451    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7452    cx.assert_editor_state(
 7453        &r#"
 7454            <!-- <p>A«</p>
 7455            <p>ˇ»B</p>ˇ -->
 7456            <!-- <p>C«</p>
 7457            <p>ˇ»D</p>ˇ -->
 7458        "#
 7459        .unindent(),
 7460    );
 7461    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7462    cx.assert_editor_state(
 7463        &r#"
 7464            <p>A«</p>
 7465            <p>ˇ»B</p>ˇ
 7466            <p>C«</p>
 7467            <p>ˇ»D</p>ˇ
 7468        "#
 7469        .unindent(),
 7470    );
 7471
 7472    // Toggle comments when different languages are active for different
 7473    // selections.
 7474    cx.set_state(
 7475        &r#"
 7476            ˇ<script>
 7477                ˇvar x = new Y();
 7478            ˇ</script>
 7479        "#
 7480        .unindent(),
 7481    );
 7482    cx.executor().run_until_parked();
 7483    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7484    cx.assert_editor_state(
 7485        &r#"
 7486            <!-- ˇ<script> -->
 7487                // ˇvar x = new Y();
 7488            <!-- ˇ</script> -->
 7489        "#
 7490        .unindent(),
 7491    );
 7492}
 7493
 7494#[gpui::test]
 7495fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
 7496    init_test(cx, |_| {});
 7497
 7498    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7499    let multibuffer = cx.new_model(|cx| {
 7500        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7501        multibuffer.push_excerpts(
 7502            buffer.clone(),
 7503            [
 7504                ExcerptRange {
 7505                    context: Point::new(0, 0)..Point::new(0, 4),
 7506                    primary: None,
 7507                },
 7508                ExcerptRange {
 7509                    context: Point::new(1, 0)..Point::new(1, 4),
 7510                    primary: None,
 7511                },
 7512            ],
 7513            cx,
 7514        );
 7515        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
 7516        multibuffer
 7517    });
 7518
 7519    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7520    _ = view.update(cx, |view, cx| {
 7521        assert_eq!(view.text(cx), "aaaa\nbbbb");
 7522        view.change_selections(None, cx, |s| {
 7523            s.select_ranges([
 7524                Point::new(0, 0)..Point::new(0, 0),
 7525                Point::new(1, 0)..Point::new(1, 0),
 7526            ])
 7527        });
 7528
 7529        view.handle_input("X", cx);
 7530        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
 7531        assert_eq!(
 7532            view.selections.ranges(cx),
 7533            [
 7534                Point::new(0, 1)..Point::new(0, 1),
 7535                Point::new(1, 1)..Point::new(1, 1),
 7536            ]
 7537        );
 7538
 7539        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
 7540        view.change_selections(None, cx, |s| {
 7541            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
 7542        });
 7543        view.backspace(&Default::default(), cx);
 7544        assert_eq!(view.text(cx), "Xa\nbbb");
 7545        assert_eq!(
 7546            view.selections.ranges(cx),
 7547            [Point::new(1, 0)..Point::new(1, 0)]
 7548        );
 7549
 7550        view.change_selections(None, cx, |s| {
 7551            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
 7552        });
 7553        view.backspace(&Default::default(), cx);
 7554        assert_eq!(view.text(cx), "X\nbb");
 7555        assert_eq!(
 7556            view.selections.ranges(cx),
 7557            [Point::new(0, 1)..Point::new(0, 1)]
 7558        );
 7559    });
 7560}
 7561
 7562#[gpui::test]
 7563fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 7564    init_test(cx, |_| {});
 7565
 7566    let markers = vec![('[', ']').into(), ('(', ')').into()];
 7567    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
 7568        indoc! {"
 7569            [aaaa
 7570            (bbbb]
 7571            cccc)",
 7572        },
 7573        markers.clone(),
 7574    );
 7575    let excerpt_ranges = markers.into_iter().map(|marker| {
 7576        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
 7577        ExcerptRange {
 7578            context,
 7579            primary: None,
 7580        }
 7581    });
 7582    let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
 7583    let multibuffer = cx.new_model(|cx| {
 7584        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7585        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
 7586        multibuffer
 7587    });
 7588
 7589    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7590    _ = view.update(cx, |view, cx| {
 7591        let (expected_text, selection_ranges) = marked_text_ranges(
 7592            indoc! {"
 7593                aaaa
 7594                bˇbbb
 7595                bˇbbˇb
 7596                cccc"
 7597            },
 7598            true,
 7599        );
 7600        assert_eq!(view.text(cx), expected_text);
 7601        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 7602
 7603        view.handle_input("X", cx);
 7604
 7605        let (expected_text, expected_selections) = marked_text_ranges(
 7606            indoc! {"
 7607                aaaa
 7608                bXˇbbXb
 7609                bXˇbbXˇb
 7610                cccc"
 7611            },
 7612            false,
 7613        );
 7614        assert_eq!(view.text(cx), expected_text);
 7615        assert_eq!(view.selections.ranges(cx), expected_selections);
 7616
 7617        view.newline(&Newline, cx);
 7618        let (expected_text, expected_selections) = marked_text_ranges(
 7619            indoc! {"
 7620                aaaa
 7621                bX
 7622                ˇbbX
 7623                b
 7624                bX
 7625                ˇbbX
 7626                ˇb
 7627                cccc"
 7628            },
 7629            false,
 7630        );
 7631        assert_eq!(view.text(cx), expected_text);
 7632        assert_eq!(view.selections.ranges(cx), expected_selections);
 7633    });
 7634}
 7635
 7636#[gpui::test]
 7637fn test_refresh_selections(cx: &mut TestAppContext) {
 7638    init_test(cx, |_| {});
 7639
 7640    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7641    let mut excerpt1_id = None;
 7642    let multibuffer = cx.new_model(|cx| {
 7643        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7644        excerpt1_id = multibuffer
 7645            .push_excerpts(
 7646                buffer.clone(),
 7647                [
 7648                    ExcerptRange {
 7649                        context: Point::new(0, 0)..Point::new(1, 4),
 7650                        primary: None,
 7651                    },
 7652                    ExcerptRange {
 7653                        context: Point::new(1, 0)..Point::new(2, 4),
 7654                        primary: None,
 7655                    },
 7656                ],
 7657                cx,
 7658            )
 7659            .into_iter()
 7660            .next();
 7661        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7662        multibuffer
 7663    });
 7664
 7665    let editor = cx.add_window(|cx| {
 7666        let mut editor = build_editor(multibuffer.clone(), cx);
 7667        let snapshot = editor.snapshot(cx);
 7668        editor.change_selections(None, cx, |s| {
 7669            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
 7670        });
 7671        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
 7672        assert_eq!(
 7673            editor.selections.ranges(cx),
 7674            [
 7675                Point::new(1, 3)..Point::new(1, 3),
 7676                Point::new(2, 1)..Point::new(2, 1),
 7677            ]
 7678        );
 7679        editor
 7680    });
 7681
 7682    // Refreshing selections is a no-op when excerpts haven't changed.
 7683    _ = editor.update(cx, |editor, cx| {
 7684        editor.change_selections(None, cx, |s| s.refresh());
 7685        assert_eq!(
 7686            editor.selections.ranges(cx),
 7687            [
 7688                Point::new(1, 3)..Point::new(1, 3),
 7689                Point::new(2, 1)..Point::new(2, 1),
 7690            ]
 7691        );
 7692    });
 7693
 7694    _ = multibuffer.update(cx, |multibuffer, cx| {
 7695        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7696    });
 7697    _ = editor.update(cx, |editor, cx| {
 7698        // Removing an excerpt causes the first selection to become degenerate.
 7699        assert_eq!(
 7700            editor.selections.ranges(cx),
 7701            [
 7702                Point::new(0, 0)..Point::new(0, 0),
 7703                Point::new(0, 1)..Point::new(0, 1)
 7704            ]
 7705        );
 7706
 7707        // Refreshing selections will relocate the first selection to the original buffer
 7708        // location.
 7709        editor.change_selections(None, cx, |s| s.refresh());
 7710        assert_eq!(
 7711            editor.selections.ranges(cx),
 7712            [
 7713                Point::new(0, 1)..Point::new(0, 1),
 7714                Point::new(0, 3)..Point::new(0, 3)
 7715            ]
 7716        );
 7717        assert!(editor.selections.pending_anchor().is_some());
 7718    });
 7719}
 7720
 7721#[gpui::test]
 7722fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
 7723    init_test(cx, |_| {});
 7724
 7725    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7726    let mut excerpt1_id = None;
 7727    let multibuffer = cx.new_model(|cx| {
 7728        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7729        excerpt1_id = multibuffer
 7730            .push_excerpts(
 7731                buffer.clone(),
 7732                [
 7733                    ExcerptRange {
 7734                        context: Point::new(0, 0)..Point::new(1, 4),
 7735                        primary: None,
 7736                    },
 7737                    ExcerptRange {
 7738                        context: Point::new(1, 0)..Point::new(2, 4),
 7739                        primary: None,
 7740                    },
 7741                ],
 7742                cx,
 7743            )
 7744            .into_iter()
 7745            .next();
 7746        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7747        multibuffer
 7748    });
 7749
 7750    let editor = cx.add_window(|cx| {
 7751        let mut editor = build_editor(multibuffer.clone(), cx);
 7752        let snapshot = editor.snapshot(cx);
 7753        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
 7754        assert_eq!(
 7755            editor.selections.ranges(cx),
 7756            [Point::new(1, 3)..Point::new(1, 3)]
 7757        );
 7758        editor
 7759    });
 7760
 7761    _ = multibuffer.update(cx, |multibuffer, cx| {
 7762        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7763    });
 7764    _ = editor.update(cx, |editor, cx| {
 7765        assert_eq!(
 7766            editor.selections.ranges(cx),
 7767            [Point::new(0, 0)..Point::new(0, 0)]
 7768        );
 7769
 7770        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
 7771        editor.change_selections(None, cx, |s| s.refresh());
 7772        assert_eq!(
 7773            editor.selections.ranges(cx),
 7774            [Point::new(0, 3)..Point::new(0, 3)]
 7775        );
 7776        assert!(editor.selections.pending_anchor().is_some());
 7777    });
 7778}
 7779
 7780#[gpui::test]
 7781async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 7782    init_test(cx, |_| {});
 7783
 7784    let language = Arc::new(
 7785        Language::new(
 7786            LanguageConfig {
 7787                brackets: BracketPairConfig {
 7788                    pairs: vec![
 7789                        BracketPair {
 7790                            start: "{".to_string(),
 7791                            end: "}".to_string(),
 7792                            close: true,
 7793                            surround: true,
 7794                            newline: true,
 7795                        },
 7796                        BracketPair {
 7797                            start: "/* ".to_string(),
 7798                            end: " */".to_string(),
 7799                            close: true,
 7800                            surround: true,
 7801                            newline: true,
 7802                        },
 7803                    ],
 7804                    ..Default::default()
 7805                },
 7806                ..Default::default()
 7807            },
 7808            Some(tree_sitter_rust::language()),
 7809        )
 7810        .with_indents_query("")
 7811        .unwrap(),
 7812    );
 7813
 7814    let text = concat!(
 7815        "{   }\n",     //
 7816        "  x\n",       //
 7817        "  /*   */\n", //
 7818        "x\n",         //
 7819        "{{} }\n",     //
 7820    );
 7821
 7822    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 7823    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 7824    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 7825    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 7826        .await;
 7827
 7828    _ = view.update(cx, |view, cx| {
 7829        view.change_selections(None, cx, |s| {
 7830            s.select_display_ranges([
 7831                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 7832                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7833                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7834            ])
 7835        });
 7836        view.newline(&Newline, cx);
 7837
 7838        assert_eq!(
 7839            view.buffer().read(cx).read(cx).text(),
 7840            concat!(
 7841                "{ \n",    // Suppress rustfmt
 7842                "\n",      //
 7843                "}\n",     //
 7844                "  x\n",   //
 7845                "  /* \n", //
 7846                "  \n",    //
 7847                "  */\n",  //
 7848                "x\n",     //
 7849                "{{} \n",  //
 7850                "}\n",     //
 7851            )
 7852        );
 7853    });
 7854}
 7855
 7856#[gpui::test]
 7857fn test_highlighted_ranges(cx: &mut TestAppContext) {
 7858    init_test(cx, |_| {});
 7859
 7860    let editor = cx.add_window(|cx| {
 7861        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
 7862        build_editor(buffer.clone(), cx)
 7863    });
 7864
 7865    _ = editor.update(cx, |editor, cx| {
 7866        struct Type1;
 7867        struct Type2;
 7868
 7869        let buffer = editor.buffer.read(cx).snapshot(cx);
 7870
 7871        let anchor_range =
 7872            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
 7873
 7874        editor.highlight_background::<Type1>(
 7875            &[
 7876                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
 7877                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
 7878                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
 7879                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
 7880            ],
 7881            |_| Hsla::red(),
 7882            cx,
 7883        );
 7884        editor.highlight_background::<Type2>(
 7885            &[
 7886                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
 7887                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
 7888                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
 7889                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
 7890            ],
 7891            |_| Hsla::green(),
 7892            cx,
 7893        );
 7894
 7895        let snapshot = editor.snapshot(cx);
 7896        let mut highlighted_ranges = editor.background_highlights_in_range(
 7897            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
 7898            &snapshot,
 7899            cx.theme().colors(),
 7900        );
 7901        // Enforce a consistent ordering based on color without relying on the ordering of the
 7902        // highlight's `TypeId` which is non-executor.
 7903        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
 7904        assert_eq!(
 7905            highlighted_ranges,
 7906            &[
 7907                (
 7908                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
 7909                    Hsla::red(),
 7910                ),
 7911                (
 7912                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
 7913                    Hsla::red(),
 7914                ),
 7915                (
 7916                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
 7917                    Hsla::green(),
 7918                ),
 7919                (
 7920                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
 7921                    Hsla::green(),
 7922                ),
 7923            ]
 7924        );
 7925        assert_eq!(
 7926            editor.background_highlights_in_range(
 7927                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
 7928                &snapshot,
 7929                cx.theme().colors(),
 7930            ),
 7931            &[(
 7932                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
 7933                Hsla::red(),
 7934            )]
 7935        );
 7936    });
 7937}
 7938
 7939#[gpui::test]
 7940async fn test_following(cx: &mut gpui::TestAppContext) {
 7941    init_test(cx, |_| {});
 7942
 7943    let fs = FakeFs::new(cx.executor());
 7944    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 7945
 7946    let buffer = project.update(cx, |project, cx| {
 7947        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
 7948        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
 7949    });
 7950    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 7951    let follower = cx.update(|cx| {
 7952        cx.open_window(
 7953            WindowOptions {
 7954                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
 7955                    gpui::Point::new(px(0.), px(0.)),
 7956                    gpui::Point::new(px(10.), px(80.)),
 7957                ))),
 7958                ..Default::default()
 7959            },
 7960            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
 7961        )
 7962        .unwrap()
 7963    });
 7964
 7965    let is_still_following = Rc::new(RefCell::new(true));
 7966    let follower_edit_event_count = Rc::new(RefCell::new(0));
 7967    let pending_update = Rc::new(RefCell::new(None));
 7968    _ = follower.update(cx, {
 7969        let update = pending_update.clone();
 7970        let is_still_following = is_still_following.clone();
 7971        let follower_edit_event_count = follower_edit_event_count.clone();
 7972        |_, cx| {
 7973            cx.subscribe(
 7974                &leader.root_view(cx).unwrap(),
 7975                move |_, leader, event, cx| {
 7976                    leader
 7977                        .read(cx)
 7978                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 7979                },
 7980            )
 7981            .detach();
 7982
 7983            cx.subscribe(
 7984                &follower.root_view(cx).unwrap(),
 7985                move |_, _, event: &EditorEvent, _cx| {
 7986                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
 7987                        *is_still_following.borrow_mut() = false;
 7988                    }
 7989
 7990                    if let EditorEvent::BufferEdited = event {
 7991                        *follower_edit_event_count.borrow_mut() += 1;
 7992                    }
 7993                },
 7994            )
 7995            .detach();
 7996        }
 7997    });
 7998
 7999    // Update the selections only
 8000    _ = leader.update(cx, |leader, cx| {
 8001        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 8002    });
 8003    follower
 8004        .update(cx, |follower, cx| {
 8005            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8006        })
 8007        .unwrap()
 8008        .await
 8009        .unwrap();
 8010    _ = follower.update(cx, |follower, cx| {
 8011        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
 8012    });
 8013    assert_eq!(*is_still_following.borrow(), true);
 8014    assert_eq!(*follower_edit_event_count.borrow(), 0);
 8015
 8016    // Update the scroll position only
 8017    _ = leader.update(cx, |leader, cx| {
 8018        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 8019    });
 8020    follower
 8021        .update(cx, |follower, cx| {
 8022            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8023        })
 8024        .unwrap()
 8025        .await
 8026        .unwrap();
 8027    assert_eq!(
 8028        follower
 8029            .update(cx, |follower, cx| follower.scroll_position(cx))
 8030            .unwrap(),
 8031        gpui::Point::new(1.5, 3.5)
 8032    );
 8033    assert_eq!(*is_still_following.borrow(), true);
 8034    assert_eq!(*follower_edit_event_count.borrow(), 0);
 8035
 8036    // Update the selections and scroll position. The follower's scroll position is updated
 8037    // via autoscroll, not via the leader's exact scroll position.
 8038    _ = leader.update(cx, |leader, cx| {
 8039        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
 8040        leader.request_autoscroll(Autoscroll::newest(), cx);
 8041        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 8042    });
 8043    follower
 8044        .update(cx, |follower, cx| {
 8045            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8046        })
 8047        .unwrap()
 8048        .await
 8049        .unwrap();
 8050    _ = follower.update(cx, |follower, cx| {
 8051        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
 8052        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
 8053    });
 8054    assert_eq!(*is_still_following.borrow(), true);
 8055
 8056    // Creating a pending selection that precedes another selection
 8057    _ = leader.update(cx, |leader, cx| {
 8058        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 8059        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
 8060    });
 8061    follower
 8062        .update(cx, |follower, cx| {
 8063            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8064        })
 8065        .unwrap()
 8066        .await
 8067        .unwrap();
 8068    _ = follower.update(cx, |follower, cx| {
 8069        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
 8070    });
 8071    assert_eq!(*is_still_following.borrow(), true);
 8072
 8073    // Extend the pending selection so that it surrounds another selection
 8074    _ = leader.update(cx, |leader, cx| {
 8075        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
 8076    });
 8077    follower
 8078        .update(cx, |follower, cx| {
 8079            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8080        })
 8081        .unwrap()
 8082        .await
 8083        .unwrap();
 8084    _ = follower.update(cx, |follower, cx| {
 8085        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
 8086    });
 8087
 8088    // Scrolling locally breaks the follow
 8089    _ = follower.update(cx, |follower, cx| {
 8090        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
 8091        follower.set_scroll_anchor(
 8092            ScrollAnchor {
 8093                anchor: top_anchor,
 8094                offset: gpui::Point::new(0.0, 0.5),
 8095            },
 8096            cx,
 8097        );
 8098    });
 8099    assert_eq!(*is_still_following.borrow(), false);
 8100}
 8101
 8102#[gpui::test]
 8103async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 8104    init_test(cx, |_| {});
 8105
 8106    let fs = FakeFs::new(cx.executor());
 8107    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 8108    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8109    let pane = workspace
 8110        .update(cx, |workspace, _| workspace.active_pane().clone())
 8111        .unwrap();
 8112
 8113    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8114
 8115    let leader = pane.update(cx, |_, cx| {
 8116        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
 8117        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
 8118    });
 8119
 8120    // Start following the editor when it has no excerpts.
 8121    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 8122    let follower_1 = cx
 8123        .update_window(*workspace.deref(), |_, cx| {
 8124            Editor::from_state_proto(
 8125                pane.clone(),
 8126                workspace.root_view(cx).unwrap(),
 8127                ViewId {
 8128                    creator: Default::default(),
 8129                    id: 0,
 8130                },
 8131                &mut state_message,
 8132                cx,
 8133            )
 8134        })
 8135        .unwrap()
 8136        .unwrap()
 8137        .await
 8138        .unwrap();
 8139
 8140    let update_message = Rc::new(RefCell::new(None));
 8141    follower_1.update(cx, {
 8142        let update = update_message.clone();
 8143        |_, cx| {
 8144            cx.subscribe(&leader, move |_, leader, event, cx| {
 8145                leader
 8146                    .read(cx)
 8147                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 8148            })
 8149            .detach();
 8150        }
 8151    });
 8152
 8153    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
 8154        (
 8155            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
 8156            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
 8157        )
 8158    });
 8159
 8160    // Insert some excerpts.
 8161    _ = leader.update(cx, |leader, cx| {
 8162        leader.buffer.update(cx, |multibuffer, cx| {
 8163            let excerpt_ids = multibuffer.push_excerpts(
 8164                buffer_1.clone(),
 8165                [
 8166                    ExcerptRange {
 8167                        context: 1..6,
 8168                        primary: None,
 8169                    },
 8170                    ExcerptRange {
 8171                        context: 12..15,
 8172                        primary: None,
 8173                    },
 8174                    ExcerptRange {
 8175                        context: 0..3,
 8176                        primary: None,
 8177                    },
 8178                ],
 8179                cx,
 8180            );
 8181            multibuffer.insert_excerpts_after(
 8182                excerpt_ids[0],
 8183                buffer_2.clone(),
 8184                [
 8185                    ExcerptRange {
 8186                        context: 8..12,
 8187                        primary: None,
 8188                    },
 8189                    ExcerptRange {
 8190                        context: 0..6,
 8191                        primary: None,
 8192                    },
 8193                ],
 8194                cx,
 8195            );
 8196        });
 8197    });
 8198
 8199    // Apply the update of adding the excerpts.
 8200    follower_1
 8201        .update(cx, |follower, cx| {
 8202            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8203        })
 8204        .await
 8205        .unwrap();
 8206    assert_eq!(
 8207        follower_1.update(cx, |editor, cx| editor.text(cx)),
 8208        leader.update(cx, |editor, cx| editor.text(cx))
 8209    );
 8210    update_message.borrow_mut().take();
 8211
 8212    // Start following separately after it already has excerpts.
 8213    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 8214    let follower_2 = cx
 8215        .update_window(*workspace.deref(), |_, cx| {
 8216            Editor::from_state_proto(
 8217                pane.clone(),
 8218                workspace.root_view(cx).unwrap().clone(),
 8219                ViewId {
 8220                    creator: Default::default(),
 8221                    id: 0,
 8222                },
 8223                &mut state_message,
 8224                cx,
 8225            )
 8226        })
 8227        .unwrap()
 8228        .unwrap()
 8229        .await
 8230        .unwrap();
 8231    assert_eq!(
 8232        follower_2.update(cx, |editor, cx| editor.text(cx)),
 8233        leader.update(cx, |editor, cx| editor.text(cx))
 8234    );
 8235
 8236    // Remove some excerpts.
 8237    _ = leader.update(cx, |leader, cx| {
 8238        leader.buffer.update(cx, |multibuffer, cx| {
 8239            let excerpt_ids = multibuffer.excerpt_ids();
 8240            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
 8241            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
 8242        });
 8243    });
 8244
 8245    // Apply the update of removing the excerpts.
 8246    follower_1
 8247        .update(cx, |follower, cx| {
 8248            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8249        })
 8250        .await
 8251        .unwrap();
 8252    follower_2
 8253        .update(cx, |follower, cx| {
 8254            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8255        })
 8256        .await
 8257        .unwrap();
 8258    update_message.borrow_mut().take();
 8259    assert_eq!(
 8260        follower_1.update(cx, |editor, cx| editor.text(cx)),
 8261        leader.update(cx, |editor, cx| editor.text(cx))
 8262    );
 8263}
 8264
 8265#[gpui::test]
 8266async fn go_to_prev_overlapping_diagnostic(
 8267    executor: BackgroundExecutor,
 8268    cx: &mut gpui::TestAppContext,
 8269) {
 8270    init_test(cx, |_| {});
 8271
 8272    let mut cx = EditorTestContext::new(cx).await;
 8273    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
 8274
 8275    cx.set_state(indoc! {"
 8276        ˇfn func(abc def: i32) -> u32 {
 8277        }
 8278    "});
 8279
 8280    _ = cx.update(|cx| {
 8281        _ = project.update(cx, |project, cx| {
 8282            project
 8283                .update_diagnostics(
 8284                    LanguageServerId(0),
 8285                    lsp::PublishDiagnosticsParams {
 8286                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
 8287                        version: None,
 8288                        diagnostics: vec![
 8289                            lsp::Diagnostic {
 8290                                range: lsp::Range::new(
 8291                                    lsp::Position::new(0, 11),
 8292                                    lsp::Position::new(0, 12),
 8293                                ),
 8294                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8295                                ..Default::default()
 8296                            },
 8297                            lsp::Diagnostic {
 8298                                range: lsp::Range::new(
 8299                                    lsp::Position::new(0, 12),
 8300                                    lsp::Position::new(0, 15),
 8301                                ),
 8302                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8303                                ..Default::default()
 8304                            },
 8305                            lsp::Diagnostic {
 8306                                range: lsp::Range::new(
 8307                                    lsp::Position::new(0, 25),
 8308                                    lsp::Position::new(0, 28),
 8309                                ),
 8310                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8311                                ..Default::default()
 8312                            },
 8313                        ],
 8314                    },
 8315                    &[],
 8316                    cx,
 8317                )
 8318                .unwrap()
 8319        });
 8320    });
 8321
 8322    executor.run_until_parked();
 8323
 8324    cx.update_editor(|editor, cx| {
 8325        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8326    });
 8327
 8328    cx.assert_editor_state(indoc! {"
 8329        fn func(abc def: i32) -> ˇu32 {
 8330        }
 8331    "});
 8332
 8333    cx.update_editor(|editor, cx| {
 8334        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8335    });
 8336
 8337    cx.assert_editor_state(indoc! {"
 8338        fn func(abc ˇdef: i32) -> u32 {
 8339        }
 8340    "});
 8341
 8342    cx.update_editor(|editor, cx| {
 8343        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8344    });
 8345
 8346    cx.assert_editor_state(indoc! {"
 8347        fn func(abcˇ def: i32) -> u32 {
 8348        }
 8349    "});
 8350
 8351    cx.update_editor(|editor, cx| {
 8352        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8353    });
 8354
 8355    cx.assert_editor_state(indoc! {"
 8356        fn func(abc def: i32) -> ˇu32 {
 8357        }
 8358    "});
 8359}
 8360
 8361#[gpui::test]
 8362async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 8363    init_test(cx, |_| {});
 8364
 8365    let mut cx = EditorTestContext::new(cx).await;
 8366
 8367    let diff_base = r#"
 8368        use some::mod;
 8369
 8370        const A: u32 = 42;
 8371
 8372        fn main() {
 8373            println!("hello");
 8374
 8375            println!("world");
 8376        }
 8377        "#
 8378    .unindent();
 8379
 8380    // Edits are modified, removed, modified, added
 8381    cx.set_state(
 8382        &r#"
 8383        use some::modified;
 8384
 8385        ˇ
 8386        fn main() {
 8387            println!("hello there");
 8388
 8389            println!("around the");
 8390            println!("world");
 8391        }
 8392        "#
 8393        .unindent(),
 8394    );
 8395
 8396    cx.set_diff_base(Some(&diff_base));
 8397    executor.run_until_parked();
 8398
 8399    cx.update_editor(|editor, cx| {
 8400        //Wrap around the bottom of the buffer
 8401        for _ in 0..3 {
 8402            editor.go_to_hunk(&GoToHunk, cx);
 8403        }
 8404    });
 8405
 8406    cx.assert_editor_state(
 8407        &r#"
 8408        ˇuse some::modified;
 8409
 8410
 8411        fn main() {
 8412            println!("hello there");
 8413
 8414            println!("around the");
 8415            println!("world");
 8416        }
 8417        "#
 8418        .unindent(),
 8419    );
 8420
 8421    cx.update_editor(|editor, cx| {
 8422        //Wrap around the top of the buffer
 8423        for _ in 0..2 {
 8424            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8425        }
 8426    });
 8427
 8428    cx.assert_editor_state(
 8429        &r#"
 8430        use some::modified;
 8431
 8432
 8433        fn main() {
 8434        ˇ    println!("hello there");
 8435
 8436            println!("around the");
 8437            println!("world");
 8438        }
 8439        "#
 8440        .unindent(),
 8441    );
 8442
 8443    cx.update_editor(|editor, cx| {
 8444        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8445    });
 8446
 8447    cx.assert_editor_state(
 8448        &r#"
 8449        use some::modified;
 8450
 8451        ˇ
 8452        fn main() {
 8453            println!("hello there");
 8454
 8455            println!("around the");
 8456            println!("world");
 8457        }
 8458        "#
 8459        .unindent(),
 8460    );
 8461
 8462    cx.update_editor(|editor, cx| {
 8463        for _ in 0..3 {
 8464            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8465        }
 8466    });
 8467
 8468    cx.assert_editor_state(
 8469        &r#"
 8470        use some::modified;
 8471
 8472
 8473        fn main() {
 8474        ˇ    println!("hello there");
 8475
 8476            println!("around the");
 8477            println!("world");
 8478        }
 8479        "#
 8480        .unindent(),
 8481    );
 8482
 8483    cx.update_editor(|editor, cx| {
 8484        editor.fold(&Fold, cx);
 8485
 8486        //Make sure that the fold only gets one hunk
 8487        for _ in 0..4 {
 8488            editor.go_to_hunk(&GoToHunk, cx);
 8489        }
 8490    });
 8491
 8492    cx.assert_editor_state(
 8493        &r#"
 8494        ˇuse some::modified;
 8495
 8496
 8497        fn main() {
 8498            println!("hello there");
 8499
 8500            println!("around the");
 8501            println!("world");
 8502        }
 8503        "#
 8504        .unindent(),
 8505    );
 8506}
 8507
 8508#[test]
 8509fn test_split_words() {
 8510    fn split(text: &str) -> Vec<&str> {
 8511        split_words(text).collect()
 8512    }
 8513
 8514    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
 8515    assert_eq!(split("hello_world"), &["hello_", "world"]);
 8516    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
 8517    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
 8518    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
 8519    assert_eq!(split("helloworld"), &["helloworld"]);
 8520
 8521    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
 8522}
 8523
 8524#[gpui::test]
 8525async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
 8526    init_test(cx, |_| {});
 8527
 8528    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
 8529    let mut assert = |before, after| {
 8530        let _state_context = cx.set_state(before);
 8531        cx.update_editor(|editor, cx| {
 8532            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
 8533        });
 8534        cx.assert_editor_state(after);
 8535    };
 8536
 8537    // Outside bracket jumps to outside of matching bracket
 8538    assert("console.logˇ(var);", "console.log(var)ˇ;");
 8539    assert("console.log(var)ˇ;", "console.logˇ(var);");
 8540
 8541    // Inside bracket jumps to inside of matching bracket
 8542    assert("console.log(ˇvar);", "console.log(varˇ);");
 8543    assert("console.log(varˇ);", "console.log(ˇvar);");
 8544
 8545    // When outside a bracket and inside, favor jumping to the inside bracket
 8546    assert(
 8547        "console.log('foo', [1, 2, 3]ˇ);",
 8548        "console.log(ˇ'foo', [1, 2, 3]);",
 8549    );
 8550    assert(
 8551        "console.log(ˇ'foo', [1, 2, 3]);",
 8552        "console.log('foo', [1, 2, 3]ˇ);",
 8553    );
 8554
 8555    // Bias forward if two options are equally likely
 8556    assert(
 8557        "let result = curried_fun()ˇ();",
 8558        "let result = curried_fun()()ˇ;",
 8559    );
 8560
 8561    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
 8562    assert(
 8563        indoc! {"
 8564            function test() {
 8565                console.log('test')ˇ
 8566            }"},
 8567        indoc! {"
 8568            function test() {
 8569                console.logˇ('test')
 8570            }"},
 8571    );
 8572}
 8573
 8574#[gpui::test]
 8575async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
 8576    init_test(cx, |_| {});
 8577
 8578    let fs = FakeFs::new(cx.executor());
 8579    fs.insert_tree(
 8580        "/a",
 8581        json!({
 8582            "main.rs": "fn main() { let a = 5; }",
 8583            "other.rs": "// Test file",
 8584        }),
 8585    )
 8586    .await;
 8587    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8588
 8589    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8590    language_registry.add(Arc::new(Language::new(
 8591        LanguageConfig {
 8592            name: "Rust".into(),
 8593            matcher: LanguageMatcher {
 8594                path_suffixes: vec!["rs".to_string()],
 8595                ..Default::default()
 8596            },
 8597            brackets: BracketPairConfig {
 8598                pairs: vec![BracketPair {
 8599                    start: "{".to_string(),
 8600                    end: "}".to_string(),
 8601                    close: true,
 8602                    surround: true,
 8603                    newline: true,
 8604                }],
 8605                disabled_scopes_by_bracket_ix: Vec::new(),
 8606            },
 8607            ..Default::default()
 8608        },
 8609        Some(tree_sitter_rust::language()),
 8610    )));
 8611    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8612        "Rust",
 8613        FakeLspAdapter {
 8614            capabilities: lsp::ServerCapabilities {
 8615                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
 8616                    first_trigger_character: "{".to_string(),
 8617                    more_trigger_character: None,
 8618                }),
 8619                ..Default::default()
 8620            },
 8621            ..Default::default()
 8622        },
 8623    );
 8624
 8625    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8626
 8627    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 8628
 8629    let worktree_id = workspace
 8630        .update(cx, |workspace, cx| {
 8631            workspace.project().update(cx, |project, cx| {
 8632                project.worktrees().next().unwrap().read(cx).id()
 8633            })
 8634        })
 8635        .unwrap();
 8636
 8637    let buffer = project
 8638        .update(cx, |project, cx| {
 8639            project.open_local_buffer("/a/main.rs", cx)
 8640        })
 8641        .await
 8642        .unwrap();
 8643    cx.executor().run_until_parked();
 8644    cx.executor().start_waiting();
 8645    let fake_server = fake_servers.next().await.unwrap();
 8646    let editor_handle = workspace
 8647        .update(cx, |workspace, cx| {
 8648            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 8649        })
 8650        .unwrap()
 8651        .await
 8652        .unwrap()
 8653        .downcast::<Editor>()
 8654        .unwrap();
 8655
 8656    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
 8657        assert_eq!(
 8658            params.text_document_position.text_document.uri,
 8659            lsp::Url::from_file_path("/a/main.rs").unwrap(),
 8660        );
 8661        assert_eq!(
 8662            params.text_document_position.position,
 8663            lsp::Position::new(0, 21),
 8664        );
 8665
 8666        Ok(Some(vec![lsp::TextEdit {
 8667            new_text: "]".to_string(),
 8668            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
 8669        }]))
 8670    });
 8671
 8672    editor_handle.update(cx, |editor, cx| {
 8673        editor.focus(cx);
 8674        editor.change_selections(None, cx, |s| {
 8675            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
 8676        });
 8677        editor.handle_input("{", cx);
 8678    });
 8679
 8680    cx.executor().run_until_parked();
 8681
 8682    _ = buffer.update(cx, |buffer, _| {
 8683        assert_eq!(
 8684            buffer.text(),
 8685            "fn main() { let a = {5}; }",
 8686            "No extra braces from on type formatting should appear in the buffer"
 8687        )
 8688    });
 8689}
 8690
 8691#[gpui::test]
 8692async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
 8693    init_test(cx, |_| {});
 8694
 8695    let fs = FakeFs::new(cx.executor());
 8696    fs.insert_tree(
 8697        "/a",
 8698        json!({
 8699            "main.rs": "fn main() { let a = 5; }",
 8700            "other.rs": "// Test file",
 8701        }),
 8702    )
 8703    .await;
 8704
 8705    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8706
 8707    let server_restarts = Arc::new(AtomicUsize::new(0));
 8708    let closure_restarts = Arc::clone(&server_restarts);
 8709    let language_server_name = "test language server";
 8710    let language_name: Arc<str> = "Rust".into();
 8711
 8712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8713    language_registry.add(Arc::new(Language::new(
 8714        LanguageConfig {
 8715            name: Arc::clone(&language_name),
 8716            matcher: LanguageMatcher {
 8717                path_suffixes: vec!["rs".to_string()],
 8718                ..Default::default()
 8719            },
 8720            ..Default::default()
 8721        },
 8722        Some(tree_sitter_rust::language()),
 8723    )));
 8724    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8725        "Rust",
 8726        FakeLspAdapter {
 8727            name: language_server_name,
 8728            initialization_options: Some(json!({
 8729                "testOptionValue": true
 8730            })),
 8731            initializer: Some(Box::new(move |fake_server| {
 8732                let task_restarts = Arc::clone(&closure_restarts);
 8733                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
 8734                    task_restarts.fetch_add(1, atomic::Ordering::Release);
 8735                    futures::future::ready(Ok(()))
 8736                });
 8737            })),
 8738            ..Default::default()
 8739        },
 8740    );
 8741
 8742    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8743    let _buffer = project
 8744        .update(cx, |project, cx| {
 8745            project.open_local_buffer("/a/main.rs", cx)
 8746        })
 8747        .await
 8748        .unwrap();
 8749    let _fake_server = fake_servers.next().await.unwrap();
 8750    update_test_language_settings(cx, |language_settings| {
 8751        language_settings.languages.insert(
 8752            Arc::clone(&language_name),
 8753            LanguageSettingsContent {
 8754                tab_size: NonZeroU32::new(8),
 8755                ..Default::default()
 8756            },
 8757        );
 8758    });
 8759    cx.executor().run_until_parked();
 8760    assert_eq!(
 8761        server_restarts.load(atomic::Ordering::Acquire),
 8762        0,
 8763        "Should not restart LSP server on an unrelated change"
 8764    );
 8765
 8766    update_test_project_settings(cx, |project_settings| {
 8767        project_settings.lsp.insert(
 8768            "Some other server name".into(),
 8769            LspSettings {
 8770                binary: None,
 8771                settings: None,
 8772                initialization_options: Some(json!({
 8773                    "some other init value": false
 8774                })),
 8775            },
 8776        );
 8777    });
 8778    cx.executor().run_until_parked();
 8779    assert_eq!(
 8780        server_restarts.load(atomic::Ordering::Acquire),
 8781        0,
 8782        "Should not restart LSP server on an unrelated LSP settings change"
 8783    );
 8784
 8785    update_test_project_settings(cx, |project_settings| {
 8786        project_settings.lsp.insert(
 8787            language_server_name.into(),
 8788            LspSettings {
 8789                binary: None,
 8790                settings: None,
 8791                initialization_options: Some(json!({
 8792                    "anotherInitValue": false
 8793                })),
 8794            },
 8795        );
 8796    });
 8797    cx.executor().run_until_parked();
 8798    assert_eq!(
 8799        server_restarts.load(atomic::Ordering::Acquire),
 8800        1,
 8801        "Should restart LSP server on a related LSP settings change"
 8802    );
 8803
 8804    update_test_project_settings(cx, |project_settings| {
 8805        project_settings.lsp.insert(
 8806            language_server_name.into(),
 8807            LspSettings {
 8808                binary: None,
 8809                settings: None,
 8810                initialization_options: Some(json!({
 8811                    "anotherInitValue": false
 8812                })),
 8813            },
 8814        );
 8815    });
 8816    cx.executor().run_until_parked();
 8817    assert_eq!(
 8818        server_restarts.load(atomic::Ordering::Acquire),
 8819        1,
 8820        "Should not restart LSP server on a related LSP settings change that is the same"
 8821    );
 8822
 8823    update_test_project_settings(cx, |project_settings| {
 8824        project_settings.lsp.insert(
 8825            language_server_name.into(),
 8826            LspSettings {
 8827                binary: None,
 8828                settings: None,
 8829                initialization_options: None,
 8830            },
 8831        );
 8832    });
 8833    cx.executor().run_until_parked();
 8834    assert_eq!(
 8835        server_restarts.load(atomic::Ordering::Acquire),
 8836        2,
 8837        "Should restart LSP server on another related LSP settings change"
 8838    );
 8839}
 8840
 8841#[gpui::test]
 8842async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
 8843    init_test(cx, |_| {});
 8844
 8845    let mut cx = EditorLspTestContext::new_rust(
 8846        lsp::ServerCapabilities {
 8847            completion_provider: Some(lsp::CompletionOptions {
 8848                trigger_characters: Some(vec![".".to_string()]),
 8849                resolve_provider: Some(true),
 8850                ..Default::default()
 8851            }),
 8852            ..Default::default()
 8853        },
 8854        cx,
 8855    )
 8856    .await;
 8857
 8858    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 8859    cx.simulate_keystroke(".");
 8860    let completion_item = lsp::CompletionItem {
 8861        label: "some".into(),
 8862        kind: Some(lsp::CompletionItemKind::SNIPPET),
 8863        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 8864        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 8865            kind: lsp::MarkupKind::Markdown,
 8866            value: "```rust\nSome(2)\n```".to_string(),
 8867        })),
 8868        deprecated: Some(false),
 8869        sort_text: Some("fffffff2".to_string()),
 8870        filter_text: Some("some".to_string()),
 8871        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 8872        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 8873            range: lsp::Range {
 8874                start: lsp::Position {
 8875                    line: 0,
 8876                    character: 22,
 8877                },
 8878                end: lsp::Position {
 8879                    line: 0,
 8880                    character: 22,
 8881                },
 8882            },
 8883            new_text: "Some(2)".to_string(),
 8884        })),
 8885        additional_text_edits: Some(vec![lsp::TextEdit {
 8886            range: lsp::Range {
 8887                start: lsp::Position {
 8888                    line: 0,
 8889                    character: 20,
 8890                },
 8891                end: lsp::Position {
 8892                    line: 0,
 8893                    character: 22,
 8894                },
 8895            },
 8896            new_text: "".to_string(),
 8897        }]),
 8898        ..Default::default()
 8899    };
 8900
 8901    let closure_completion_item = completion_item.clone();
 8902    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 8903        let task_completion_item = closure_completion_item.clone();
 8904        async move {
 8905            Ok(Some(lsp::CompletionResponse::Array(vec![
 8906                task_completion_item,
 8907            ])))
 8908        }
 8909    });
 8910
 8911    request.next().await;
 8912
 8913    cx.condition(|editor, _| editor.context_menu_visible())
 8914        .await;
 8915    let apply_additional_edits = cx.update_editor(|editor, cx| {
 8916        editor
 8917            .confirm_completion(&ConfirmCompletion::default(), cx)
 8918            .unwrap()
 8919    });
 8920    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
 8921
 8922    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
 8923        let task_completion_item = completion_item.clone();
 8924        async move { Ok(task_completion_item) }
 8925    })
 8926    .next()
 8927    .await
 8928    .unwrap();
 8929    apply_additional_edits.await.unwrap();
 8930    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
 8931}
 8932
 8933#[gpui::test]
 8934async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
 8935    init_test(cx, |_| {});
 8936
 8937    let mut cx = EditorLspTestContext::new(
 8938        Language::new(
 8939            LanguageConfig {
 8940                matcher: LanguageMatcher {
 8941                    path_suffixes: vec!["jsx".into()],
 8942                    ..Default::default()
 8943                },
 8944                overrides: [(
 8945                    "element".into(),
 8946                    LanguageConfigOverride {
 8947                        word_characters: Override::Set(['-'].into_iter().collect()),
 8948                        ..Default::default()
 8949                    },
 8950                )]
 8951                .into_iter()
 8952                .collect(),
 8953                ..Default::default()
 8954            },
 8955            Some(tree_sitter_typescript::language_tsx()),
 8956        )
 8957        .with_override_query("(jsx_self_closing_element) @element")
 8958        .unwrap(),
 8959        lsp::ServerCapabilities {
 8960            completion_provider: Some(lsp::CompletionOptions {
 8961                trigger_characters: Some(vec![":".to_string()]),
 8962                ..Default::default()
 8963            }),
 8964            ..Default::default()
 8965        },
 8966        cx,
 8967    )
 8968    .await;
 8969
 8970    cx.lsp
 8971        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 8972            Ok(Some(lsp::CompletionResponse::Array(vec![
 8973                lsp::CompletionItem {
 8974                    label: "bg-blue".into(),
 8975                    ..Default::default()
 8976                },
 8977                lsp::CompletionItem {
 8978                    label: "bg-red".into(),
 8979                    ..Default::default()
 8980                },
 8981                lsp::CompletionItem {
 8982                    label: "bg-yellow".into(),
 8983                    ..Default::default()
 8984                },
 8985            ])))
 8986        });
 8987
 8988    cx.set_state(r#"<p class="bgˇ" />"#);
 8989
 8990    // Trigger completion when typing a dash, because the dash is an extra
 8991    // word character in the 'element' scope, which contains the cursor.
 8992    cx.simulate_keystroke("-");
 8993    cx.executor().run_until_parked();
 8994    cx.update_editor(|editor, _| {
 8995        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8996            assert_eq!(
 8997                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8998                &["bg-red", "bg-blue", "bg-yellow"]
 8999            );
 9000        } else {
 9001            panic!("expected completion menu to be open");
 9002        }
 9003    });
 9004
 9005    cx.simulate_keystroke("l");
 9006    cx.executor().run_until_parked();
 9007    cx.update_editor(|editor, _| {
 9008        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 9009            assert_eq!(
 9010                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 9011                &["bg-blue", "bg-yellow"]
 9012            );
 9013        } else {
 9014            panic!("expected completion menu to be open");
 9015        }
 9016    });
 9017
 9018    // When filtering completions, consider the character after the '-' to
 9019    // be the start of a subword.
 9020    cx.set_state(r#"<p class="yelˇ" />"#);
 9021    cx.simulate_keystroke("l");
 9022    cx.executor().run_until_parked();
 9023    cx.update_editor(|editor, _| {
 9024        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 9025            assert_eq!(
 9026                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 9027                &["bg-yellow"]
 9028            );
 9029        } else {
 9030            panic!("expected completion menu to be open");
 9031        }
 9032    });
 9033}
 9034
 9035#[gpui::test]
 9036async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
 9037    init_test(cx, |settings| {
 9038        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
 9039    });
 9040
 9041    let fs = FakeFs::new(cx.executor());
 9042    fs.insert_file("/file.ts", Default::default()).await;
 9043
 9044    let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
 9045    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9046
 9047    language_registry.add(Arc::new(Language::new(
 9048        LanguageConfig {
 9049            name: "TypeScript".into(),
 9050            matcher: LanguageMatcher {
 9051                path_suffixes: vec!["ts".to_string()],
 9052                ..Default::default()
 9053            },
 9054            ..Default::default()
 9055        },
 9056        Some(tree_sitter_rust::language()),
 9057    )));
 9058    update_test_language_settings(cx, |settings| {
 9059        settings.defaults.prettier = Some(PrettierSettings {
 9060            allowed: true,
 9061            ..PrettierSettings::default()
 9062        });
 9063    });
 9064
 9065    let test_plugin = "test_plugin";
 9066    let _ = language_registry.register_fake_lsp_adapter(
 9067        "TypeScript",
 9068        FakeLspAdapter {
 9069            prettier_plugins: vec![test_plugin],
 9070            ..Default::default()
 9071        },
 9072    );
 9073
 9074    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
 9075    let buffer = project
 9076        .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
 9077        .await
 9078        .unwrap();
 9079
 9080    let buffer_text = "one\ntwo\nthree\n";
 9081    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 9082    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 9083    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
 9084
 9085    editor
 9086        .update(cx, |editor, cx| {
 9087            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 9088        })
 9089        .unwrap()
 9090        .await;
 9091    assert_eq!(
 9092        editor.update(cx, |editor, cx| editor.text(cx)),
 9093        buffer_text.to_string() + prettier_format_suffix,
 9094        "Test prettier formatting was not applied to the original buffer text",
 9095    );
 9096
 9097    update_test_language_settings(cx, |settings| {
 9098        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 9099    });
 9100    let format = editor.update(cx, |editor, cx| {
 9101        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 9102    });
 9103    format.await.unwrap();
 9104    assert_eq!(
 9105        editor.update(cx, |editor, cx| editor.text(cx)),
 9106        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
 9107        "Autoformatting (via test prettier) was not applied to the original buffer text",
 9108    );
 9109}
 9110
 9111#[gpui::test]
 9112async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
 9113    init_test(cx, |_| {});
 9114    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9115    let base_text = indoc! {r#"struct Row;
 9116struct Row1;
 9117struct Row2;
 9118
 9119struct Row4;
 9120struct Row5;
 9121struct Row6;
 9122
 9123struct Row8;
 9124struct Row9;
 9125struct Row10;"#};
 9126
 9127    // When addition hunks are not adjacent to carets, no hunk revert is performed
 9128    assert_hunk_revert(
 9129        indoc! {r#"struct Row;
 9130                   struct Row1;
 9131                   struct Row1.1;
 9132                   struct Row1.2;
 9133                   struct Row2;ˇ
 9134
 9135                   struct Row4;
 9136                   struct Row5;
 9137                   struct Row6;
 9138
 9139                   struct Row8;
 9140                   ˇstruct Row9;
 9141                   struct Row9.1;
 9142                   struct Row9.2;
 9143                   struct Row9.3;
 9144                   struct Row10;"#},
 9145        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 9146        indoc! {r#"struct Row;
 9147                   struct Row1;
 9148                   struct Row1.1;
 9149                   struct Row1.2;
 9150                   struct Row2;ˇ
 9151
 9152                   struct Row4;
 9153                   struct Row5;
 9154                   struct Row6;
 9155
 9156                   struct Row8;
 9157                   ˇstruct Row9;
 9158                   struct Row9.1;
 9159                   struct Row9.2;
 9160                   struct Row9.3;
 9161                   struct Row10;"#},
 9162        base_text,
 9163        &mut cx,
 9164    );
 9165    // Same for selections
 9166    assert_hunk_revert(
 9167        indoc! {r#"struct Row;
 9168                   struct Row1;
 9169                   struct Row2;
 9170                   struct Row2.1;
 9171                   struct Row2.2;
 9172                   «ˇ
 9173                   struct Row4;
 9174                   struct» Row5;
 9175                   «struct Row6;
 9176                   ˇ»
 9177                   struct Row9.1;
 9178                   struct Row9.2;
 9179                   struct Row9.3;
 9180                   struct Row8;
 9181                   struct Row9;
 9182                   struct Row10;"#},
 9183        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 9184        indoc! {r#"struct Row;
 9185                   struct Row1;
 9186                   struct Row2;
 9187                   struct Row2.1;
 9188                   struct Row2.2;
 9189                   «ˇ
 9190                   struct Row4;
 9191                   struct» Row5;
 9192                   «struct Row6;
 9193                   ˇ»
 9194                   struct Row9.1;
 9195                   struct Row9.2;
 9196                   struct Row9.3;
 9197                   struct Row8;
 9198                   struct Row9;
 9199                   struct Row10;"#},
 9200        base_text,
 9201        &mut cx,
 9202    );
 9203
 9204    // When carets and selections intersect the addition hunks, those are reverted.
 9205    // Adjacent carets got merged.
 9206    assert_hunk_revert(
 9207        indoc! {r#"struct Row;
 9208                   ˇ// something on the top
 9209                   struct Row1;
 9210                   struct Row2;
 9211                   struct Roˇw3.1;
 9212                   struct Row2.2;
 9213                   struct Row2.3;ˇ
 9214
 9215                   struct Row4;
 9216                   struct ˇRow5.1;
 9217                   struct Row5.2;
 9218                   struct «Rowˇ»5.3;
 9219                   struct Row5;
 9220                   struct Row6;
 9221                   ˇ
 9222                   struct Row9.1;
 9223                   struct «Rowˇ»9.2;
 9224                   struct «ˇRow»9.3;
 9225                   struct Row8;
 9226                   struct Row9;
 9227                   «ˇ// something on bottom»
 9228                   struct Row10;"#},
 9229        vec![
 9230            DiffHunkStatus::Added,
 9231            DiffHunkStatus::Added,
 9232            DiffHunkStatus::Added,
 9233            DiffHunkStatus::Added,
 9234            DiffHunkStatus::Added,
 9235        ],
 9236        indoc! {r#"struct Row;
 9237                   ˇstruct Row1;
 9238                   struct Row2;
 9239                   ˇ
 9240                   struct Row4;
 9241                   ˇstruct Row5;
 9242                   struct Row6;
 9243                   ˇ
 9244                   ˇstruct Row8;
 9245                   struct Row9;
 9246                   ˇstruct Row10;"#},
 9247        base_text,
 9248        &mut cx,
 9249    );
 9250}
 9251
 9252#[gpui::test]
 9253async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
 9254    init_test(cx, |_| {});
 9255    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9256    let base_text = indoc! {r#"struct Row;
 9257struct Row1;
 9258struct Row2;
 9259
 9260struct Row4;
 9261struct Row5;
 9262struct Row6;
 9263
 9264struct Row8;
 9265struct Row9;
 9266struct Row10;"#};
 9267
 9268    // Modification hunks behave the same as the addition ones.
 9269    assert_hunk_revert(
 9270        indoc! {r#"struct Row;
 9271                   struct Row1;
 9272                   struct Row33;
 9273                   ˇ
 9274                   struct Row4;
 9275                   struct Row5;
 9276                   struct Row6;
 9277                   ˇ
 9278                   struct Row99;
 9279                   struct Row9;
 9280                   struct Row10;"#},
 9281        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 9282        indoc! {r#"struct Row;
 9283                   struct Row1;
 9284                   struct Row33;
 9285                   ˇ
 9286                   struct Row4;
 9287                   struct Row5;
 9288                   struct Row6;
 9289                   ˇ
 9290                   struct Row99;
 9291                   struct Row9;
 9292                   struct Row10;"#},
 9293        base_text,
 9294        &mut cx,
 9295    );
 9296    assert_hunk_revert(
 9297        indoc! {r#"struct Row;
 9298                   struct Row1;
 9299                   struct Row33;
 9300                   «ˇ
 9301                   struct Row4;
 9302                   struct» Row5;
 9303                   «struct Row6;
 9304                   ˇ»
 9305                   struct Row99;
 9306                   struct Row9;
 9307                   struct Row10;"#},
 9308        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 9309        indoc! {r#"struct Row;
 9310                   struct Row1;
 9311                   struct Row33;
 9312                   «ˇ
 9313                   struct Row4;
 9314                   struct» Row5;
 9315                   «struct Row6;
 9316                   ˇ»
 9317                   struct Row99;
 9318                   struct Row9;
 9319                   struct Row10;"#},
 9320        base_text,
 9321        &mut cx,
 9322    );
 9323
 9324    assert_hunk_revert(
 9325        indoc! {r#"ˇstruct Row1.1;
 9326                   struct Row1;
 9327                   «ˇstr»uct Row22;
 9328
 9329                   struct ˇRow44;
 9330                   struct Row5;
 9331                   struct «Rˇ»ow66;ˇ
 9332
 9333                   «struˇ»ct Row88;
 9334                   struct Row9;
 9335                   struct Row1011;ˇ"#},
 9336        vec![
 9337            DiffHunkStatus::Modified,
 9338            DiffHunkStatus::Modified,
 9339            DiffHunkStatus::Modified,
 9340            DiffHunkStatus::Modified,
 9341            DiffHunkStatus::Modified,
 9342            DiffHunkStatus::Modified,
 9343        ],
 9344        indoc! {r#"struct Row;
 9345                   ˇstruct Row1;
 9346                   struct Row2;
 9347                   ˇ
 9348                   struct Row4;
 9349                   ˇstruct Row5;
 9350                   struct Row6;
 9351                   ˇ
 9352                   struct Row8;
 9353                   ˇstruct Row9;
 9354                   struct Row10;ˇ"#},
 9355        base_text,
 9356        &mut cx,
 9357    );
 9358}
 9359
 9360#[gpui::test]
 9361async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
 9362    init_test(cx, |_| {});
 9363    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9364    let base_text = indoc! {r#"struct Row;
 9365struct Row1;
 9366struct Row2;
 9367
 9368struct Row4;
 9369struct Row5;
 9370struct Row6;
 9371
 9372struct Row8;
 9373struct Row9;
 9374struct Row10;"#};
 9375
 9376    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
 9377    assert_hunk_revert(
 9378        indoc! {r#"struct Row;
 9379                   struct Row2;
 9380
 9381                   ˇstruct Row4;
 9382                   struct Row5;
 9383                   struct Row6;
 9384                   ˇ
 9385                   struct Row8;
 9386                   struct Row10;"#},
 9387        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9388        indoc! {r#"struct Row;
 9389                   struct Row2;
 9390
 9391                   ˇstruct Row4;
 9392                   struct Row5;
 9393                   struct Row6;
 9394                   ˇ
 9395                   struct Row8;
 9396                   struct Row10;"#},
 9397        base_text,
 9398        &mut cx,
 9399    );
 9400    assert_hunk_revert(
 9401        indoc! {r#"struct Row;
 9402                   struct Row2;
 9403
 9404                   «ˇstruct Row4;
 9405                   struct» Row5;
 9406                   «struct Row6;
 9407                   ˇ»
 9408                   struct Row8;
 9409                   struct Row10;"#},
 9410        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9411        indoc! {r#"struct Row;
 9412                   struct Row2;
 9413
 9414                   «ˇstruct Row4;
 9415                   struct» Row5;
 9416                   «struct Row6;
 9417                   ˇ»
 9418                   struct Row8;
 9419                   struct Row10;"#},
 9420        base_text,
 9421        &mut cx,
 9422    );
 9423
 9424    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
 9425    assert_hunk_revert(
 9426        indoc! {r#"struct Row;
 9427                   ˇstruct Row2;
 9428
 9429                   struct Row4;
 9430                   struct Row5;
 9431                   struct Row6;
 9432
 9433                   struct Row8;ˇ
 9434                   struct Row10;"#},
 9435        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9436        indoc! {r#"struct Row;
 9437                   struct Row1;
 9438                   ˇstruct Row2;
 9439
 9440                   struct Row4;
 9441                   struct Row5;
 9442                   struct Row6;
 9443
 9444                   struct Row8;ˇ
 9445                   struct Row9;
 9446                   struct Row10;"#},
 9447        base_text,
 9448        &mut cx,
 9449    );
 9450    assert_hunk_revert(
 9451        indoc! {r#"struct Row;
 9452                   struct Row2«ˇ;
 9453                   struct Row4;
 9454                   struct» Row5;
 9455                   «struct Row6;
 9456
 9457                   struct Row8;ˇ»
 9458                   struct Row10;"#},
 9459        vec![
 9460            DiffHunkStatus::Removed,
 9461            DiffHunkStatus::Removed,
 9462            DiffHunkStatus::Removed,
 9463        ],
 9464        indoc! {r#"struct Row;
 9465                   struct Row1;
 9466                   struct Row2«ˇ;
 9467
 9468                   struct Row4;
 9469                   struct» Row5;
 9470                   «struct Row6;
 9471
 9472                   struct Row8;ˇ»
 9473                   struct Row9;
 9474                   struct Row10;"#},
 9475        base_text,
 9476        &mut cx,
 9477    );
 9478}
 9479
 9480#[gpui::test]
 9481async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
 9482    init_test(cx, |_| {});
 9483
 9484    let cols = 4;
 9485    let rows = 10;
 9486    let sample_text_1 = sample_text(rows, cols, 'a');
 9487    assert_eq!(
 9488        sample_text_1,
 9489        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9490    );
 9491    let sample_text_2 = sample_text(rows, cols, 'l');
 9492    assert_eq!(
 9493        sample_text_2,
 9494        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9495    );
 9496    let sample_text_3 = sample_text(rows, cols, 'v');
 9497    assert_eq!(
 9498        sample_text_3,
 9499        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9500    );
 9501
 9502    fn diff_every_buffer_row(
 9503        buffer: &Model<Buffer>,
 9504        sample_text: String,
 9505        cols: usize,
 9506        cx: &mut gpui::TestAppContext,
 9507    ) {
 9508        // revert first character in each row, creating one large diff hunk per buffer
 9509        let is_first_char = |offset: usize| offset % cols == 0;
 9510        buffer.update(cx, |buffer, cx| {
 9511            buffer.set_text(
 9512                sample_text
 9513                    .chars()
 9514                    .enumerate()
 9515                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
 9516                    .collect::<String>(),
 9517                cx,
 9518            );
 9519            buffer.set_diff_base(Some(sample_text), cx);
 9520        });
 9521        cx.executor().run_until_parked();
 9522    }
 9523
 9524    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9525    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9526
 9527    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9528    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9529
 9530    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9531    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9532
 9533    let multibuffer = cx.new_model(|cx| {
 9534        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9535        multibuffer.push_excerpts(
 9536            buffer_1.clone(),
 9537            [
 9538                ExcerptRange {
 9539                    context: Point::new(0, 0)..Point::new(3, 0),
 9540                    primary: None,
 9541                },
 9542                ExcerptRange {
 9543                    context: Point::new(5, 0)..Point::new(7, 0),
 9544                    primary: None,
 9545                },
 9546                ExcerptRange {
 9547                    context: Point::new(9, 0)..Point::new(10, 4),
 9548                    primary: None,
 9549                },
 9550            ],
 9551            cx,
 9552        );
 9553        multibuffer.push_excerpts(
 9554            buffer_2.clone(),
 9555            [
 9556                ExcerptRange {
 9557                    context: Point::new(0, 0)..Point::new(3, 0),
 9558                    primary: None,
 9559                },
 9560                ExcerptRange {
 9561                    context: Point::new(5, 0)..Point::new(7, 0),
 9562                    primary: None,
 9563                },
 9564                ExcerptRange {
 9565                    context: Point::new(9, 0)..Point::new(10, 4),
 9566                    primary: None,
 9567                },
 9568            ],
 9569            cx,
 9570        );
 9571        multibuffer.push_excerpts(
 9572            buffer_3.clone(),
 9573            [
 9574                ExcerptRange {
 9575                    context: Point::new(0, 0)..Point::new(3, 0),
 9576                    primary: None,
 9577                },
 9578                ExcerptRange {
 9579                    context: Point::new(5, 0)..Point::new(7, 0),
 9580                    primary: None,
 9581                },
 9582                ExcerptRange {
 9583                    context: Point::new(9, 0)..Point::new(10, 4),
 9584                    primary: None,
 9585                },
 9586            ],
 9587            cx,
 9588        );
 9589        multibuffer
 9590    });
 9591
 9592    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 9593    editor.update(cx, |editor, cx| {
 9594        assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
 9595        editor.select_all(&SelectAll, cx);
 9596        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9597    });
 9598    cx.executor().run_until_parked();
 9599    // When all ranges are selected, all buffer hunks are reverted.
 9600    editor.update(cx, |editor, cx| {
 9601        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
 9602    });
 9603    buffer_1.update(cx, |buffer, _| {
 9604        assert_eq!(buffer.text(), sample_text_1);
 9605    });
 9606    buffer_2.update(cx, |buffer, _| {
 9607        assert_eq!(buffer.text(), sample_text_2);
 9608    });
 9609    buffer_3.update(cx, |buffer, _| {
 9610        assert_eq!(buffer.text(), sample_text_3);
 9611    });
 9612
 9613    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9614    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9615    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9616    editor.update(cx, |editor, cx| {
 9617        editor.change_selections(None, cx, |s| {
 9618            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
 9619        });
 9620        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9621    });
 9622    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
 9623    // but not affect buffer_2 and its related excerpts.
 9624    editor.update(cx, |editor, cx| {
 9625        assert_eq!(
 9626            editor.text(cx),
 9627            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
 9628        );
 9629    });
 9630    buffer_1.update(cx, |buffer, _| {
 9631        assert_eq!(buffer.text(), sample_text_1);
 9632    });
 9633    buffer_2.update(cx, |buffer, _| {
 9634        assert_eq!(
 9635            buffer.text(),
 9636            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
 9637        );
 9638    });
 9639    buffer_3.update(cx, |buffer, _| {
 9640        assert_eq!(
 9641            buffer.text(),
 9642            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
 9643        );
 9644    });
 9645}
 9646
 9647#[gpui::test]
 9648async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
 9649    init_test(cx, |_| {});
 9650
 9651    let cols = 4;
 9652    let rows = 10;
 9653    let sample_text_1 = sample_text(rows, cols, 'a');
 9654    assert_eq!(
 9655        sample_text_1,
 9656        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9657    );
 9658    let sample_text_2 = sample_text(rows, cols, 'l');
 9659    assert_eq!(
 9660        sample_text_2,
 9661        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9662    );
 9663    let sample_text_3 = sample_text(rows, cols, 'v');
 9664    assert_eq!(
 9665        sample_text_3,
 9666        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9667    );
 9668
 9669    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9670    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9671    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9672
 9673    let multi_buffer = cx.new_model(|cx| {
 9674        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9675        multibuffer.push_excerpts(
 9676            buffer_1.clone(),
 9677            [
 9678                ExcerptRange {
 9679                    context: Point::new(0, 0)..Point::new(3, 0),
 9680                    primary: None,
 9681                },
 9682                ExcerptRange {
 9683                    context: Point::new(5, 0)..Point::new(7, 0),
 9684                    primary: None,
 9685                },
 9686                ExcerptRange {
 9687                    context: Point::new(9, 0)..Point::new(10, 4),
 9688                    primary: None,
 9689                },
 9690            ],
 9691            cx,
 9692        );
 9693        multibuffer.push_excerpts(
 9694            buffer_2.clone(),
 9695            [
 9696                ExcerptRange {
 9697                    context: Point::new(0, 0)..Point::new(3, 0),
 9698                    primary: None,
 9699                },
 9700                ExcerptRange {
 9701                    context: Point::new(5, 0)..Point::new(7, 0),
 9702                    primary: None,
 9703                },
 9704                ExcerptRange {
 9705                    context: Point::new(9, 0)..Point::new(10, 4),
 9706                    primary: None,
 9707                },
 9708            ],
 9709            cx,
 9710        );
 9711        multibuffer.push_excerpts(
 9712            buffer_3.clone(),
 9713            [
 9714                ExcerptRange {
 9715                    context: Point::new(0, 0)..Point::new(3, 0),
 9716                    primary: None,
 9717                },
 9718                ExcerptRange {
 9719                    context: Point::new(5, 0)..Point::new(7, 0),
 9720                    primary: None,
 9721                },
 9722                ExcerptRange {
 9723                    context: Point::new(9, 0)..Point::new(10, 4),
 9724                    primary: None,
 9725                },
 9726            ],
 9727            cx,
 9728        );
 9729        multibuffer
 9730    });
 9731
 9732    let fs = FakeFs::new(cx.executor());
 9733    fs.insert_tree(
 9734        "/a",
 9735        json!({
 9736            "main.rs": sample_text_1,
 9737            "other.rs": sample_text_2,
 9738            "lib.rs": sample_text_3,
 9739        }),
 9740    )
 9741    .await;
 9742    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 9743    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 9744    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9745    let multi_buffer_editor = cx.new_view(|cx| {
 9746        Editor::new(
 9747            EditorMode::Full,
 9748            multi_buffer,
 9749            Some(project.clone()),
 9750            true,
 9751            cx,
 9752        )
 9753    });
 9754    let multibuffer_item_id = workspace
 9755        .update(cx, |workspace, cx| {
 9756            assert!(
 9757                workspace.active_item(cx).is_none(),
 9758                "active item should be None before the first item is added"
 9759            );
 9760            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
 9761            let active_item = workspace
 9762                .active_item(cx)
 9763                .expect("should have an active item after adding the multi buffer");
 9764            assert!(
 9765                !active_item.is_singleton(cx),
 9766                "A multi buffer was expected to active after adding"
 9767            );
 9768            active_item.item_id()
 9769        })
 9770        .unwrap();
 9771    cx.executor().run_until_parked();
 9772
 9773    multi_buffer_editor.update(cx, |editor, cx| {
 9774        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 9775        editor.open_excerpts(&OpenExcerpts, cx);
 9776    });
 9777    cx.executor().run_until_parked();
 9778    let first_item_id = workspace
 9779        .update(cx, |workspace, cx| {
 9780            let active_item = workspace
 9781                .active_item(cx)
 9782                .expect("should have an active item after navigating into the 1st buffer");
 9783            let first_item_id = active_item.item_id();
 9784            assert_ne!(
 9785                first_item_id, multibuffer_item_id,
 9786                "Should navigate into the 1st buffer and activate it"
 9787            );
 9788            assert!(
 9789                active_item.is_singleton(cx),
 9790                "New active item should be a singleton buffer"
 9791            );
 9792            assert_eq!(
 9793                active_item
 9794                    .act_as::<Editor>(cx)
 9795                    .expect("should have navigated into an editor for the 1st buffer")
 9796                    .read(cx)
 9797                    .text(cx),
 9798                sample_text_1
 9799            );
 9800
 9801            workspace
 9802                .go_back(workspace.active_pane().downgrade(), cx)
 9803                .detach_and_log_err(cx);
 9804
 9805            first_item_id
 9806        })
 9807        .unwrap();
 9808    cx.executor().run_until_parked();
 9809    workspace
 9810        .update(cx, |workspace, cx| {
 9811            let active_item = workspace
 9812                .active_item(cx)
 9813                .expect("should have an active item after navigating back");
 9814            assert_eq!(
 9815                active_item.item_id(),
 9816                multibuffer_item_id,
 9817                "Should navigate back to the multi buffer"
 9818            );
 9819            assert!(!active_item.is_singleton(cx));
 9820        })
 9821        .unwrap();
 9822
 9823    multi_buffer_editor.update(cx, |editor, cx| {
 9824        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9825            s.select_ranges(Some(39..40))
 9826        });
 9827        editor.open_excerpts(&OpenExcerpts, cx);
 9828    });
 9829    cx.executor().run_until_parked();
 9830    let second_item_id = workspace
 9831        .update(cx, |workspace, cx| {
 9832            let active_item = workspace
 9833                .active_item(cx)
 9834                .expect("should have an active item after navigating into the 2nd buffer");
 9835            let second_item_id = active_item.item_id();
 9836            assert_ne!(
 9837                second_item_id, multibuffer_item_id,
 9838                "Should navigate away from the multibuffer"
 9839            );
 9840            assert_ne!(
 9841                second_item_id, first_item_id,
 9842                "Should navigate into the 2nd buffer and activate it"
 9843            );
 9844            assert!(
 9845                active_item.is_singleton(cx),
 9846                "New active item should be a singleton buffer"
 9847            );
 9848            assert_eq!(
 9849                active_item
 9850                    .act_as::<Editor>(cx)
 9851                    .expect("should have navigated into an editor")
 9852                    .read(cx)
 9853                    .text(cx),
 9854                sample_text_2
 9855            );
 9856
 9857            workspace
 9858                .go_back(workspace.active_pane().downgrade(), cx)
 9859                .detach_and_log_err(cx);
 9860
 9861            second_item_id
 9862        })
 9863        .unwrap();
 9864    cx.executor().run_until_parked();
 9865    workspace
 9866        .update(cx, |workspace, cx| {
 9867            let active_item = workspace
 9868                .active_item(cx)
 9869                .expect("should have an active item after navigating back from the 2nd buffer");
 9870            assert_eq!(
 9871                active_item.item_id(),
 9872                multibuffer_item_id,
 9873                "Should navigate back from the 2nd buffer to the multi buffer"
 9874            );
 9875            assert!(!active_item.is_singleton(cx));
 9876        })
 9877        .unwrap();
 9878
 9879    multi_buffer_editor.update(cx, |editor, cx| {
 9880        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9881            s.select_ranges(Some(60..70))
 9882        });
 9883        editor.open_excerpts(&OpenExcerpts, cx);
 9884    });
 9885    cx.executor().run_until_parked();
 9886    workspace
 9887        .update(cx, |workspace, cx| {
 9888            let active_item = workspace
 9889                .active_item(cx)
 9890                .expect("should have an active item after navigating into the 3rd buffer");
 9891            let third_item_id = active_item.item_id();
 9892            assert_ne!(
 9893                third_item_id, multibuffer_item_id,
 9894                "Should navigate into the 3rd buffer and activate it"
 9895            );
 9896            assert_ne!(third_item_id, first_item_id);
 9897            assert_ne!(third_item_id, second_item_id);
 9898            assert!(
 9899                active_item.is_singleton(cx),
 9900                "New active item should be a singleton buffer"
 9901            );
 9902            assert_eq!(
 9903                active_item
 9904                    .act_as::<Editor>(cx)
 9905                    .expect("should have navigated into an editor")
 9906                    .read(cx)
 9907                    .text(cx),
 9908                sample_text_3
 9909            );
 9910
 9911            workspace
 9912                .go_back(workspace.active_pane().downgrade(), cx)
 9913                .detach_and_log_err(cx);
 9914        })
 9915        .unwrap();
 9916    cx.executor().run_until_parked();
 9917    workspace
 9918        .update(cx, |workspace, cx| {
 9919            let active_item = workspace
 9920                .active_item(cx)
 9921                .expect("should have an active item after navigating back from the 3rd buffer");
 9922            assert_eq!(
 9923                active_item.item_id(),
 9924                multibuffer_item_id,
 9925                "Should navigate back from the 3rd buffer to the multi buffer"
 9926            );
 9927            assert!(!active_item.is_singleton(cx));
 9928        })
 9929        .unwrap();
 9930}
 9931
 9932#[gpui::test]
 9933async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 9934    init_test(cx, |_| {});
 9935
 9936    let mut cx = EditorTestContext::new(cx).await;
 9937
 9938    let diff_base = r#"
 9939        use some::mod;
 9940
 9941        const A: u32 = 42;
 9942
 9943        fn main() {
 9944            println!("hello");
 9945
 9946            println!("world");
 9947        }
 9948        "#
 9949    .unindent();
 9950
 9951    cx.set_state(
 9952        &r#"
 9953        use some::modified;
 9954
 9955        ˇ
 9956        fn main() {
 9957            println!("hello there");
 9958
 9959            println!("around the");
 9960            println!("world");
 9961        }
 9962        "#
 9963        .unindent(),
 9964    );
 9965
 9966    cx.set_diff_base(Some(&diff_base));
 9967    executor.run_until_parked();
 9968    let unexpanded_hunks = vec![
 9969        (
 9970            "use some::mod;\n".to_string(),
 9971            DiffHunkStatus::Modified,
 9972            DisplayRow(0)..DisplayRow(1),
 9973        ),
 9974        (
 9975            "const A: u32 = 42;\n".to_string(),
 9976            DiffHunkStatus::Removed,
 9977            DisplayRow(2)..DisplayRow(2),
 9978        ),
 9979        (
 9980            "    println!(\"hello\");\n".to_string(),
 9981            DiffHunkStatus::Modified,
 9982            DisplayRow(4)..DisplayRow(5),
 9983        ),
 9984        (
 9985            "".to_string(),
 9986            DiffHunkStatus::Added,
 9987            DisplayRow(6)..DisplayRow(7),
 9988        ),
 9989    ];
 9990    cx.update_editor(|editor, cx| {
 9991        let snapshot = editor.snapshot(cx);
 9992        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9993        assert_eq!(all_hunks, unexpanded_hunks);
 9994    });
 9995
 9996    cx.update_editor(|editor, cx| {
 9997        for _ in 0..4 {
 9998            editor.go_to_hunk(&GoToHunk, cx);
 9999            editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10000        }
10001    });
10002    executor.run_until_parked();
10003    cx.assert_editor_state(
10004        &r#"
10005        use some::modified;
10006
10007        ˇ
10008        fn main() {
10009            println!("hello there");
10010
10011            println!("around the");
10012            println!("world");
10013        }
10014        "#
10015        .unindent(),
10016    );
10017    cx.update_editor(|editor, cx| {
10018        let snapshot = editor.snapshot(cx);
10019        let all_hunks = editor_hunks(editor, &snapshot, cx);
10020        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10021        assert_eq!(
10022            expanded_hunks_background_highlights(editor, cx),
10023            vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10024            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10025        );
10026        assert_eq!(
10027            all_hunks,
10028            vec![
10029                ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10030                ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10031                ("    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10032                ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10033            ],
10034            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10035            (from modified and removed hunks)"
10036        );
10037        assert_eq!(
10038            all_hunks, all_expanded_hunks,
10039            "Editor hunks should not change and all be expanded"
10040        );
10041    });
10042
10043    cx.update_editor(|editor, cx| {
10044        editor.cancel(&Cancel, cx);
10045
10046        let snapshot = editor.snapshot(cx);
10047        let all_hunks = editor_hunks(editor, &snapshot, cx);
10048        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10049        assert_eq!(
10050            expanded_hunks_background_highlights(editor, cx),
10051            Vec::new(),
10052            "After cancelling in editor, no git highlights should be left"
10053        );
10054        assert_eq!(
10055            all_expanded_hunks,
10056            Vec::new(),
10057            "After cancelling in editor, no hunks should be expanded"
10058        );
10059        assert_eq!(
10060            all_hunks, unexpanded_hunks,
10061            "After cancelling in editor, regular hunks' coordinates should get back to normal"
10062        );
10063    });
10064}
10065
10066#[gpui::test]
10067async fn test_toggled_diff_base_change(
10068    executor: BackgroundExecutor,
10069    cx: &mut gpui::TestAppContext,
10070) {
10071    init_test(cx, |_| {});
10072
10073    let mut cx = EditorTestContext::new(cx).await;
10074
10075    let diff_base = r#"
10076        use some::mod1;
10077        use some::mod2;
10078
10079        const A: u32 = 42;
10080        const B: u32 = 42;
10081        const C: u32 = 42;
10082
10083        fn main(ˇ) {
10084            println!("hello");
10085
10086            println!("world");
10087        }
10088        "#
10089    .unindent();
10090
10091    cx.set_state(
10092        &r#"
10093        use some::mod2;
10094
10095        const A: u32 = 42;
10096        const C: u32 = 42;
10097
10098        fn main(ˇ) {
10099            //println!("hello");
10100
10101            println!("world");
10102            //
10103            //
10104        }
10105        "#
10106        .unindent(),
10107    );
10108
10109    cx.set_diff_base(Some(&diff_base));
10110    executor.run_until_parked();
10111    cx.update_editor(|editor, cx| {
10112        let snapshot = editor.snapshot(cx);
10113        let all_hunks = editor_hunks(editor, &snapshot, cx);
10114        assert_eq!(
10115            all_hunks,
10116            vec![
10117                (
10118                    "use some::mod1;\n".to_string(),
10119                    DiffHunkStatus::Removed,
10120                    DisplayRow(0)..DisplayRow(0)
10121                ),
10122                (
10123                    "const B: u32 = 42;\n".to_string(),
10124                    DiffHunkStatus::Removed,
10125                    DisplayRow(3)..DisplayRow(3)
10126                ),
10127                (
10128                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10129                    DiffHunkStatus::Modified,
10130                    DisplayRow(5)..DisplayRow(7)
10131                ),
10132                (
10133                    "".to_string(),
10134                    DiffHunkStatus::Added,
10135                    DisplayRow(9)..DisplayRow(11)
10136                ),
10137            ]
10138        );
10139    });
10140
10141    cx.update_editor(|editor, cx| {
10142        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10143    });
10144    executor.run_until_parked();
10145    cx.assert_editor_state(
10146        &r#"
10147        use some::mod2;
10148
10149        const A: u32 = 42;
10150        const C: u32 = 42;
10151
10152        fn main(ˇ) {
10153            //println!("hello");
10154
10155            println!("world");
10156            //
10157            //
10158        }
10159        "#
10160        .unindent(),
10161    );
10162    cx.update_editor(|editor, cx| {
10163        let snapshot = editor.snapshot(cx);
10164        let all_hunks = editor_hunks(editor, &snapshot, cx);
10165        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10166        assert_eq!(
10167            expanded_hunks_background_highlights(editor, cx),
10168            vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10169            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10170        );
10171        assert_eq!(
10172            all_hunks,
10173            vec![
10174                ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10175                ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10176                ("fn main(ˇ) {\n    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10177                ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10178            ],
10179            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10180            (from modified and removed hunks)"
10181        );
10182        assert_eq!(
10183            all_hunks, all_expanded_hunks,
10184            "Editor hunks should not change and all be expanded"
10185        );
10186    });
10187
10188    cx.set_diff_base(Some("new diff base!"));
10189    executor.run_until_parked();
10190
10191    cx.update_editor(|editor, cx| {
10192        let snapshot = editor.snapshot(cx);
10193        let all_hunks = editor_hunks(editor, &snapshot, cx);
10194        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10195        assert_eq!(
10196            expanded_hunks_background_highlights(editor, cx),
10197            Vec::new(),
10198            "After diff base is changed, old git highlights should be removed"
10199        );
10200        assert_eq!(
10201            all_expanded_hunks,
10202            Vec::new(),
10203            "After diff base is changed, old git hunk expansions should be removed"
10204        );
10205        assert_eq!(
10206            all_hunks,
10207            vec![(
10208                "new diff base!".to_string(),
10209                DiffHunkStatus::Modified,
10210                DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10211            )],
10212            "After diff base is changed, hunks should update"
10213        );
10214    });
10215}
10216
10217#[gpui::test]
10218async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10219    init_test(cx, |_| {});
10220
10221    let mut cx = EditorTestContext::new(cx).await;
10222
10223    let diff_base = r#"
10224        use some::mod1;
10225        use some::mod2;
10226
10227        const A: u32 = 42;
10228        const B: u32 = 42;
10229        const C: u32 = 42;
10230
10231        fn main(ˇ) {
10232            println!("hello");
10233
10234            println!("world");
10235        }
10236
10237        fn another() {
10238            println!("another");
10239        }
10240
10241        fn another2() {
10242            println!("another2");
10243        }
10244        "#
10245    .unindent();
10246
10247    cx.set_state(
10248        &r#"
10249        «use some::mod2;
10250
10251        const A: u32 = 42;
10252        const C: u32 = 42;
10253
10254        fn main() {
10255            //println!("hello");
10256
10257            println!("world");
10258            //
10259            //ˇ»
10260        }
10261
10262        fn another() {
10263            println!("another");
10264            println!("another");
10265        }
10266
10267            println!("another2");
10268        }
10269        "#
10270        .unindent(),
10271    );
10272
10273    cx.set_diff_base(Some(&diff_base));
10274    executor.run_until_parked();
10275    cx.update_editor(|editor, cx| {
10276        let snapshot = editor.snapshot(cx);
10277        let all_hunks = editor_hunks(editor, &snapshot, cx);
10278        assert_eq!(
10279            all_hunks,
10280            vec![
10281                (
10282                    "use some::mod1;\n".to_string(),
10283                    DiffHunkStatus::Removed,
10284                    DisplayRow(0)..DisplayRow(0)
10285                ),
10286                (
10287                    "const B: u32 = 42;\n".to_string(),
10288                    DiffHunkStatus::Removed,
10289                    DisplayRow(3)..DisplayRow(3)
10290                ),
10291                (
10292                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10293                    DiffHunkStatus::Modified,
10294                    DisplayRow(5)..DisplayRow(7)
10295                ),
10296                (
10297                    "".to_string(),
10298                    DiffHunkStatus::Added,
10299                    DisplayRow(9)..DisplayRow(11)
10300                ),
10301                (
10302                    "".to_string(),
10303                    DiffHunkStatus::Added,
10304                    DisplayRow(15)..DisplayRow(16)
10305                ),
10306                (
10307                    "fn another2() {\n".to_string(),
10308                    DiffHunkStatus::Removed,
10309                    DisplayRow(18)..DisplayRow(18)
10310                ),
10311            ]
10312        );
10313    });
10314
10315    cx.update_editor(|editor, cx| {
10316        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10317    });
10318    executor.run_until_parked();
10319    cx.assert_editor_state(
10320        &r#"
10321        «use some::mod2;
10322
10323        const A: u32 = 42;
10324        const C: u32 = 42;
10325
10326        fn main() {
10327            //println!("hello");
10328
10329            println!("world");
10330            //
10331            //ˇ»
10332        }
10333
10334        fn another() {
10335            println!("another");
10336            println!("another");
10337        }
10338
10339            println!("another2");
10340        }
10341        "#
10342        .unindent(),
10343    );
10344    cx.update_editor(|editor, cx| {
10345        let snapshot = editor.snapshot(cx);
10346        let all_hunks = editor_hunks(editor, &snapshot, cx);
10347        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10348        assert_eq!(
10349            expanded_hunks_background_highlights(editor, cx),
10350            vec![
10351                DisplayRow(9)..=DisplayRow(10),
10352                DisplayRow(13)..=DisplayRow(14),
10353                DisplayRow(19)..=DisplayRow(19)
10354            ]
10355        );
10356        assert_eq!(
10357            all_hunks,
10358            vec![
10359                (
10360                    "use some::mod1;\n".to_string(),
10361                    DiffHunkStatus::Removed,
10362                    DisplayRow(1)..DisplayRow(1)
10363                ),
10364                (
10365                    "const B: u32 = 42;\n".to_string(),
10366                    DiffHunkStatus::Removed,
10367                    DisplayRow(5)..DisplayRow(5)
10368                ),
10369                (
10370                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10371                    DiffHunkStatus::Modified,
10372                    DisplayRow(9)..DisplayRow(11)
10373                ),
10374                (
10375                    "".to_string(),
10376                    DiffHunkStatus::Added,
10377                    DisplayRow(13)..DisplayRow(15)
10378                ),
10379                (
10380                    "".to_string(),
10381                    DiffHunkStatus::Added,
10382                    DisplayRow(19)..DisplayRow(20)
10383                ),
10384                (
10385                    "fn another2() {\n".to_string(),
10386                    DiffHunkStatus::Removed,
10387                    DisplayRow(23)..DisplayRow(23)
10388                ),
10389            ],
10390        );
10391        assert_eq!(all_hunks, all_expanded_hunks);
10392    });
10393
10394    cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10395    cx.executor().run_until_parked();
10396    cx.assert_editor_state(
10397        &r#"
10398        «use some::mod2;
10399
10400        const A: u32 = 42;
10401        const C: u32 = 42;
10402
10403        fn main() {
10404            //println!("hello");
10405
10406            println!("world");
10407            //
10408            //ˇ»
10409        }
10410
10411        fn another() {
10412            println!("another");
10413            println!("another");
10414        }
10415
10416            println!("another2");
10417        }
10418        "#
10419        .unindent(),
10420    );
10421    cx.update_editor(|editor, cx| {
10422        let snapshot = editor.snapshot(cx);
10423        let all_hunks = editor_hunks(editor, &snapshot, cx);
10424        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10425        assert_eq!(
10426            expanded_hunks_background_highlights(editor, cx),
10427            vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10428            "Only one hunk is left not folded, its highlight should be visible"
10429        );
10430        assert_eq!(
10431            all_hunks,
10432            vec![
10433                (
10434                    "use some::mod1;\n".to_string(),
10435                    DiffHunkStatus::Removed,
10436                    DisplayRow(0)..DisplayRow(0)
10437                ),
10438                (
10439                    "const B: u32 = 42;\n".to_string(),
10440                    DiffHunkStatus::Removed,
10441                    DisplayRow(0)..DisplayRow(0)
10442                ),
10443                (
10444                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10445                    DiffHunkStatus::Modified,
10446                    DisplayRow(0)..DisplayRow(0)
10447                ),
10448                (
10449                    "".to_string(),
10450                    DiffHunkStatus::Added,
10451                    DisplayRow(0)..DisplayRow(1)
10452                ),
10453                (
10454                    "".to_string(),
10455                    DiffHunkStatus::Added,
10456                    DisplayRow(5)..DisplayRow(6)
10457                ),
10458                (
10459                    "fn another2() {\n".to_string(),
10460                    DiffHunkStatus::Removed,
10461                    DisplayRow(9)..DisplayRow(9)
10462                ),
10463            ],
10464            "Hunk list should still return shifted folded hunks"
10465        );
10466        assert_eq!(
10467            all_expanded_hunks,
10468            vec![
10469                (
10470                    "".to_string(),
10471                    DiffHunkStatus::Added,
10472                    DisplayRow(5)..DisplayRow(6)
10473                ),
10474                (
10475                    "fn another2() {\n".to_string(),
10476                    DiffHunkStatus::Removed,
10477                    DisplayRow(9)..DisplayRow(9)
10478                ),
10479            ],
10480            "Only non-folded hunks should be left expanded"
10481        );
10482    });
10483
10484    cx.update_editor(|editor, cx| {
10485        editor.select_all(&SelectAll, cx);
10486        editor.unfold_lines(&UnfoldLines, cx);
10487    });
10488    cx.executor().run_until_parked();
10489    cx.assert_editor_state(
10490        &r#"
10491        «use some::mod2;
10492
10493        const A: u32 = 42;
10494        const C: u32 = 42;
10495
10496        fn main() {
10497            //println!("hello");
10498
10499            println!("world");
10500            //
10501            //
10502        }
10503
10504        fn another() {
10505            println!("another");
10506            println!("another");
10507        }
10508
10509            println!("another2");
10510        }
10511        ˇ»"#
10512        .unindent(),
10513    );
10514    cx.update_editor(|editor, cx| {
10515        let snapshot = editor.snapshot(cx);
10516        let all_hunks = editor_hunks(editor, &snapshot, cx);
10517        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10518        assert_eq!(
10519            expanded_hunks_background_highlights(editor, cx),
10520            vec![
10521                DisplayRow(9)..=DisplayRow(10),
10522                DisplayRow(13)..=DisplayRow(14),
10523                DisplayRow(19)..=DisplayRow(19)
10524            ],
10525            "After unfolding, all hunk diffs should be visible again"
10526        );
10527        assert_eq!(
10528            all_hunks,
10529            vec![
10530                (
10531                    "use some::mod1;\n".to_string(),
10532                    DiffHunkStatus::Removed,
10533                    DisplayRow(1)..DisplayRow(1)
10534                ),
10535                (
10536                    "const B: u32 = 42;\n".to_string(),
10537                    DiffHunkStatus::Removed,
10538                    DisplayRow(5)..DisplayRow(5)
10539                ),
10540                (
10541                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10542                    DiffHunkStatus::Modified,
10543                    DisplayRow(9)..DisplayRow(11)
10544                ),
10545                (
10546                    "".to_string(),
10547                    DiffHunkStatus::Added,
10548                    DisplayRow(13)..DisplayRow(15)
10549                ),
10550                (
10551                    "".to_string(),
10552                    DiffHunkStatus::Added,
10553                    DisplayRow(19)..DisplayRow(20)
10554                ),
10555                (
10556                    "fn another2() {\n".to_string(),
10557                    DiffHunkStatus::Removed,
10558                    DisplayRow(23)..DisplayRow(23)
10559                ),
10560            ],
10561        );
10562        assert_eq!(all_hunks, all_expanded_hunks);
10563    });
10564}
10565
10566#[gpui::test]
10567async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10568    init_test(cx, |_| {});
10569
10570    let cols = 4;
10571    let rows = 10;
10572    let sample_text_1 = sample_text(rows, cols, 'a');
10573    assert_eq!(
10574        sample_text_1,
10575        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10576    );
10577    let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10578    let sample_text_2 = sample_text(rows, cols, 'l');
10579    assert_eq!(
10580        sample_text_2,
10581        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10582    );
10583    let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10584    let sample_text_3 = sample_text(rows, cols, 'v');
10585    assert_eq!(
10586        sample_text_3,
10587        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10588    );
10589    let modified_sample_text_3 =
10590        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10591    let buffer_1 = cx.new_model(|cx| {
10592        let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10593        buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10594        buffer
10595    });
10596    let buffer_2 = cx.new_model(|cx| {
10597        let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10598        buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10599        buffer
10600    });
10601    let buffer_3 = cx.new_model(|cx| {
10602        let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10603        buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10604        buffer
10605    });
10606
10607    let multi_buffer = cx.new_model(|cx| {
10608        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10609        multibuffer.push_excerpts(
10610            buffer_1.clone(),
10611            [
10612                ExcerptRange {
10613                    context: Point::new(0, 0)..Point::new(3, 0),
10614                    primary: None,
10615                },
10616                ExcerptRange {
10617                    context: Point::new(5, 0)..Point::new(7, 0),
10618                    primary: None,
10619                },
10620                ExcerptRange {
10621                    context: Point::new(9, 0)..Point::new(10, 4),
10622                    primary: None,
10623                },
10624            ],
10625            cx,
10626        );
10627        multibuffer.push_excerpts(
10628            buffer_2.clone(),
10629            [
10630                ExcerptRange {
10631                    context: Point::new(0, 0)..Point::new(3, 0),
10632                    primary: None,
10633                },
10634                ExcerptRange {
10635                    context: Point::new(5, 0)..Point::new(7, 0),
10636                    primary: None,
10637                },
10638                ExcerptRange {
10639                    context: Point::new(9, 0)..Point::new(10, 4),
10640                    primary: None,
10641                },
10642            ],
10643            cx,
10644        );
10645        multibuffer.push_excerpts(
10646            buffer_3.clone(),
10647            [
10648                ExcerptRange {
10649                    context: Point::new(0, 0)..Point::new(3, 0),
10650                    primary: None,
10651                },
10652                ExcerptRange {
10653                    context: Point::new(5, 0)..Point::new(7, 0),
10654                    primary: None,
10655                },
10656                ExcerptRange {
10657                    context: Point::new(9, 0)..Point::new(10, 4),
10658                    primary: None,
10659                },
10660            ],
10661            cx,
10662        );
10663        multibuffer
10664    });
10665
10666    let fs = FakeFs::new(cx.executor());
10667    fs.insert_tree(
10668        "/a",
10669        json!({
10670            "main.rs": modified_sample_text_1,
10671            "other.rs": modified_sample_text_2,
10672            "lib.rs": modified_sample_text_3,
10673        }),
10674    )
10675    .await;
10676
10677    let project = Project::test(fs, ["/a".as_ref()], cx).await;
10678    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10679    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10680    let multi_buffer_editor = cx.new_view(|cx| {
10681        Editor::new(
10682            EditorMode::Full,
10683            multi_buffer,
10684            Some(project.clone()),
10685            true,
10686            cx,
10687        )
10688    });
10689    cx.executor().run_until_parked();
10690
10691    let expected_all_hunks = vec![
10692        (
10693            "bbbb\n".to_string(),
10694            DiffHunkStatus::Removed,
10695            DisplayRow(4)..DisplayRow(4),
10696        ),
10697        (
10698            "nnnn\n".to_string(),
10699            DiffHunkStatus::Modified,
10700            DisplayRow(21)..DisplayRow(22),
10701        ),
10702        (
10703            "".to_string(),
10704            DiffHunkStatus::Added,
10705            DisplayRow(41)..DisplayRow(42),
10706        ),
10707    ];
10708    let expected_all_hunks_shifted = vec![
10709        (
10710            "bbbb\n".to_string(),
10711            DiffHunkStatus::Removed,
10712            DisplayRow(5)..DisplayRow(5),
10713        ),
10714        (
10715            "nnnn\n".to_string(),
10716            DiffHunkStatus::Modified,
10717            DisplayRow(23)..DisplayRow(24),
10718        ),
10719        (
10720            "".to_string(),
10721            DiffHunkStatus::Added,
10722            DisplayRow(43)..DisplayRow(44),
10723        ),
10724    ];
10725
10726    multi_buffer_editor.update(cx, |editor, cx| {
10727        let snapshot = editor.snapshot(cx);
10728        let all_hunks = editor_hunks(editor, &snapshot, cx);
10729        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10730        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10731        assert_eq!(all_hunks, expected_all_hunks);
10732        assert_eq!(all_expanded_hunks, Vec::new());
10733    });
10734
10735    multi_buffer_editor.update(cx, |editor, cx| {
10736        editor.select_all(&SelectAll, cx);
10737        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10738    });
10739    cx.executor().run_until_parked();
10740    multi_buffer_editor.update(cx, |editor, cx| {
10741        let snapshot = editor.snapshot(cx);
10742        let all_hunks = editor_hunks(editor, &snapshot, cx);
10743        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10744        assert_eq!(
10745            expanded_hunks_background_highlights(editor, cx),
10746            vec![
10747                DisplayRow(23)..=DisplayRow(23),
10748                DisplayRow(43)..=DisplayRow(43)
10749            ],
10750        );
10751        assert_eq!(all_hunks, expected_all_hunks_shifted);
10752        assert_eq!(all_hunks, all_expanded_hunks);
10753    });
10754
10755    multi_buffer_editor.update(cx, |editor, cx| {
10756        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10757    });
10758    cx.executor().run_until_parked();
10759    multi_buffer_editor.update(cx, |editor, cx| {
10760        let snapshot = editor.snapshot(cx);
10761        let all_hunks = editor_hunks(editor, &snapshot, cx);
10762        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10763        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10764        assert_eq!(all_hunks, expected_all_hunks);
10765        assert_eq!(all_expanded_hunks, Vec::new());
10766    });
10767
10768    multi_buffer_editor.update(cx, |editor, cx| {
10769        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10770    });
10771    cx.executor().run_until_parked();
10772    multi_buffer_editor.update(cx, |editor, cx| {
10773        let snapshot = editor.snapshot(cx);
10774        let all_hunks = editor_hunks(editor, &snapshot, cx);
10775        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10776        assert_eq!(
10777            expanded_hunks_background_highlights(editor, cx),
10778            vec![
10779                DisplayRow(23)..=DisplayRow(23),
10780                DisplayRow(43)..=DisplayRow(43)
10781            ],
10782        );
10783        assert_eq!(all_hunks, expected_all_hunks_shifted);
10784        assert_eq!(all_hunks, all_expanded_hunks);
10785    });
10786
10787    multi_buffer_editor.update(cx, |editor, cx| {
10788        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10789    });
10790    cx.executor().run_until_parked();
10791    multi_buffer_editor.update(cx, |editor, cx| {
10792        let snapshot = editor.snapshot(cx);
10793        let all_hunks = editor_hunks(editor, &snapshot, cx);
10794        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10795        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10796        assert_eq!(all_hunks, expected_all_hunks);
10797        assert_eq!(all_expanded_hunks, Vec::new());
10798    });
10799}
10800
10801#[gpui::test]
10802async fn test_edits_around_toggled_additions(
10803    executor: BackgroundExecutor,
10804    cx: &mut gpui::TestAppContext,
10805) {
10806    init_test(cx, |_| {});
10807
10808    let mut cx = EditorTestContext::new(cx).await;
10809
10810    let diff_base = r#"
10811        use some::mod1;
10812        use some::mod2;
10813
10814        const A: u32 = 42;
10815
10816        fn main() {
10817            println!("hello");
10818
10819            println!("world");
10820        }
10821        "#
10822    .unindent();
10823    executor.run_until_parked();
10824    cx.set_state(
10825        &r#"
10826        use some::mod1;
10827        use some::mod2;
10828
10829        const A: u32 = 42;
10830        const B: u32 = 42;
10831        const C: u32 = 42;
10832        ˇ
10833
10834        fn main() {
10835            println!("hello");
10836
10837            println!("world");
10838        }
10839        "#
10840        .unindent(),
10841    );
10842
10843    cx.set_diff_base(Some(&diff_base));
10844    executor.run_until_parked();
10845    cx.update_editor(|editor, cx| {
10846        let snapshot = editor.snapshot(cx);
10847        let all_hunks = editor_hunks(editor, &snapshot, cx);
10848        assert_eq!(
10849            all_hunks,
10850            vec![(
10851                "".to_string(),
10852                DiffHunkStatus::Added,
10853                DisplayRow(4)..DisplayRow(7)
10854            )]
10855        );
10856    });
10857    cx.update_editor(|editor, cx| {
10858        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10859    });
10860    executor.run_until_parked();
10861    cx.assert_editor_state(
10862        &r#"
10863        use some::mod1;
10864        use some::mod2;
10865
10866        const A: u32 = 42;
10867        const B: u32 = 42;
10868        const C: u32 = 42;
10869        ˇ
10870
10871        fn main() {
10872            println!("hello");
10873
10874            println!("world");
10875        }
10876        "#
10877        .unindent(),
10878    );
10879    cx.update_editor(|editor, cx| {
10880        let snapshot = editor.snapshot(cx);
10881        let all_hunks = editor_hunks(editor, &snapshot, cx);
10882        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10883        assert_eq!(
10884            all_hunks,
10885            vec![(
10886                "".to_string(),
10887                DiffHunkStatus::Added,
10888                DisplayRow(4)..DisplayRow(7)
10889            )]
10890        );
10891        assert_eq!(
10892            expanded_hunks_background_highlights(editor, cx),
10893            vec![DisplayRow(4)..=DisplayRow(6)]
10894        );
10895        assert_eq!(all_hunks, all_expanded_hunks);
10896    });
10897
10898    cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10899    executor.run_until_parked();
10900    cx.assert_editor_state(
10901        &r#"
10902        use some::mod1;
10903        use some::mod2;
10904
10905        const A: u32 = 42;
10906        const B: u32 = 42;
10907        const C: u32 = 42;
10908        const D: u32 = 42;
10909        ˇ
10910
10911        fn main() {
10912            println!("hello");
10913
10914            println!("world");
10915        }
10916        "#
10917        .unindent(),
10918    );
10919    cx.update_editor(|editor, cx| {
10920        let snapshot = editor.snapshot(cx);
10921        let all_hunks = editor_hunks(editor, &snapshot, cx);
10922        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10923        assert_eq!(
10924            all_hunks,
10925            vec![(
10926                "".to_string(),
10927                DiffHunkStatus::Added,
10928                DisplayRow(4)..DisplayRow(8)
10929            )]
10930        );
10931        assert_eq!(
10932            expanded_hunks_background_highlights(editor, cx),
10933            vec![DisplayRow(4)..=DisplayRow(6)],
10934            "Edited hunk should have one more line added"
10935        );
10936        assert_eq!(
10937            all_hunks, all_expanded_hunks,
10938            "Expanded hunk should also grow with the addition"
10939        );
10940    });
10941
10942    cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10943    executor.run_until_parked();
10944    cx.assert_editor_state(
10945        &r#"
10946        use some::mod1;
10947        use some::mod2;
10948
10949        const A: u32 = 42;
10950        const B: u32 = 42;
10951        const C: u32 = 42;
10952        const D: u32 = 42;
10953        const E: u32 = 42;
10954        ˇ
10955
10956        fn main() {
10957            println!("hello");
10958
10959            println!("world");
10960        }
10961        "#
10962        .unindent(),
10963    );
10964    cx.update_editor(|editor, cx| {
10965        let snapshot = editor.snapshot(cx);
10966        let all_hunks = editor_hunks(editor, &snapshot, cx);
10967        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10968        assert_eq!(
10969            all_hunks,
10970            vec![(
10971                "".to_string(),
10972                DiffHunkStatus::Added,
10973                DisplayRow(4)..DisplayRow(9)
10974            )]
10975        );
10976        assert_eq!(
10977            expanded_hunks_background_highlights(editor, cx),
10978            vec![DisplayRow(4)..=DisplayRow(6)],
10979            "Edited hunk should have one more line added"
10980        );
10981        assert_eq!(all_hunks, all_expanded_hunks);
10982    });
10983
10984    cx.update_editor(|editor, cx| {
10985        editor.move_up(&MoveUp, cx);
10986        editor.delete_line(&DeleteLine, cx);
10987    });
10988    executor.run_until_parked();
10989    cx.assert_editor_state(
10990        &r#"
10991        use some::mod1;
10992        use some::mod2;
10993
10994        const A: u32 = 42;
10995        const B: u32 = 42;
10996        const C: u32 = 42;
10997        const D: u32 = 42;
10998        ˇ
10999
11000        fn main() {
11001            println!("hello");
11002
11003            println!("world");
11004        }
11005        "#
11006        .unindent(),
11007    );
11008    cx.update_editor(|editor, cx| {
11009        let snapshot = editor.snapshot(cx);
11010        let all_hunks = editor_hunks(editor, &snapshot, cx);
11011        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11012        assert_eq!(
11013            all_hunks,
11014            vec![(
11015                "".to_string(),
11016                DiffHunkStatus::Added,
11017                DisplayRow(4)..DisplayRow(8)
11018            )]
11019        );
11020        assert_eq!(
11021            expanded_hunks_background_highlights(editor, cx),
11022            vec![DisplayRow(4)..=DisplayRow(6)],
11023            "Deleting a line should shrint the hunk"
11024        );
11025        assert_eq!(
11026            all_hunks, all_expanded_hunks,
11027            "Expanded hunk should also shrink with the addition"
11028        );
11029    });
11030
11031    cx.update_editor(|editor, cx| {
11032        editor.move_up(&MoveUp, cx);
11033        editor.delete_line(&DeleteLine, cx);
11034        editor.move_up(&MoveUp, cx);
11035        editor.delete_line(&DeleteLine, cx);
11036        editor.move_up(&MoveUp, cx);
11037        editor.delete_line(&DeleteLine, cx);
11038    });
11039    executor.run_until_parked();
11040    cx.assert_editor_state(
11041        &r#"
11042        use some::mod1;
11043        use some::mod2;
11044
11045        const A: u32 = 42;
11046        ˇ
11047
11048        fn main() {
11049            println!("hello");
11050
11051            println!("world");
11052        }
11053        "#
11054        .unindent(),
11055    );
11056    cx.update_editor(|editor, cx| {
11057        let snapshot = editor.snapshot(cx);
11058        let all_hunks = editor_hunks(editor, &snapshot, cx);
11059        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11060        assert_eq!(
11061            all_hunks,
11062            vec![(
11063                "".to_string(),
11064                DiffHunkStatus::Added,
11065                DisplayRow(5)..DisplayRow(6)
11066            )]
11067        );
11068        assert_eq!(
11069            expanded_hunks_background_highlights(editor, cx),
11070            vec![DisplayRow(5)..=DisplayRow(5)]
11071        );
11072        assert_eq!(all_hunks, all_expanded_hunks);
11073    });
11074
11075    cx.update_editor(|editor, cx| {
11076        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11077        editor.delete_line(&DeleteLine, cx);
11078    });
11079    executor.run_until_parked();
11080    cx.assert_editor_state(
11081        &r#"
11082        ˇ
11083
11084        fn main() {
11085            println!("hello");
11086
11087            println!("world");
11088        }
11089        "#
11090        .unindent(),
11091    );
11092    cx.update_editor(|editor, cx| {
11093        let snapshot = editor.snapshot(cx);
11094        let all_hunks = editor_hunks(editor, &snapshot, cx);
11095        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11096        assert_eq!(
11097            all_hunks,
11098            vec![
11099                (
11100                    "use some::mod1;\nuse some::mod2;\n".to_string(),
11101                    DiffHunkStatus::Removed,
11102                    DisplayRow(0)..DisplayRow(0)
11103                ),
11104                (
11105                    "const A: u32 = 42;\n".to_string(),
11106                    DiffHunkStatus::Removed,
11107                    DisplayRow(2)..DisplayRow(2)
11108                )
11109            ]
11110        );
11111        assert_eq!(
11112            expanded_hunks_background_highlights(editor, cx),
11113            Vec::new(),
11114            "Should close all stale expanded addition hunks"
11115        );
11116        assert_eq!(
11117            all_expanded_hunks,
11118            vec![(
11119                "const A: u32 = 42;\n".to_string(),
11120                DiffHunkStatus::Removed,
11121                DisplayRow(2)..DisplayRow(2)
11122            )],
11123            "Should open hunks that were adjacent to the stale addition one"
11124        );
11125    });
11126}
11127
11128#[gpui::test]
11129async fn test_edits_around_toggled_deletions(
11130    executor: BackgroundExecutor,
11131    cx: &mut gpui::TestAppContext,
11132) {
11133    init_test(cx, |_| {});
11134
11135    let mut cx = EditorTestContext::new(cx).await;
11136
11137    let diff_base = r#"
11138        use some::mod1;
11139        use some::mod2;
11140
11141        const A: u32 = 42;
11142        const B: u32 = 42;
11143        const C: u32 = 42;
11144
11145
11146        fn main() {
11147            println!("hello");
11148
11149            println!("world");
11150        }
11151        "#
11152    .unindent();
11153    executor.run_until_parked();
11154    cx.set_state(
11155        &r#"
11156        use some::mod1;
11157        use some::mod2;
11158
11159        ˇconst B: u32 = 42;
11160        const C: u32 = 42;
11161
11162
11163        fn main() {
11164            println!("hello");
11165
11166            println!("world");
11167        }
11168        "#
11169        .unindent(),
11170    );
11171
11172    cx.set_diff_base(Some(&diff_base));
11173    executor.run_until_parked();
11174    cx.update_editor(|editor, cx| {
11175        let snapshot = editor.snapshot(cx);
11176        let all_hunks = editor_hunks(editor, &snapshot, cx);
11177        assert_eq!(
11178            all_hunks,
11179            vec![(
11180                "const A: u32 = 42;\n".to_string(),
11181                DiffHunkStatus::Removed,
11182                DisplayRow(3)..DisplayRow(3)
11183            )]
11184        );
11185    });
11186    cx.update_editor(|editor, cx| {
11187        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11188    });
11189    executor.run_until_parked();
11190    cx.assert_editor_state(
11191        &r#"
11192        use some::mod1;
11193        use some::mod2;
11194
11195        ˇconst B: u32 = 42;
11196        const C: u32 = 42;
11197
11198
11199        fn main() {
11200            println!("hello");
11201
11202            println!("world");
11203        }
11204        "#
11205        .unindent(),
11206    );
11207    cx.update_editor(|editor, cx| {
11208        let snapshot = editor.snapshot(cx);
11209        let all_hunks = editor_hunks(editor, &snapshot, cx);
11210        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11211        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11212        assert_eq!(
11213            all_hunks,
11214            vec![(
11215                "const A: u32 = 42;\n".to_string(),
11216                DiffHunkStatus::Removed,
11217                DisplayRow(4)..DisplayRow(4)
11218            )]
11219        );
11220        assert_eq!(all_hunks, all_expanded_hunks);
11221    });
11222
11223    cx.update_editor(|editor, cx| {
11224        editor.delete_line(&DeleteLine, cx);
11225    });
11226    executor.run_until_parked();
11227    cx.assert_editor_state(
11228        &r#"
11229        use some::mod1;
11230        use some::mod2;
11231
11232        ˇconst C: u32 = 42;
11233
11234
11235        fn main() {
11236            println!("hello");
11237
11238            println!("world");
11239        }
11240        "#
11241        .unindent(),
11242    );
11243    cx.update_editor(|editor, cx| {
11244        let snapshot = editor.snapshot(cx);
11245        let all_hunks = editor_hunks(editor, &snapshot, cx);
11246        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11247        assert_eq!(
11248            expanded_hunks_background_highlights(editor, cx),
11249            Vec::new(),
11250            "Deleted hunks do not highlight current editor's background"
11251        );
11252        assert_eq!(
11253            all_hunks,
11254            vec![(
11255                "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11256                DiffHunkStatus::Removed,
11257                DisplayRow(5)..DisplayRow(5)
11258            )]
11259        );
11260        assert_eq!(all_hunks, all_expanded_hunks);
11261    });
11262
11263    cx.update_editor(|editor, cx| {
11264        editor.delete_line(&DeleteLine, cx);
11265    });
11266    executor.run_until_parked();
11267    cx.assert_editor_state(
11268        &r#"
11269        use some::mod1;
11270        use some::mod2;
11271
11272        ˇ
11273
11274        fn main() {
11275            println!("hello");
11276
11277            println!("world");
11278        }
11279        "#
11280        .unindent(),
11281    );
11282    cx.update_editor(|editor, cx| {
11283        let snapshot = editor.snapshot(cx);
11284        let all_hunks = editor_hunks(editor, &snapshot, cx);
11285        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11286        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11287        assert_eq!(
11288            all_hunks,
11289            vec![(
11290                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11291                DiffHunkStatus::Removed,
11292                DisplayRow(6)..DisplayRow(6)
11293            )]
11294        );
11295        assert_eq!(all_hunks, all_expanded_hunks);
11296    });
11297
11298    cx.update_editor(|editor, cx| {
11299        editor.handle_input("replacement", cx);
11300    });
11301    executor.run_until_parked();
11302    cx.assert_editor_state(
11303        &r#"
11304        use some::mod1;
11305        use some::mod2;
11306
11307        replacementˇ
11308
11309        fn main() {
11310            println!("hello");
11311
11312            println!("world");
11313        }
11314        "#
11315        .unindent(),
11316    );
11317    cx.update_editor(|editor, cx| {
11318        let snapshot = editor.snapshot(cx);
11319        let all_hunks = editor_hunks(editor, &snapshot, cx);
11320        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11321        assert_eq!(
11322            all_hunks,
11323            vec![(
11324                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
11325                DiffHunkStatus::Modified,
11326                DisplayRow(7)..DisplayRow(8)
11327            )]
11328        );
11329        assert_eq!(
11330            expanded_hunks_background_highlights(editor, cx),
11331            vec![DisplayRow(7)..=DisplayRow(7)],
11332            "Modified expanded hunks should display additions and highlight their background"
11333        );
11334        assert_eq!(all_hunks, all_expanded_hunks);
11335    });
11336}
11337
11338#[gpui::test]
11339async fn test_edits_around_toggled_modifications(
11340    executor: BackgroundExecutor,
11341    cx: &mut gpui::TestAppContext,
11342) {
11343    init_test(cx, |_| {});
11344
11345    let mut cx = EditorTestContext::new(cx).await;
11346
11347    let diff_base = r#"
11348        use some::mod1;
11349        use some::mod2;
11350
11351        const A: u32 = 42;
11352        const B: u32 = 42;
11353        const C: u32 = 42;
11354        const D: u32 = 42;
11355
11356
11357        fn main() {
11358            println!("hello");
11359
11360            println!("world");
11361        }"#
11362    .unindent();
11363    executor.run_until_parked();
11364    cx.set_state(
11365        &r#"
11366        use some::mod1;
11367        use some::mod2;
11368
11369        const A: u32 = 42;
11370        const B: u32 = 42;
11371        const C: u32 = 43ˇ
11372        const D: u32 = 42;
11373
11374
11375        fn main() {
11376            println!("hello");
11377
11378            println!("world");
11379        }"#
11380        .unindent(),
11381    );
11382
11383    cx.set_diff_base(Some(&diff_base));
11384    executor.run_until_parked();
11385    cx.update_editor(|editor, cx| {
11386        let snapshot = editor.snapshot(cx);
11387        let all_hunks = editor_hunks(editor, &snapshot, cx);
11388        assert_eq!(
11389            all_hunks,
11390            vec![(
11391                "const C: u32 = 42;\n".to_string(),
11392                DiffHunkStatus::Modified,
11393                DisplayRow(5)..DisplayRow(6)
11394            )]
11395        );
11396    });
11397    cx.update_editor(|editor, cx| {
11398        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11399    });
11400    executor.run_until_parked();
11401    cx.assert_editor_state(
11402        &r#"
11403        use some::mod1;
11404        use some::mod2;
11405
11406        const A: u32 = 42;
11407        const B: u32 = 42;
11408        const C: u32 = 43ˇ
11409        const D: u32 = 42;
11410
11411
11412        fn main() {
11413            println!("hello");
11414
11415            println!("world");
11416        }"#
11417        .unindent(),
11418    );
11419    cx.update_editor(|editor, cx| {
11420        let snapshot = editor.snapshot(cx);
11421        let all_hunks = editor_hunks(editor, &snapshot, cx);
11422        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11423        assert_eq!(
11424            expanded_hunks_background_highlights(editor, cx),
11425            vec![DisplayRow(6)..=DisplayRow(6)],
11426        );
11427        assert_eq!(
11428            all_hunks,
11429            vec![(
11430                "const C: u32 = 42;\n".to_string(),
11431                DiffHunkStatus::Modified,
11432                DisplayRow(6)..DisplayRow(7)
11433            )]
11434        );
11435        assert_eq!(all_hunks, all_expanded_hunks);
11436    });
11437
11438    cx.update_editor(|editor, cx| {
11439        editor.handle_input("\nnew_line\n", cx);
11440    });
11441    executor.run_until_parked();
11442    cx.assert_editor_state(
11443        &r#"
11444            use some::mod1;
11445            use some::mod2;
11446
11447            const A: u32 = 42;
11448            const B: u32 = 42;
11449            const C: u32 = 43
11450            new_line
11451            ˇ
11452            const D: u32 = 42;
11453
11454
11455            fn main() {
11456                println!("hello");
11457
11458                println!("world");
11459            }"#
11460        .unindent(),
11461    );
11462    cx.update_editor(|editor, cx| {
11463        let snapshot = editor.snapshot(cx);
11464        let all_hunks = editor_hunks(editor, &snapshot, cx);
11465        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11466        assert_eq!(
11467            expanded_hunks_background_highlights(editor, cx),
11468            vec![DisplayRow(6)..=DisplayRow(6)],
11469            "Modified hunk should grow highlighted lines on more text additions"
11470        );
11471        assert_eq!(
11472            all_hunks,
11473            vec![(
11474                "const C: u32 = 42;\n".to_string(),
11475                DiffHunkStatus::Modified,
11476                DisplayRow(6)..DisplayRow(9)
11477            )]
11478        );
11479        assert_eq!(all_hunks, all_expanded_hunks);
11480    });
11481
11482    cx.update_editor(|editor, cx| {
11483        editor.move_up(&MoveUp, cx);
11484        editor.move_up(&MoveUp, cx);
11485        editor.move_up(&MoveUp, cx);
11486        editor.delete_line(&DeleteLine, cx);
11487    });
11488    executor.run_until_parked();
11489    cx.assert_editor_state(
11490        &r#"
11491            use some::mod1;
11492            use some::mod2;
11493
11494            const A: u32 = 42;
11495            ˇconst C: u32 = 43
11496            new_line
11497
11498            const D: u32 = 42;
11499
11500
11501            fn main() {
11502                println!("hello");
11503
11504                println!("world");
11505            }"#
11506        .unindent(),
11507    );
11508    cx.update_editor(|editor, cx| {
11509        let snapshot = editor.snapshot(cx);
11510        let all_hunks = editor_hunks(editor, &snapshot, cx);
11511        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11512        assert_eq!(
11513            expanded_hunks_background_highlights(editor, cx),
11514            vec![DisplayRow(6)..=DisplayRow(8)],
11515        );
11516        assert_eq!(
11517            all_hunks,
11518            vec![(
11519                "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11520                DiffHunkStatus::Modified,
11521                DisplayRow(6)..DisplayRow(9)
11522            )],
11523            "Modified hunk should grow deleted lines on text deletions above"
11524        );
11525        assert_eq!(all_hunks, all_expanded_hunks);
11526    });
11527
11528    cx.update_editor(|editor, cx| {
11529        editor.move_up(&MoveUp, cx);
11530        editor.handle_input("v", cx);
11531    });
11532    executor.run_until_parked();
11533    cx.assert_editor_state(
11534        &r#"
11535            use some::mod1;
11536            use some::mod2;
11537
11538            vˇconst A: u32 = 42;
11539            const C: u32 = 43
11540            new_line
11541
11542            const D: u32 = 42;
11543
11544
11545            fn main() {
11546                println!("hello");
11547
11548                println!("world");
11549            }"#
11550        .unindent(),
11551    );
11552    cx.update_editor(|editor, cx| {
11553        let snapshot = editor.snapshot(cx);
11554        let all_hunks = editor_hunks(editor, &snapshot, cx);
11555        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11556        assert_eq!(
11557            expanded_hunks_background_highlights(editor, cx),
11558            vec![DisplayRow(6)..=DisplayRow(9)],
11559            "Modified hunk should grow deleted lines on text modifications above"
11560        );
11561        assert_eq!(
11562            all_hunks,
11563            vec![(
11564                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11565                DiffHunkStatus::Modified,
11566                DisplayRow(6)..DisplayRow(10)
11567            )]
11568        );
11569        assert_eq!(all_hunks, all_expanded_hunks);
11570    });
11571
11572    cx.update_editor(|editor, cx| {
11573        editor.move_down(&MoveDown, cx);
11574        editor.move_down(&MoveDown, cx);
11575        editor.delete_line(&DeleteLine, cx)
11576    });
11577    executor.run_until_parked();
11578    cx.assert_editor_state(
11579        &r#"
11580            use some::mod1;
11581            use some::mod2;
11582
11583            vconst A: u32 = 42;
11584            const C: u32 = 43
11585            ˇ
11586            const D: u32 = 42;
11587
11588
11589            fn main() {
11590                println!("hello");
11591
11592                println!("world");
11593            }"#
11594        .unindent(),
11595    );
11596    cx.update_editor(|editor, cx| {
11597        let snapshot = editor.snapshot(cx);
11598        let all_hunks = editor_hunks(editor, &snapshot, cx);
11599        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11600        assert_eq!(
11601            expanded_hunks_background_highlights(editor, cx),
11602            vec![DisplayRow(6)..=DisplayRow(8)],
11603            "Modified hunk should grow shrink lines on modification lines removal"
11604        );
11605        assert_eq!(
11606            all_hunks,
11607            vec![(
11608                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11609                DiffHunkStatus::Modified,
11610                DisplayRow(6)..DisplayRow(9)
11611            )]
11612        );
11613        assert_eq!(all_hunks, all_expanded_hunks);
11614    });
11615
11616    cx.update_editor(|editor, cx| {
11617        editor.move_up(&MoveUp, cx);
11618        editor.move_up(&MoveUp, cx);
11619        editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11620        editor.delete_line(&DeleteLine, cx)
11621    });
11622    executor.run_until_parked();
11623    cx.assert_editor_state(
11624        &r#"
11625            use some::mod1;
11626            use some::mod2;
11627
11628            ˇ
11629
11630            fn main() {
11631                println!("hello");
11632
11633                println!("world");
11634            }"#
11635        .unindent(),
11636    );
11637    cx.update_editor(|editor, cx| {
11638        let snapshot = editor.snapshot(cx);
11639        let all_hunks = editor_hunks(editor, &snapshot, cx);
11640        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11641        assert_eq!(
11642            expanded_hunks_background_highlights(editor, cx),
11643            Vec::new(),
11644            "Modified hunk should turn into a removed one on all modified lines removal"
11645        );
11646        assert_eq!(
11647            all_hunks,
11648            vec![(
11649                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11650                    .to_string(),
11651                DiffHunkStatus::Removed,
11652                DisplayRow(7)..DisplayRow(7)
11653            )]
11654        );
11655        assert_eq!(all_hunks, all_expanded_hunks);
11656    });
11657}
11658
11659#[gpui::test]
11660async fn test_multiple_expanded_hunks_merge(
11661    executor: BackgroundExecutor,
11662    cx: &mut gpui::TestAppContext,
11663) {
11664    init_test(cx, |_| {});
11665
11666    let mut cx = EditorTestContext::new(cx).await;
11667
11668    let diff_base = r#"
11669        use some::mod1;
11670        use some::mod2;
11671
11672        const A: u32 = 42;
11673        const B: u32 = 42;
11674        const C: u32 = 42;
11675        const D: u32 = 42;
11676
11677
11678        fn main() {
11679            println!("hello");
11680
11681            println!("world");
11682        }"#
11683    .unindent();
11684    executor.run_until_parked();
11685    cx.set_state(
11686        &r#"
11687        use some::mod1;
11688        use some::mod2;
11689
11690        const A: u32 = 42;
11691        const B: u32 = 42;
11692        const C: u32 = 43ˇ
11693        const D: u32 = 42;
11694
11695
11696        fn main() {
11697            println!("hello");
11698
11699            println!("world");
11700        }"#
11701        .unindent(),
11702    );
11703
11704    cx.set_diff_base(Some(&diff_base));
11705    executor.run_until_parked();
11706    cx.update_editor(|editor, cx| {
11707        let snapshot = editor.snapshot(cx);
11708        let all_hunks = editor_hunks(editor, &snapshot, cx);
11709        assert_eq!(
11710            all_hunks,
11711            vec![(
11712                "const C: u32 = 42;\n".to_string(),
11713                DiffHunkStatus::Modified,
11714                DisplayRow(5)..DisplayRow(6)
11715            )]
11716        );
11717    });
11718    cx.update_editor(|editor, cx| {
11719        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11720    });
11721    executor.run_until_parked();
11722    cx.assert_editor_state(
11723        &r#"
11724        use some::mod1;
11725        use some::mod2;
11726
11727        const A: u32 = 42;
11728        const B: u32 = 42;
11729        const C: u32 = 43ˇ
11730        const D: u32 = 42;
11731
11732
11733        fn main() {
11734            println!("hello");
11735
11736            println!("world");
11737        }"#
11738        .unindent(),
11739    );
11740    cx.update_editor(|editor, cx| {
11741        let snapshot = editor.snapshot(cx);
11742        let all_hunks = editor_hunks(editor, &snapshot, cx);
11743        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11744        assert_eq!(
11745            expanded_hunks_background_highlights(editor, cx),
11746            vec![DisplayRow(6)..=DisplayRow(6)],
11747        );
11748        assert_eq!(
11749            all_hunks,
11750            vec![(
11751                "const C: u32 = 42;\n".to_string(),
11752                DiffHunkStatus::Modified,
11753                DisplayRow(6)..DisplayRow(7)
11754            )]
11755        );
11756        assert_eq!(all_hunks, all_expanded_hunks);
11757    });
11758
11759    cx.update_editor(|editor, cx| {
11760        editor.handle_input("\nnew_line\n", cx);
11761    });
11762    executor.run_until_parked();
11763    cx.assert_editor_state(
11764        &r#"
11765            use some::mod1;
11766            use some::mod2;
11767
11768            const A: u32 = 42;
11769            const B: u32 = 42;
11770            const C: u32 = 43
11771            new_line
11772            ˇ
11773            const D: u32 = 42;
11774
11775
11776            fn main() {
11777                println!("hello");
11778
11779                println!("world");
11780            }"#
11781        .unindent(),
11782    );
11783}
11784
11785async fn setup_indent_guides_editor(
11786    text: &str,
11787    cx: &mut gpui::TestAppContext,
11788) -> (BufferId, EditorTestContext) {
11789    init_test(cx, |_| {});
11790
11791    let mut cx = EditorTestContext::new(cx).await;
11792
11793    let buffer_id = cx.update_editor(|editor, cx| {
11794        editor.set_text(text, cx);
11795        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11796        let buffer_id = buffer_ids[0];
11797        buffer_id
11798    });
11799
11800    (buffer_id, cx)
11801}
11802
11803fn assert_indent_guides(
11804    range: Range<u32>,
11805    expected: Vec<IndentGuide>,
11806    active_indices: Option<Vec<usize>>,
11807    cx: &mut EditorTestContext,
11808) {
11809    let indent_guides = cx.update_editor(|editor, cx| {
11810        let snapshot = editor.snapshot(cx).display_snapshot;
11811        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11812            MultiBufferRow(range.start)..MultiBufferRow(range.end),
11813            true,
11814            &snapshot,
11815            cx,
11816        );
11817
11818        indent_guides.sort_by(|a, b| {
11819            a.depth.cmp(&b.depth).then(
11820                a.start_row
11821                    .cmp(&b.start_row)
11822                    .then(a.end_row.cmp(&b.end_row)),
11823            )
11824        });
11825        indent_guides
11826    });
11827
11828    if let Some(expected) = active_indices {
11829        let active_indices = cx.update_editor(|editor, cx| {
11830            let snapshot = editor.snapshot(cx).display_snapshot;
11831            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11832        });
11833
11834        assert_eq!(
11835            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11836            expected,
11837            "Active indent guide indices do not match"
11838        );
11839    }
11840
11841    let expected: Vec<_> = expected
11842        .into_iter()
11843        .map(|guide| MultiBufferIndentGuide {
11844            multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11845            buffer: guide,
11846        })
11847        .collect();
11848
11849    assert_eq!(indent_guides, expected, "Indent guides do not match");
11850}
11851
11852fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
11853    IndentGuide {
11854        buffer_id,
11855        start_row,
11856        end_row,
11857        depth,
11858        tab_size: 4,
11859        settings: IndentGuideSettings {
11860            enabled: true,
11861            line_width: 1,
11862            active_line_width: 1,
11863            ..Default::default()
11864        },
11865    }
11866}
11867
11868#[gpui::test]
11869async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11870    let (buffer_id, mut cx) = setup_indent_guides_editor(
11871        &"
11872    fn main() {
11873        let a = 1;
11874    }"
11875        .unindent(),
11876        cx,
11877    )
11878    .await;
11879
11880    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11881}
11882
11883#[gpui::test]
11884async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11885    let (buffer_id, mut cx) = setup_indent_guides_editor(
11886        &"
11887    fn main() {
11888        let a = 1;
11889        let b = 2;
11890    }"
11891        .unindent(),
11892        cx,
11893    )
11894    .await;
11895
11896    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
11897}
11898
11899#[gpui::test]
11900async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11901    let (buffer_id, mut cx) = setup_indent_guides_editor(
11902        &"
11903    fn main() {
11904        let a = 1;
11905        if a == 3 {
11906            let b = 2;
11907        } else {
11908            let c = 3;
11909        }
11910    }"
11911        .unindent(),
11912        cx,
11913    )
11914    .await;
11915
11916    assert_indent_guides(
11917        0..8,
11918        vec![
11919            indent_guide(buffer_id, 1, 6, 0),
11920            indent_guide(buffer_id, 3, 3, 1),
11921            indent_guide(buffer_id, 5, 5, 1),
11922        ],
11923        None,
11924        &mut cx,
11925    );
11926}
11927
11928#[gpui::test]
11929async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11930    let (buffer_id, mut cx) = setup_indent_guides_editor(
11931        &"
11932    fn main() {
11933        let a = 1;
11934            let b = 2;
11935        let c = 3;
11936    }"
11937        .unindent(),
11938        cx,
11939    )
11940    .await;
11941
11942    assert_indent_guides(
11943        0..5,
11944        vec![
11945            indent_guide(buffer_id, 1, 3, 0),
11946            indent_guide(buffer_id, 2, 2, 1),
11947        ],
11948        None,
11949        &mut cx,
11950    );
11951}
11952
11953#[gpui::test]
11954async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
11955    let (buffer_id, mut cx) = setup_indent_guides_editor(
11956        &"
11957        fn main() {
11958            let a = 1;
11959
11960            let c = 3;
11961        }"
11962        .unindent(),
11963        cx,
11964    )
11965    .await;
11966
11967    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
11968}
11969
11970#[gpui::test]
11971async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
11972    let (buffer_id, mut cx) = setup_indent_guides_editor(
11973        &"
11974        fn main() {
11975            let a = 1;
11976
11977            let c = 3;
11978
11979            if a == 3 {
11980                let b = 2;
11981            } else {
11982                let c = 3;
11983            }
11984        }"
11985        .unindent(),
11986        cx,
11987    )
11988    .await;
11989
11990    assert_indent_guides(
11991        0..11,
11992        vec![
11993            indent_guide(buffer_id, 1, 9, 0),
11994            indent_guide(buffer_id, 6, 6, 1),
11995            indent_guide(buffer_id, 8, 8, 1),
11996        ],
11997        None,
11998        &mut cx,
11999    );
12000}
12001
12002#[gpui::test]
12003async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12004    let (buffer_id, mut cx) = setup_indent_guides_editor(
12005        &"
12006        fn main() {
12007            let a = 1;
12008
12009            let c = 3;
12010
12011            if a == 3 {
12012                let b = 2;
12013            } else {
12014                let c = 3;
12015            }
12016        }"
12017        .unindent(),
12018        cx,
12019    )
12020    .await;
12021
12022    assert_indent_guides(
12023        1..11,
12024        vec![
12025            indent_guide(buffer_id, 1, 9, 0),
12026            indent_guide(buffer_id, 6, 6, 1),
12027            indent_guide(buffer_id, 8, 8, 1),
12028        ],
12029        None,
12030        &mut cx,
12031    );
12032}
12033
12034#[gpui::test]
12035async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12036    let (buffer_id, mut cx) = setup_indent_guides_editor(
12037        &"
12038        fn main() {
12039            let a = 1;
12040
12041            let c = 3;
12042
12043            if a == 3 {
12044                let b = 2;
12045            } else {
12046                let c = 3;
12047            }
12048        }"
12049        .unindent(),
12050        cx,
12051    )
12052    .await;
12053
12054    assert_indent_guides(
12055        1..10,
12056        vec![
12057            indent_guide(buffer_id, 1, 9, 0),
12058            indent_guide(buffer_id, 6, 6, 1),
12059            indent_guide(buffer_id, 8, 8, 1),
12060        ],
12061        None,
12062        &mut cx,
12063    );
12064}
12065
12066#[gpui::test]
12067async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12068    let (buffer_id, mut cx) = setup_indent_guides_editor(
12069        &"
12070        block1
12071            block2
12072                block3
12073                    block4
12074            block2
12075        block1
12076        block1"
12077            .unindent(),
12078        cx,
12079    )
12080    .await;
12081
12082    assert_indent_guides(
12083        1..10,
12084        vec![
12085            indent_guide(buffer_id, 1, 4, 0),
12086            indent_guide(buffer_id, 2, 3, 1),
12087            indent_guide(buffer_id, 3, 3, 2),
12088        ],
12089        None,
12090        &mut cx,
12091    );
12092}
12093
12094#[gpui::test]
12095async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12096    let (buffer_id, mut cx) = setup_indent_guides_editor(
12097        &"
12098        block1
12099            block2
12100                block3
12101
12102        block1
12103        block1"
12104            .unindent(),
12105        cx,
12106    )
12107    .await;
12108
12109    assert_indent_guides(
12110        0..6,
12111        vec![
12112            indent_guide(buffer_id, 1, 2, 0),
12113            indent_guide(buffer_id, 2, 2, 1),
12114        ],
12115        None,
12116        &mut cx,
12117    );
12118}
12119
12120#[gpui::test]
12121async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12122    let (buffer_id, mut cx) = setup_indent_guides_editor(
12123        &"
12124        block1
12125
12126
12127
12128            block2
12129        "
12130        .unindent(),
12131        cx,
12132    )
12133    .await;
12134
12135    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12136}
12137
12138#[gpui::test]
12139async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12140    let (buffer_id, mut cx) = setup_indent_guides_editor(
12141        &"
12142        def a:
12143        \tb = 3
12144        \tif True:
12145        \t\tc = 4
12146        \t\td = 5
12147        \tprint(b)
12148        "
12149        .unindent(),
12150        cx,
12151    )
12152    .await;
12153
12154    assert_indent_guides(
12155        0..6,
12156        vec![
12157            indent_guide(buffer_id, 1, 6, 0),
12158            indent_guide(buffer_id, 3, 4, 1),
12159        ],
12160        None,
12161        &mut cx,
12162    );
12163}
12164
12165#[gpui::test]
12166async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12167    let (buffer_id, mut cx) = setup_indent_guides_editor(
12168        &"
12169    fn main() {
12170        let a = 1;
12171    }"
12172        .unindent(),
12173        cx,
12174    )
12175    .await;
12176
12177    cx.update_editor(|editor, cx| {
12178        editor.change_selections(None, cx, |s| {
12179            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12180        });
12181    });
12182
12183    assert_indent_guides(
12184        0..3,
12185        vec![indent_guide(buffer_id, 1, 1, 0)],
12186        Some(vec![0]),
12187        &mut cx,
12188    );
12189}
12190
12191#[gpui::test]
12192async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12193    let (buffer_id, mut cx) = setup_indent_guides_editor(
12194        &"
12195    fn main() {
12196        if 1 == 2 {
12197            let a = 1;
12198        }
12199    }"
12200        .unindent(),
12201        cx,
12202    )
12203    .await;
12204
12205    cx.update_editor(|editor, cx| {
12206        editor.change_selections(None, cx, |s| {
12207            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12208        });
12209    });
12210
12211    assert_indent_guides(
12212        0..4,
12213        vec![
12214            indent_guide(buffer_id, 1, 3, 0),
12215            indent_guide(buffer_id, 2, 2, 1),
12216        ],
12217        Some(vec![1]),
12218        &mut cx,
12219    );
12220
12221    cx.update_editor(|editor, cx| {
12222        editor.change_selections(None, cx, |s| {
12223            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12224        });
12225    });
12226
12227    assert_indent_guides(
12228        0..4,
12229        vec![
12230            indent_guide(buffer_id, 1, 3, 0),
12231            indent_guide(buffer_id, 2, 2, 1),
12232        ],
12233        Some(vec![1]),
12234        &mut cx,
12235    );
12236
12237    cx.update_editor(|editor, cx| {
12238        editor.change_selections(None, cx, |s| {
12239            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12240        });
12241    });
12242
12243    assert_indent_guides(
12244        0..4,
12245        vec![
12246            indent_guide(buffer_id, 1, 3, 0),
12247            indent_guide(buffer_id, 2, 2, 1),
12248        ],
12249        Some(vec![0]),
12250        &mut cx,
12251    );
12252}
12253
12254#[gpui::test]
12255async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12256    let (buffer_id, mut cx) = setup_indent_guides_editor(
12257        &"
12258    fn main() {
12259        let a = 1;
12260
12261        let b = 2;
12262    }"
12263        .unindent(),
12264        cx,
12265    )
12266    .await;
12267
12268    cx.update_editor(|editor, cx| {
12269        editor.change_selections(None, cx, |s| {
12270            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12271        });
12272    });
12273
12274    assert_indent_guides(
12275        0..5,
12276        vec![indent_guide(buffer_id, 1, 3, 0)],
12277        Some(vec![0]),
12278        &mut cx,
12279    );
12280}
12281
12282#[gpui::test]
12283async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12284    let (buffer_id, mut cx) = setup_indent_guides_editor(
12285        &"
12286    def m:
12287        a = 1
12288        pass"
12289            .unindent(),
12290        cx,
12291    )
12292    .await;
12293
12294    cx.update_editor(|editor, cx| {
12295        editor.change_selections(None, cx, |s| {
12296            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12297        });
12298    });
12299
12300    assert_indent_guides(
12301        0..3,
12302        vec![indent_guide(buffer_id, 1, 2, 0)],
12303        Some(vec![0]),
12304        &mut cx,
12305    );
12306}
12307
12308#[gpui::test]
12309fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12310    init_test(cx, |_| {});
12311
12312    let editor = cx.add_window(|cx| {
12313        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12314        build_editor(buffer, cx)
12315    });
12316
12317    let render_args = Arc::new(Mutex::new(None));
12318    let snapshot = editor
12319        .update(cx, |editor, cx| {
12320            let snapshot = editor.buffer().read(cx).snapshot(cx);
12321            let range =
12322                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12323
12324            struct RenderArgs {
12325                row: MultiBufferRow,
12326                folded: bool,
12327                callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12328            }
12329
12330            let crease = Crease::new(
12331                range,
12332                FoldPlaceholder::test(),
12333                {
12334                    let toggle_callback = render_args.clone();
12335                    move |row, folded, callback, _cx| {
12336                        *toggle_callback.lock() = Some(RenderArgs {
12337                            row,
12338                            folded,
12339                            callback,
12340                        });
12341                        div()
12342                    }
12343                },
12344                |_row, _folded, _cx| div(),
12345            );
12346
12347            editor.insert_creases(Some(crease), cx);
12348            let snapshot = editor.snapshot(cx);
12349            let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12350            snapshot
12351        })
12352        .unwrap();
12353
12354    let render_args = render_args.lock().take().unwrap();
12355    assert_eq!(render_args.row, MultiBufferRow(1));
12356    assert_eq!(render_args.folded, false);
12357    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12358
12359    cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12360        .unwrap();
12361    let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12362    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12363
12364    cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12365        .unwrap();
12366    let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12367    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12368}
12369
12370fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12371    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12372    point..point
12373}
12374
12375fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12376    let (text, ranges) = marked_text_ranges(marked_text, true);
12377    assert_eq!(view.text(cx), text);
12378    assert_eq!(
12379        view.selections.ranges(cx),
12380        ranges,
12381        "Assert selections are {}",
12382        marked_text
12383    );
12384}
12385
12386/// Handle completion request passing a marked string specifying where the completion
12387/// should be triggered from using '|' character, what range should be replaced, and what completions
12388/// should be returned using '<' and '>' to delimit the range
12389pub fn handle_completion_request(
12390    cx: &mut EditorLspTestContext,
12391    marked_string: &str,
12392    completions: Vec<&'static str>,
12393    counter: Arc<AtomicUsize>,
12394) -> impl Future<Output = ()> {
12395    let complete_from_marker: TextRangeMarker = '|'.into();
12396    let replace_range_marker: TextRangeMarker = ('<', '>').into();
12397    let (_, mut marked_ranges) = marked_text_ranges_by(
12398        marked_string,
12399        vec![complete_from_marker.clone(), replace_range_marker.clone()],
12400    );
12401
12402    let complete_from_position =
12403        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12404    let replace_range =
12405        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12406
12407    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12408        let completions = completions.clone();
12409        counter.fetch_add(1, atomic::Ordering::Release);
12410        async move {
12411            assert_eq!(params.text_document_position.text_document.uri, url.clone());
12412            assert_eq!(
12413                params.text_document_position.position,
12414                complete_from_position
12415            );
12416            Ok(Some(lsp::CompletionResponse::Array(
12417                completions
12418                    .iter()
12419                    .map(|completion_text| lsp::CompletionItem {
12420                        label: completion_text.to_string(),
12421                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12422                            range: replace_range,
12423                            new_text: completion_text.to_string(),
12424                        })),
12425                        ..Default::default()
12426                    })
12427                    .collect(),
12428            )))
12429        }
12430    });
12431
12432    async move {
12433        request.next().await;
12434    }
12435}
12436
12437fn handle_resolve_completion_request(
12438    cx: &mut EditorLspTestContext,
12439    edits: Option<Vec<(&'static str, &'static str)>>,
12440) -> impl Future<Output = ()> {
12441    let edits = edits.map(|edits| {
12442        edits
12443            .iter()
12444            .map(|(marked_string, new_text)| {
12445                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12446                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12447                lsp::TextEdit::new(replace_range, new_text.to_string())
12448            })
12449            .collect::<Vec<_>>()
12450    });
12451
12452    let mut request =
12453        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12454            let edits = edits.clone();
12455            async move {
12456                Ok(lsp::CompletionItem {
12457                    additional_text_edits: edits,
12458                    ..Default::default()
12459                })
12460            }
12461        });
12462
12463    async move {
12464        request.next().await;
12465    }
12466}
12467
12468pub(crate) fn update_test_language_settings(
12469    cx: &mut TestAppContext,
12470    f: impl Fn(&mut AllLanguageSettingsContent),
12471) {
12472    _ = cx.update(|cx| {
12473        SettingsStore::update_global(cx, |store, cx| {
12474            store.update_user_settings::<AllLanguageSettings>(cx, f);
12475        });
12476    });
12477}
12478
12479pub(crate) fn update_test_project_settings(
12480    cx: &mut TestAppContext,
12481    f: impl Fn(&mut ProjectSettings),
12482) {
12483    _ = cx.update(|cx| {
12484        SettingsStore::update_global(cx, |store, cx| {
12485            store.update_user_settings::<ProjectSettings>(cx, f);
12486        });
12487    });
12488}
12489
12490pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12491    _ = cx.update(|cx| {
12492        cx.text_system()
12493            .add_fonts(vec![assets::Assets
12494                .load("fonts/zed-mono/zed-mono-extended.ttf")
12495                .unwrap()
12496                .unwrap()])
12497            .unwrap();
12498        let store = SettingsStore::test(cx);
12499        cx.set_global(store);
12500        theme::init(theme::LoadThemes::JustBase, cx);
12501        release_channel::init(SemanticVersion::default(), cx);
12502        client::init_settings(cx);
12503        language::init(cx);
12504        Project::init_settings(cx);
12505        workspace::init_settings(cx);
12506        crate::init(cx);
12507    });
12508
12509    update_test_language_settings(cx, f);
12510}
12511
12512pub(crate) fn rust_lang() -> Arc<Language> {
12513    Arc::new(Language::new(
12514        LanguageConfig {
12515            name: "Rust".into(),
12516            matcher: LanguageMatcher {
12517                path_suffixes: vec!["rs".to_string()],
12518                ..Default::default()
12519            },
12520            ..Default::default()
12521        },
12522        Some(tree_sitter_rust::language()),
12523    ))
12524}
12525
12526#[track_caller]
12527fn assert_hunk_revert(
12528    not_reverted_text_with_selections: &str,
12529    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12530    expected_reverted_text_with_selections: &str,
12531    base_text: &str,
12532    cx: &mut EditorLspTestContext,
12533) {
12534    cx.set_state(not_reverted_text_with_selections);
12535    cx.update_editor(|editor, cx| {
12536        editor
12537            .buffer()
12538            .read(cx)
12539            .as_singleton()
12540            .unwrap()
12541            .update(cx, |buffer, cx| {
12542                buffer.set_diff_base(Some(base_text.into()), cx);
12543            });
12544    });
12545    cx.executor().run_until_parked();
12546
12547    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12548        let snapshot = editor.buffer().read(cx).snapshot(cx);
12549        let reverted_hunk_statuses = snapshot
12550            .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12551            .map(|hunk| hunk_status(&hunk))
12552            .collect::<Vec<_>>();
12553
12554        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12555        reverted_hunk_statuses
12556    });
12557    cx.executor().run_until_parked();
12558    cx.assert_editor_state(expected_reverted_text_with_selections);
12559    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12560}