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, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
   14    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_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
 7024    init_test(cx, |_| {});
 7025    let mut cx = EditorLspTestContext::new_rust(
 7026        lsp::ServerCapabilities {
 7027            completion_provider: Some(lsp::CompletionOptions {
 7028                trigger_characters: Some(vec![".".to_string()]),
 7029                ..Default::default()
 7030            }),
 7031            ..Default::default()
 7032        },
 7033        cx,
 7034    )
 7035    .await;
 7036    cx.lsp
 7037        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 7038            Ok(Some(lsp::CompletionResponse::Array(vec![
 7039                lsp::CompletionItem {
 7040                    label: "first".into(),
 7041                    ..Default::default()
 7042                },
 7043                lsp::CompletionItem {
 7044                    label: "last".into(),
 7045                    ..Default::default()
 7046                },
 7047            ])))
 7048        });
 7049    cx.set_state("variableˇ");
 7050    cx.simulate_keystroke(".");
 7051    cx.executor().run_until_parked();
 7052
 7053    cx.update_editor(|editor, _| {
 7054        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 7055            assert_eq!(
 7056                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 7057                &["first", "last"]
 7058            );
 7059        } else {
 7060            panic!("expected completion menu to be open");
 7061        }
 7062    });
 7063
 7064    cx.update_editor(|editor, cx| {
 7065        editor.move_page_down(&MovePageDown::default(), cx);
 7066        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 7067            assert!(
 7068                menu.selected_item == 1,
 7069                "expected PageDown to select the last item from the context menu"
 7070            );
 7071        } else {
 7072            panic!("expected completion menu to stay open after PageDown");
 7073        }
 7074    });
 7075
 7076    cx.update_editor(|editor, cx| {
 7077        editor.move_page_up(&MovePageUp::default(), cx);
 7078        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 7079            assert!(
 7080                menu.selected_item == 0,
 7081                "expected PageUp to select the first item from the context menu"
 7082            );
 7083        } else {
 7084            panic!("expected completion menu to stay open after PageUp");
 7085        }
 7086    });
 7087}
 7088
 7089#[gpui::test]
 7090async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
 7091    init_test(cx, |_| {});
 7092
 7093    let mut cx = EditorLspTestContext::new_rust(
 7094        lsp::ServerCapabilities {
 7095            completion_provider: Some(lsp::CompletionOptions {
 7096                trigger_characters: Some(vec![".".to_string()]),
 7097                resolve_provider: Some(true),
 7098                ..Default::default()
 7099            }),
 7100            ..Default::default()
 7101        },
 7102        cx,
 7103    )
 7104    .await;
 7105
 7106    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 7107    cx.simulate_keystroke(".");
 7108    let completion_item = lsp::CompletionItem {
 7109        label: "Some".into(),
 7110        kind: Some(lsp::CompletionItemKind::SNIPPET),
 7111        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 7112        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 7113            kind: lsp::MarkupKind::Markdown,
 7114            value: "```rust\nSome(2)\n```".to_string(),
 7115        })),
 7116        deprecated: Some(false),
 7117        sort_text: Some("Some".to_string()),
 7118        filter_text: Some("Some".to_string()),
 7119        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 7120        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 7121            range: lsp::Range {
 7122                start: lsp::Position {
 7123                    line: 0,
 7124                    character: 22,
 7125                },
 7126                end: lsp::Position {
 7127                    line: 0,
 7128                    character: 22,
 7129                },
 7130            },
 7131            new_text: "Some(2)".to_string(),
 7132        })),
 7133        additional_text_edits: Some(vec![lsp::TextEdit {
 7134            range: lsp::Range {
 7135                start: lsp::Position {
 7136                    line: 0,
 7137                    character: 20,
 7138                },
 7139                end: lsp::Position {
 7140                    line: 0,
 7141                    character: 22,
 7142                },
 7143            },
 7144            new_text: "".to_string(),
 7145        }]),
 7146        ..Default::default()
 7147    };
 7148
 7149    let closure_completion_item = completion_item.clone();
 7150    let counter = Arc::new(AtomicUsize::new(0));
 7151    let counter_clone = counter.clone();
 7152    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 7153        let task_completion_item = closure_completion_item.clone();
 7154        counter_clone.fetch_add(1, atomic::Ordering::Release);
 7155        async move {
 7156            Ok(Some(lsp::CompletionResponse::Array(vec![
 7157                task_completion_item,
 7158            ])))
 7159        }
 7160    });
 7161
 7162    cx.condition(|editor, _| editor.context_menu_visible())
 7163        .await;
 7164    cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
 7165    assert!(request.next().await.is_some());
 7166    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 7167
 7168    cx.simulate_keystroke("S");
 7169    cx.simulate_keystroke("o");
 7170    cx.simulate_keystroke("m");
 7171    cx.condition(|editor, _| editor.context_menu_visible())
 7172        .await;
 7173    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
 7174    assert!(request.next().await.is_some());
 7175    assert!(request.next().await.is_some());
 7176    assert!(request.next().await.is_some());
 7177    request.close();
 7178    assert!(request.next().await.is_none());
 7179    assert_eq!(
 7180        counter.load(atomic::Ordering::Acquire),
 7181        4,
 7182        "With the completions menu open, only one LSP request should happen per input"
 7183    );
 7184}
 7185
 7186#[gpui::test]
 7187async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
 7188    init_test(cx, |_| {});
 7189    let mut cx = EditorTestContext::new(cx).await;
 7190    let language = Arc::new(Language::new(
 7191        LanguageConfig {
 7192            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
 7193            ..Default::default()
 7194        },
 7195        Some(tree_sitter_rust::language()),
 7196    ));
 7197    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7198
 7199    // If multiple selections intersect a line, the line is only toggled once.
 7200    cx.set_state(indoc! {"
 7201        fn a() {
 7202            «//b();
 7203            ˇ»// «c();
 7204            //ˇ»  d();
 7205        }
 7206    "});
 7207
 7208    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7209
 7210    cx.assert_editor_state(indoc! {"
 7211        fn a() {
 7212            «b();
 7213            c();
 7214            ˇ» d();
 7215        }
 7216    "});
 7217
 7218    // The comment prefix is inserted at the same column for every line in a
 7219    // selection.
 7220    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7221
 7222    cx.assert_editor_state(indoc! {"
 7223        fn a() {
 7224            // «b();
 7225            // c();
 7226            ˇ»//  d();
 7227        }
 7228    "});
 7229
 7230    // If a selection ends at the beginning of a line, that line is not toggled.
 7231    cx.set_selections_state(indoc! {"
 7232        fn a() {
 7233            // b();
 7234            «// c();
 7235        ˇ»    //  d();
 7236        }
 7237    "});
 7238
 7239    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7240
 7241    cx.assert_editor_state(indoc! {"
 7242        fn a() {
 7243            // b();
 7244            «c();
 7245        ˇ»    //  d();
 7246        }
 7247    "});
 7248
 7249    // If a selection span a single line and is empty, the line is toggled.
 7250    cx.set_state(indoc! {"
 7251        fn a() {
 7252            a();
 7253            b();
 7254        ˇ
 7255        }
 7256    "});
 7257
 7258    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7259
 7260    cx.assert_editor_state(indoc! {"
 7261        fn a() {
 7262            a();
 7263            b();
 7264        //•ˇ
 7265        }
 7266    "});
 7267
 7268    // If a selection span multiple lines, empty lines are not toggled.
 7269    cx.set_state(indoc! {"
 7270        fn a() {
 7271            «a();
 7272
 7273            c();ˇ»
 7274        }
 7275    "});
 7276
 7277    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7278
 7279    cx.assert_editor_state(indoc! {"
 7280        fn a() {
 7281            // «a();
 7282
 7283            // c();ˇ»
 7284        }
 7285    "});
 7286
 7287    // If a selection includes multiple comment prefixes, all lines are uncommented.
 7288    cx.set_state(indoc! {"
 7289        fn a() {
 7290            «// a();
 7291            /// b();
 7292            //! c();ˇ»
 7293        }
 7294    "});
 7295
 7296    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 7297
 7298    cx.assert_editor_state(indoc! {"
 7299        fn a() {
 7300            «a();
 7301            b();
 7302            c();ˇ»
 7303        }
 7304    "});
 7305}
 7306
 7307#[gpui::test]
 7308async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
 7309    init_test(cx, |_| {});
 7310
 7311    let language = Arc::new(Language::new(
 7312        LanguageConfig {
 7313            line_comments: vec!["// ".into()],
 7314            ..Default::default()
 7315        },
 7316        Some(tree_sitter_rust::language()),
 7317    ));
 7318
 7319    let mut cx = EditorTestContext::new(cx).await;
 7320
 7321    cx.language_registry().add(language.clone());
 7322    cx.update_buffer(|buffer, cx| {
 7323        buffer.set_language(Some(language), cx);
 7324    });
 7325
 7326    let toggle_comments = &ToggleComments {
 7327        advance_downwards: true,
 7328    };
 7329
 7330    // Single cursor on one line -> advance
 7331    // Cursor moves horizontally 3 characters as well on non-blank line
 7332    cx.set_state(indoc!(
 7333        "fn a() {
 7334             ˇdog();
 7335             cat();
 7336        }"
 7337    ));
 7338    cx.update_editor(|editor, cx| {
 7339        editor.toggle_comments(toggle_comments, cx);
 7340    });
 7341    cx.assert_editor_state(indoc!(
 7342        "fn a() {
 7343             // dog();
 7344             catˇ();
 7345        }"
 7346    ));
 7347
 7348    // Single selection on one line -> don't advance
 7349    cx.set_state(indoc!(
 7350        "fn a() {
 7351             «dog()ˇ»;
 7352             cat();
 7353        }"
 7354    ));
 7355    cx.update_editor(|editor, cx| {
 7356        editor.toggle_comments(toggle_comments, cx);
 7357    });
 7358    cx.assert_editor_state(indoc!(
 7359        "fn a() {
 7360             // «dog()ˇ»;
 7361             cat();
 7362        }"
 7363    ));
 7364
 7365    // Multiple cursors on one line -> advance
 7366    cx.set_state(indoc!(
 7367        "fn a() {
 7368             ˇdˇog();
 7369             cat();
 7370        }"
 7371    ));
 7372    cx.update_editor(|editor, cx| {
 7373        editor.toggle_comments(toggle_comments, cx);
 7374    });
 7375    cx.assert_editor_state(indoc!(
 7376        "fn a() {
 7377             // dog();
 7378             catˇ(ˇ);
 7379        }"
 7380    ));
 7381
 7382    // Multiple cursors on one line, with selection -> don't advance
 7383    cx.set_state(indoc!(
 7384        "fn a() {
 7385             ˇdˇog«()ˇ»;
 7386             cat();
 7387        }"
 7388    ));
 7389    cx.update_editor(|editor, cx| {
 7390        editor.toggle_comments(toggle_comments, cx);
 7391    });
 7392    cx.assert_editor_state(indoc!(
 7393        "fn a() {
 7394             // ˇdˇog«()ˇ»;
 7395             cat();
 7396        }"
 7397    ));
 7398
 7399    // Single cursor on one line -> advance
 7400    // Cursor moves to column 0 on blank line
 7401    cx.set_state(indoc!(
 7402        "fn a() {
 7403             ˇdog();
 7404
 7405             cat();
 7406        }"
 7407    ));
 7408    cx.update_editor(|editor, cx| {
 7409        editor.toggle_comments(toggle_comments, cx);
 7410    });
 7411    cx.assert_editor_state(indoc!(
 7412        "fn a() {
 7413             // dog();
 7414        ˇ
 7415             cat();
 7416        }"
 7417    ));
 7418
 7419    // Single cursor on one line -> advance
 7420    // Cursor starts and ends at column 0
 7421    cx.set_state(indoc!(
 7422        "fn a() {
 7423         ˇ    dog();
 7424             cat();
 7425        }"
 7426    ));
 7427    cx.update_editor(|editor, cx| {
 7428        editor.toggle_comments(toggle_comments, cx);
 7429    });
 7430    cx.assert_editor_state(indoc!(
 7431        "fn a() {
 7432             // dog();
 7433         ˇ    cat();
 7434        }"
 7435    ));
 7436}
 7437
 7438#[gpui::test]
 7439async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 7440    init_test(cx, |_| {});
 7441
 7442    let mut cx = EditorTestContext::new(cx).await;
 7443
 7444    let html_language = Arc::new(
 7445        Language::new(
 7446            LanguageConfig {
 7447                name: "HTML".into(),
 7448                block_comment: Some(("<!-- ".into(), " -->".into())),
 7449                ..Default::default()
 7450            },
 7451            Some(tree_sitter_html::language()),
 7452        )
 7453        .with_injection_query(
 7454            r#"
 7455            (script_element
 7456                (raw_text) @content
 7457                (#set! "language" "javascript"))
 7458            "#,
 7459        )
 7460        .unwrap(),
 7461    );
 7462
 7463    let javascript_language = Arc::new(Language::new(
 7464        LanguageConfig {
 7465            name: "JavaScript".into(),
 7466            line_comments: vec!["// ".into()],
 7467            ..Default::default()
 7468        },
 7469        Some(tree_sitter_typescript::language_tsx()),
 7470    ));
 7471
 7472    cx.language_registry().add(html_language.clone());
 7473    cx.language_registry().add(javascript_language.clone());
 7474    cx.update_buffer(|buffer, cx| {
 7475        buffer.set_language(Some(html_language), cx);
 7476    });
 7477
 7478    // Toggle comments for empty selections
 7479    cx.set_state(
 7480        &r#"
 7481            <p>A</p>ˇ
 7482            <p>B</p>ˇ
 7483            <p>C</p>ˇ
 7484        "#
 7485        .unindent(),
 7486    );
 7487    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7488    cx.assert_editor_state(
 7489        &r#"
 7490            <!-- <p>A</p>ˇ -->
 7491            <!-- <p>B</p>ˇ -->
 7492            <!-- <p>C</p>ˇ -->
 7493        "#
 7494        .unindent(),
 7495    );
 7496    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7497    cx.assert_editor_state(
 7498        &r#"
 7499            <p>A</p>ˇ
 7500            <p>B</p>ˇ
 7501            <p>C</p>ˇ
 7502        "#
 7503        .unindent(),
 7504    );
 7505
 7506    // Toggle comments for mixture of empty and non-empty selections, where
 7507    // multiple selections occupy a given line.
 7508    cx.set_state(
 7509        &r#"
 7510            <p>A«</p>
 7511            <p>ˇ»B</p>ˇ
 7512            <p>C«</p>
 7513            <p>ˇ»D</p>ˇ
 7514        "#
 7515        .unindent(),
 7516    );
 7517
 7518    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7519    cx.assert_editor_state(
 7520        &r#"
 7521            <!-- <p>A«</p>
 7522            <p>ˇ»B</p>ˇ -->
 7523            <!-- <p>C«</p>
 7524            <p>ˇ»D</p>ˇ -->
 7525        "#
 7526        .unindent(),
 7527    );
 7528    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7529    cx.assert_editor_state(
 7530        &r#"
 7531            <p>A«</p>
 7532            <p>ˇ»B</p>ˇ
 7533            <p>C«</p>
 7534            <p>ˇ»D</p>ˇ
 7535        "#
 7536        .unindent(),
 7537    );
 7538
 7539    // Toggle comments when different languages are active for different
 7540    // selections.
 7541    cx.set_state(
 7542        &r#"
 7543            ˇ<script>
 7544                ˇvar x = new Y();
 7545            ˇ</script>
 7546        "#
 7547        .unindent(),
 7548    );
 7549    cx.executor().run_until_parked();
 7550    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7551    cx.assert_editor_state(
 7552        &r#"
 7553            <!-- ˇ<script> -->
 7554                // ˇvar x = new Y();
 7555            <!-- ˇ</script> -->
 7556        "#
 7557        .unindent(),
 7558    );
 7559}
 7560
 7561#[gpui::test]
 7562fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
 7563    init_test(cx, |_| {});
 7564
 7565    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7566    let multibuffer = cx.new_model(|cx| {
 7567        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7568        multibuffer.push_excerpts(
 7569            buffer.clone(),
 7570            [
 7571                ExcerptRange {
 7572                    context: Point::new(0, 0)..Point::new(0, 4),
 7573                    primary: None,
 7574                },
 7575                ExcerptRange {
 7576                    context: Point::new(1, 0)..Point::new(1, 4),
 7577                    primary: None,
 7578                },
 7579            ],
 7580            cx,
 7581        );
 7582        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
 7583        multibuffer
 7584    });
 7585
 7586    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7587    _ = view.update(cx, |view, cx| {
 7588        assert_eq!(view.text(cx), "aaaa\nbbbb");
 7589        view.change_selections(None, cx, |s| {
 7590            s.select_ranges([
 7591                Point::new(0, 0)..Point::new(0, 0),
 7592                Point::new(1, 0)..Point::new(1, 0),
 7593            ])
 7594        });
 7595
 7596        view.handle_input("X", cx);
 7597        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
 7598        assert_eq!(
 7599            view.selections.ranges(cx),
 7600            [
 7601                Point::new(0, 1)..Point::new(0, 1),
 7602                Point::new(1, 1)..Point::new(1, 1),
 7603            ]
 7604        );
 7605
 7606        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
 7607        view.change_selections(None, cx, |s| {
 7608            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
 7609        });
 7610        view.backspace(&Default::default(), cx);
 7611        assert_eq!(view.text(cx), "Xa\nbbb");
 7612        assert_eq!(
 7613            view.selections.ranges(cx),
 7614            [Point::new(1, 0)..Point::new(1, 0)]
 7615        );
 7616
 7617        view.change_selections(None, cx, |s| {
 7618            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
 7619        });
 7620        view.backspace(&Default::default(), cx);
 7621        assert_eq!(view.text(cx), "X\nbb");
 7622        assert_eq!(
 7623            view.selections.ranges(cx),
 7624            [Point::new(0, 1)..Point::new(0, 1)]
 7625        );
 7626    });
 7627}
 7628
 7629#[gpui::test]
 7630fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 7631    init_test(cx, |_| {});
 7632
 7633    let markers = vec![('[', ']').into(), ('(', ')').into()];
 7634    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
 7635        indoc! {"
 7636            [aaaa
 7637            (bbbb]
 7638            cccc)",
 7639        },
 7640        markers.clone(),
 7641    );
 7642    let excerpt_ranges = markers.into_iter().map(|marker| {
 7643        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
 7644        ExcerptRange {
 7645            context,
 7646            primary: None,
 7647        }
 7648    });
 7649    let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
 7650    let multibuffer = cx.new_model(|cx| {
 7651        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7652        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
 7653        multibuffer
 7654    });
 7655
 7656    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7657    _ = view.update(cx, |view, cx| {
 7658        let (expected_text, selection_ranges) = marked_text_ranges(
 7659            indoc! {"
 7660                aaaa
 7661                bˇbbb
 7662                bˇbbˇb
 7663                cccc"
 7664            },
 7665            true,
 7666        );
 7667        assert_eq!(view.text(cx), expected_text);
 7668        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 7669
 7670        view.handle_input("X", cx);
 7671
 7672        let (expected_text, expected_selections) = marked_text_ranges(
 7673            indoc! {"
 7674                aaaa
 7675                bXˇbbXb
 7676                bXˇbbXˇb
 7677                cccc"
 7678            },
 7679            false,
 7680        );
 7681        assert_eq!(view.text(cx), expected_text);
 7682        assert_eq!(view.selections.ranges(cx), expected_selections);
 7683
 7684        view.newline(&Newline, cx);
 7685        let (expected_text, expected_selections) = marked_text_ranges(
 7686            indoc! {"
 7687                aaaa
 7688                bX
 7689                ˇbbX
 7690                b
 7691                bX
 7692                ˇbbX
 7693                ˇb
 7694                cccc"
 7695            },
 7696            false,
 7697        );
 7698        assert_eq!(view.text(cx), expected_text);
 7699        assert_eq!(view.selections.ranges(cx), expected_selections);
 7700    });
 7701}
 7702
 7703#[gpui::test]
 7704fn test_refresh_selections(cx: &mut TestAppContext) {
 7705    init_test(cx, |_| {});
 7706
 7707    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7708    let mut excerpt1_id = None;
 7709    let multibuffer = cx.new_model(|cx| {
 7710        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7711        excerpt1_id = multibuffer
 7712            .push_excerpts(
 7713                buffer.clone(),
 7714                [
 7715                    ExcerptRange {
 7716                        context: Point::new(0, 0)..Point::new(1, 4),
 7717                        primary: None,
 7718                    },
 7719                    ExcerptRange {
 7720                        context: Point::new(1, 0)..Point::new(2, 4),
 7721                        primary: None,
 7722                    },
 7723                ],
 7724                cx,
 7725            )
 7726            .into_iter()
 7727            .next();
 7728        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7729        multibuffer
 7730    });
 7731
 7732    let editor = cx.add_window(|cx| {
 7733        let mut editor = build_editor(multibuffer.clone(), cx);
 7734        let snapshot = editor.snapshot(cx);
 7735        editor.change_selections(None, cx, |s| {
 7736            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
 7737        });
 7738        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
 7739        assert_eq!(
 7740            editor.selections.ranges(cx),
 7741            [
 7742                Point::new(1, 3)..Point::new(1, 3),
 7743                Point::new(2, 1)..Point::new(2, 1),
 7744            ]
 7745        );
 7746        editor
 7747    });
 7748
 7749    // Refreshing selections is a no-op when excerpts haven't changed.
 7750    _ = editor.update(cx, |editor, cx| {
 7751        editor.change_selections(None, cx, |s| s.refresh());
 7752        assert_eq!(
 7753            editor.selections.ranges(cx),
 7754            [
 7755                Point::new(1, 3)..Point::new(1, 3),
 7756                Point::new(2, 1)..Point::new(2, 1),
 7757            ]
 7758        );
 7759    });
 7760
 7761    _ = multibuffer.update(cx, |multibuffer, cx| {
 7762        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7763    });
 7764    _ = editor.update(cx, |editor, cx| {
 7765        // Removing an excerpt causes the first selection to become degenerate.
 7766        assert_eq!(
 7767            editor.selections.ranges(cx),
 7768            [
 7769                Point::new(0, 0)..Point::new(0, 0),
 7770                Point::new(0, 1)..Point::new(0, 1)
 7771            ]
 7772        );
 7773
 7774        // Refreshing selections will relocate the first selection to the original buffer
 7775        // location.
 7776        editor.change_selections(None, cx, |s| s.refresh());
 7777        assert_eq!(
 7778            editor.selections.ranges(cx),
 7779            [
 7780                Point::new(0, 1)..Point::new(0, 1),
 7781                Point::new(0, 3)..Point::new(0, 3)
 7782            ]
 7783        );
 7784        assert!(editor.selections.pending_anchor().is_some());
 7785    });
 7786}
 7787
 7788#[gpui::test]
 7789fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
 7790    init_test(cx, |_| {});
 7791
 7792    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7793    let mut excerpt1_id = None;
 7794    let multibuffer = cx.new_model(|cx| {
 7795        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7796        excerpt1_id = multibuffer
 7797            .push_excerpts(
 7798                buffer.clone(),
 7799                [
 7800                    ExcerptRange {
 7801                        context: Point::new(0, 0)..Point::new(1, 4),
 7802                        primary: None,
 7803                    },
 7804                    ExcerptRange {
 7805                        context: Point::new(1, 0)..Point::new(2, 4),
 7806                        primary: None,
 7807                    },
 7808                ],
 7809                cx,
 7810            )
 7811            .into_iter()
 7812            .next();
 7813        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7814        multibuffer
 7815    });
 7816
 7817    let editor = cx.add_window(|cx| {
 7818        let mut editor = build_editor(multibuffer.clone(), cx);
 7819        let snapshot = editor.snapshot(cx);
 7820        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
 7821        assert_eq!(
 7822            editor.selections.ranges(cx),
 7823            [Point::new(1, 3)..Point::new(1, 3)]
 7824        );
 7825        editor
 7826    });
 7827
 7828    _ = multibuffer.update(cx, |multibuffer, cx| {
 7829        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7830    });
 7831    _ = editor.update(cx, |editor, cx| {
 7832        assert_eq!(
 7833            editor.selections.ranges(cx),
 7834            [Point::new(0, 0)..Point::new(0, 0)]
 7835        );
 7836
 7837        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
 7838        editor.change_selections(None, cx, |s| s.refresh());
 7839        assert_eq!(
 7840            editor.selections.ranges(cx),
 7841            [Point::new(0, 3)..Point::new(0, 3)]
 7842        );
 7843        assert!(editor.selections.pending_anchor().is_some());
 7844    });
 7845}
 7846
 7847#[gpui::test]
 7848async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 7849    init_test(cx, |_| {});
 7850
 7851    let language = Arc::new(
 7852        Language::new(
 7853            LanguageConfig {
 7854                brackets: BracketPairConfig {
 7855                    pairs: vec![
 7856                        BracketPair {
 7857                            start: "{".to_string(),
 7858                            end: "}".to_string(),
 7859                            close: true,
 7860                            surround: true,
 7861                            newline: true,
 7862                        },
 7863                        BracketPair {
 7864                            start: "/* ".to_string(),
 7865                            end: " */".to_string(),
 7866                            close: true,
 7867                            surround: true,
 7868                            newline: true,
 7869                        },
 7870                    ],
 7871                    ..Default::default()
 7872                },
 7873                ..Default::default()
 7874            },
 7875            Some(tree_sitter_rust::language()),
 7876        )
 7877        .with_indents_query("")
 7878        .unwrap(),
 7879    );
 7880
 7881    let text = concat!(
 7882        "{   }\n",     //
 7883        "  x\n",       //
 7884        "  /*   */\n", //
 7885        "x\n",         //
 7886        "{{} }\n",     //
 7887    );
 7888
 7889    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 7890    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 7891    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 7892    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 7893        .await;
 7894
 7895    _ = view.update(cx, |view, cx| {
 7896        view.change_selections(None, cx, |s| {
 7897            s.select_display_ranges([
 7898                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 7899                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7900                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7901            ])
 7902        });
 7903        view.newline(&Newline, cx);
 7904
 7905        assert_eq!(
 7906            view.buffer().read(cx).read(cx).text(),
 7907            concat!(
 7908                "{ \n",    // Suppress rustfmt
 7909                "\n",      //
 7910                "}\n",     //
 7911                "  x\n",   //
 7912                "  /* \n", //
 7913                "  \n",    //
 7914                "  */\n",  //
 7915                "x\n",     //
 7916                "{{} \n",  //
 7917                "}\n",     //
 7918            )
 7919        );
 7920    });
 7921}
 7922
 7923#[gpui::test]
 7924fn test_highlighted_ranges(cx: &mut TestAppContext) {
 7925    init_test(cx, |_| {});
 7926
 7927    let editor = cx.add_window(|cx| {
 7928        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
 7929        build_editor(buffer.clone(), cx)
 7930    });
 7931
 7932    _ = editor.update(cx, |editor, cx| {
 7933        struct Type1;
 7934        struct Type2;
 7935
 7936        let buffer = editor.buffer.read(cx).snapshot(cx);
 7937
 7938        let anchor_range =
 7939            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
 7940
 7941        editor.highlight_background::<Type1>(
 7942            &[
 7943                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
 7944                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
 7945                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
 7946                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
 7947            ],
 7948            |_| Hsla::red(),
 7949            cx,
 7950        );
 7951        editor.highlight_background::<Type2>(
 7952            &[
 7953                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
 7954                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
 7955                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
 7956                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
 7957            ],
 7958            |_| Hsla::green(),
 7959            cx,
 7960        );
 7961
 7962        let snapshot = editor.snapshot(cx);
 7963        let mut highlighted_ranges = editor.background_highlights_in_range(
 7964            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
 7965            &snapshot,
 7966            cx.theme().colors(),
 7967        );
 7968        // Enforce a consistent ordering based on color without relying on the ordering of the
 7969        // highlight's `TypeId` which is non-executor.
 7970        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
 7971        assert_eq!(
 7972            highlighted_ranges,
 7973            &[
 7974                (
 7975                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
 7976                    Hsla::red(),
 7977                ),
 7978                (
 7979                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
 7980                    Hsla::red(),
 7981                ),
 7982                (
 7983                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
 7984                    Hsla::green(),
 7985                ),
 7986                (
 7987                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
 7988                    Hsla::green(),
 7989                ),
 7990            ]
 7991        );
 7992        assert_eq!(
 7993            editor.background_highlights_in_range(
 7994                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
 7995                &snapshot,
 7996                cx.theme().colors(),
 7997            ),
 7998            &[(
 7999                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
 8000                Hsla::red(),
 8001            )]
 8002        );
 8003    });
 8004}
 8005
 8006#[gpui::test]
 8007async fn test_following(cx: &mut gpui::TestAppContext) {
 8008    init_test(cx, |_| {});
 8009
 8010    let fs = FakeFs::new(cx.executor());
 8011    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 8012
 8013    let buffer = project.update(cx, |project, cx| {
 8014        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
 8015        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
 8016    });
 8017    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 8018    let follower = cx.update(|cx| {
 8019        cx.open_window(
 8020            WindowOptions {
 8021                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
 8022                    gpui::Point::new(px(0.), px(0.)),
 8023                    gpui::Point::new(px(10.), px(80.)),
 8024                ))),
 8025                ..Default::default()
 8026            },
 8027            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
 8028        )
 8029        .unwrap()
 8030    });
 8031
 8032    let is_still_following = Rc::new(RefCell::new(true));
 8033    let follower_edit_event_count = Rc::new(RefCell::new(0));
 8034    let pending_update = Rc::new(RefCell::new(None));
 8035    _ = follower.update(cx, {
 8036        let update = pending_update.clone();
 8037        let is_still_following = is_still_following.clone();
 8038        let follower_edit_event_count = follower_edit_event_count.clone();
 8039        |_, cx| {
 8040            cx.subscribe(
 8041                &leader.root_view(cx).unwrap(),
 8042                move |_, leader, event, cx| {
 8043                    leader
 8044                        .read(cx)
 8045                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 8046                },
 8047            )
 8048            .detach();
 8049
 8050            cx.subscribe(
 8051                &follower.root_view(cx).unwrap(),
 8052                move |_, _, event: &EditorEvent, _cx| {
 8053                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
 8054                        *is_still_following.borrow_mut() = false;
 8055                    }
 8056
 8057                    if let EditorEvent::BufferEdited = event {
 8058                        *follower_edit_event_count.borrow_mut() += 1;
 8059                    }
 8060                },
 8061            )
 8062            .detach();
 8063        }
 8064    });
 8065
 8066    // Update the selections only
 8067    _ = leader.update(cx, |leader, cx| {
 8068        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 8069    });
 8070    follower
 8071        .update(cx, |follower, cx| {
 8072            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8073        })
 8074        .unwrap()
 8075        .await
 8076        .unwrap();
 8077    _ = follower.update(cx, |follower, cx| {
 8078        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
 8079    });
 8080    assert_eq!(*is_still_following.borrow(), true);
 8081    assert_eq!(*follower_edit_event_count.borrow(), 0);
 8082
 8083    // Update the scroll position only
 8084    _ = leader.update(cx, |leader, cx| {
 8085        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 8086    });
 8087    follower
 8088        .update(cx, |follower, cx| {
 8089            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8090        })
 8091        .unwrap()
 8092        .await
 8093        .unwrap();
 8094    assert_eq!(
 8095        follower
 8096            .update(cx, |follower, cx| follower.scroll_position(cx))
 8097            .unwrap(),
 8098        gpui::Point::new(1.5, 3.5)
 8099    );
 8100    assert_eq!(*is_still_following.borrow(), true);
 8101    assert_eq!(*follower_edit_event_count.borrow(), 0);
 8102
 8103    // Update the selections and scroll position. The follower's scroll position is updated
 8104    // via autoscroll, not via the leader's exact scroll position.
 8105    _ = leader.update(cx, |leader, cx| {
 8106        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
 8107        leader.request_autoscroll(Autoscroll::newest(), cx);
 8108        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 8109    });
 8110    follower
 8111        .update(cx, |follower, cx| {
 8112            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8113        })
 8114        .unwrap()
 8115        .await
 8116        .unwrap();
 8117    _ = follower.update(cx, |follower, cx| {
 8118        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
 8119        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
 8120    });
 8121    assert_eq!(*is_still_following.borrow(), true);
 8122
 8123    // Creating a pending selection that precedes another selection
 8124    _ = leader.update(cx, |leader, cx| {
 8125        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 8126        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
 8127    });
 8128    follower
 8129        .update(cx, |follower, cx| {
 8130            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8131        })
 8132        .unwrap()
 8133        .await
 8134        .unwrap();
 8135    _ = follower.update(cx, |follower, cx| {
 8136        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
 8137    });
 8138    assert_eq!(*is_still_following.borrow(), true);
 8139
 8140    // Extend the pending selection so that it surrounds another selection
 8141    _ = leader.update(cx, |leader, cx| {
 8142        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
 8143    });
 8144    follower
 8145        .update(cx, |follower, cx| {
 8146            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 8147        })
 8148        .unwrap()
 8149        .await
 8150        .unwrap();
 8151    _ = follower.update(cx, |follower, cx| {
 8152        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
 8153    });
 8154
 8155    // Scrolling locally breaks the follow
 8156    _ = follower.update(cx, |follower, cx| {
 8157        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
 8158        follower.set_scroll_anchor(
 8159            ScrollAnchor {
 8160                anchor: top_anchor,
 8161                offset: gpui::Point::new(0.0, 0.5),
 8162            },
 8163            cx,
 8164        );
 8165    });
 8166    assert_eq!(*is_still_following.borrow(), false);
 8167}
 8168
 8169#[gpui::test]
 8170async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 8171    init_test(cx, |_| {});
 8172
 8173    let fs = FakeFs::new(cx.executor());
 8174    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 8175    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8176    let pane = workspace
 8177        .update(cx, |workspace, _| workspace.active_pane().clone())
 8178        .unwrap();
 8179
 8180    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8181
 8182    let leader = pane.update(cx, |_, cx| {
 8183        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
 8184        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
 8185    });
 8186
 8187    // Start following the editor when it has no excerpts.
 8188    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 8189    let follower_1 = cx
 8190        .update_window(*workspace.deref(), |_, cx| {
 8191            Editor::from_state_proto(
 8192                pane.clone(),
 8193                workspace.root_view(cx).unwrap(),
 8194                ViewId {
 8195                    creator: Default::default(),
 8196                    id: 0,
 8197                },
 8198                &mut state_message,
 8199                cx,
 8200            )
 8201        })
 8202        .unwrap()
 8203        .unwrap()
 8204        .await
 8205        .unwrap();
 8206
 8207    let update_message = Rc::new(RefCell::new(None));
 8208    follower_1.update(cx, {
 8209        let update = update_message.clone();
 8210        |_, cx| {
 8211            cx.subscribe(&leader, move |_, leader, event, cx| {
 8212                leader
 8213                    .read(cx)
 8214                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 8215            })
 8216            .detach();
 8217        }
 8218    });
 8219
 8220    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
 8221        (
 8222            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
 8223            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
 8224        )
 8225    });
 8226
 8227    // Insert some excerpts.
 8228    _ = leader.update(cx, |leader, cx| {
 8229        leader.buffer.update(cx, |multibuffer, cx| {
 8230            let excerpt_ids = multibuffer.push_excerpts(
 8231                buffer_1.clone(),
 8232                [
 8233                    ExcerptRange {
 8234                        context: 1..6,
 8235                        primary: None,
 8236                    },
 8237                    ExcerptRange {
 8238                        context: 12..15,
 8239                        primary: None,
 8240                    },
 8241                    ExcerptRange {
 8242                        context: 0..3,
 8243                        primary: None,
 8244                    },
 8245                ],
 8246                cx,
 8247            );
 8248            multibuffer.insert_excerpts_after(
 8249                excerpt_ids[0],
 8250                buffer_2.clone(),
 8251                [
 8252                    ExcerptRange {
 8253                        context: 8..12,
 8254                        primary: None,
 8255                    },
 8256                    ExcerptRange {
 8257                        context: 0..6,
 8258                        primary: None,
 8259                    },
 8260                ],
 8261                cx,
 8262            );
 8263        });
 8264    });
 8265
 8266    // Apply the update of adding the excerpts.
 8267    follower_1
 8268        .update(cx, |follower, cx| {
 8269            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8270        })
 8271        .await
 8272        .unwrap();
 8273    assert_eq!(
 8274        follower_1.update(cx, |editor, cx| editor.text(cx)),
 8275        leader.update(cx, |editor, cx| editor.text(cx))
 8276    );
 8277    update_message.borrow_mut().take();
 8278
 8279    // Start following separately after it already has excerpts.
 8280    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 8281    let follower_2 = cx
 8282        .update_window(*workspace.deref(), |_, cx| {
 8283            Editor::from_state_proto(
 8284                pane.clone(),
 8285                workspace.root_view(cx).unwrap().clone(),
 8286                ViewId {
 8287                    creator: Default::default(),
 8288                    id: 0,
 8289                },
 8290                &mut state_message,
 8291                cx,
 8292            )
 8293        })
 8294        .unwrap()
 8295        .unwrap()
 8296        .await
 8297        .unwrap();
 8298    assert_eq!(
 8299        follower_2.update(cx, |editor, cx| editor.text(cx)),
 8300        leader.update(cx, |editor, cx| editor.text(cx))
 8301    );
 8302
 8303    // Remove some excerpts.
 8304    _ = leader.update(cx, |leader, cx| {
 8305        leader.buffer.update(cx, |multibuffer, cx| {
 8306            let excerpt_ids = multibuffer.excerpt_ids();
 8307            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
 8308            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
 8309        });
 8310    });
 8311
 8312    // Apply the update of removing the excerpts.
 8313    follower_1
 8314        .update(cx, |follower, cx| {
 8315            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8316        })
 8317        .await
 8318        .unwrap();
 8319    follower_2
 8320        .update(cx, |follower, cx| {
 8321            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 8322        })
 8323        .await
 8324        .unwrap();
 8325    update_message.borrow_mut().take();
 8326    assert_eq!(
 8327        follower_1.update(cx, |editor, cx| editor.text(cx)),
 8328        leader.update(cx, |editor, cx| editor.text(cx))
 8329    );
 8330}
 8331
 8332#[gpui::test]
 8333async fn go_to_prev_overlapping_diagnostic(
 8334    executor: BackgroundExecutor,
 8335    cx: &mut gpui::TestAppContext,
 8336) {
 8337    init_test(cx, |_| {});
 8338
 8339    let mut cx = EditorTestContext::new(cx).await;
 8340    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
 8341
 8342    cx.set_state(indoc! {"
 8343        ˇfn func(abc def: i32) -> u32 {
 8344        }
 8345    "});
 8346
 8347    _ = cx.update(|cx| {
 8348        _ = project.update(cx, |project, cx| {
 8349            project
 8350                .update_diagnostics(
 8351                    LanguageServerId(0),
 8352                    lsp::PublishDiagnosticsParams {
 8353                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
 8354                        version: None,
 8355                        diagnostics: vec![
 8356                            lsp::Diagnostic {
 8357                                range: lsp::Range::new(
 8358                                    lsp::Position::new(0, 11),
 8359                                    lsp::Position::new(0, 12),
 8360                                ),
 8361                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8362                                ..Default::default()
 8363                            },
 8364                            lsp::Diagnostic {
 8365                                range: lsp::Range::new(
 8366                                    lsp::Position::new(0, 12),
 8367                                    lsp::Position::new(0, 15),
 8368                                ),
 8369                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8370                                ..Default::default()
 8371                            },
 8372                            lsp::Diagnostic {
 8373                                range: lsp::Range::new(
 8374                                    lsp::Position::new(0, 25),
 8375                                    lsp::Position::new(0, 28),
 8376                                ),
 8377                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 8378                                ..Default::default()
 8379                            },
 8380                        ],
 8381                    },
 8382                    &[],
 8383                    cx,
 8384                )
 8385                .unwrap()
 8386        });
 8387    });
 8388
 8389    executor.run_until_parked();
 8390
 8391    cx.update_editor(|editor, cx| {
 8392        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8393    });
 8394
 8395    cx.assert_editor_state(indoc! {"
 8396        fn func(abc def: i32) -> ˇu32 {
 8397        }
 8398    "});
 8399
 8400    cx.update_editor(|editor, cx| {
 8401        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8402    });
 8403
 8404    cx.assert_editor_state(indoc! {"
 8405        fn func(abc ˇdef: i32) -> u32 {
 8406        }
 8407    "});
 8408
 8409    cx.update_editor(|editor, cx| {
 8410        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8411    });
 8412
 8413    cx.assert_editor_state(indoc! {"
 8414        fn func(abcˇ def: i32) -> u32 {
 8415        }
 8416    "});
 8417
 8418    cx.update_editor(|editor, cx| {
 8419        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 8420    });
 8421
 8422    cx.assert_editor_state(indoc! {"
 8423        fn func(abc def: i32) -> ˇu32 {
 8424        }
 8425    "});
 8426}
 8427
 8428#[gpui::test]
 8429async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 8430    init_test(cx, |_| {});
 8431
 8432    let mut cx = EditorTestContext::new(cx).await;
 8433
 8434    let diff_base = r#"
 8435        use some::mod;
 8436
 8437        const A: u32 = 42;
 8438
 8439        fn main() {
 8440            println!("hello");
 8441
 8442            println!("world");
 8443        }
 8444        "#
 8445    .unindent();
 8446
 8447    // Edits are modified, removed, modified, added
 8448    cx.set_state(
 8449        &r#"
 8450        use some::modified;
 8451
 8452        ˇ
 8453        fn main() {
 8454            println!("hello there");
 8455
 8456            println!("around the");
 8457            println!("world");
 8458        }
 8459        "#
 8460        .unindent(),
 8461    );
 8462
 8463    cx.set_diff_base(Some(&diff_base));
 8464    executor.run_until_parked();
 8465
 8466    cx.update_editor(|editor, cx| {
 8467        //Wrap around the bottom of the buffer
 8468        for _ in 0..3 {
 8469            editor.go_to_hunk(&GoToHunk, cx);
 8470        }
 8471    });
 8472
 8473    cx.assert_editor_state(
 8474        &r#"
 8475        ˇuse some::modified;
 8476
 8477
 8478        fn main() {
 8479            println!("hello there");
 8480
 8481            println!("around the");
 8482            println!("world");
 8483        }
 8484        "#
 8485        .unindent(),
 8486    );
 8487
 8488    cx.update_editor(|editor, cx| {
 8489        //Wrap around the top of the buffer
 8490        for _ in 0..2 {
 8491            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8492        }
 8493    });
 8494
 8495    cx.assert_editor_state(
 8496        &r#"
 8497        use some::modified;
 8498
 8499
 8500        fn main() {
 8501        ˇ    println!("hello there");
 8502
 8503            println!("around the");
 8504            println!("world");
 8505        }
 8506        "#
 8507        .unindent(),
 8508    );
 8509
 8510    cx.update_editor(|editor, cx| {
 8511        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8512    });
 8513
 8514    cx.assert_editor_state(
 8515        &r#"
 8516        use some::modified;
 8517
 8518        ˇ
 8519        fn main() {
 8520            println!("hello there");
 8521
 8522            println!("around the");
 8523            println!("world");
 8524        }
 8525        "#
 8526        .unindent(),
 8527    );
 8528
 8529    cx.update_editor(|editor, cx| {
 8530        for _ in 0..3 {
 8531            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 8532        }
 8533    });
 8534
 8535    cx.assert_editor_state(
 8536        &r#"
 8537        use some::modified;
 8538
 8539
 8540        fn main() {
 8541        ˇ    println!("hello there");
 8542
 8543            println!("around the");
 8544            println!("world");
 8545        }
 8546        "#
 8547        .unindent(),
 8548    );
 8549
 8550    cx.update_editor(|editor, cx| {
 8551        editor.fold(&Fold, cx);
 8552
 8553        //Make sure that the fold only gets one hunk
 8554        for _ in 0..4 {
 8555            editor.go_to_hunk(&GoToHunk, cx);
 8556        }
 8557    });
 8558
 8559    cx.assert_editor_state(
 8560        &r#"
 8561        ˇuse some::modified;
 8562
 8563
 8564        fn main() {
 8565            println!("hello there");
 8566
 8567            println!("around the");
 8568            println!("world");
 8569        }
 8570        "#
 8571        .unindent(),
 8572    );
 8573}
 8574
 8575#[test]
 8576fn test_split_words() {
 8577    fn split(text: &str) -> Vec<&str> {
 8578        split_words(text).collect()
 8579    }
 8580
 8581    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
 8582    assert_eq!(split("hello_world"), &["hello_", "world"]);
 8583    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
 8584    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
 8585    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
 8586    assert_eq!(split("helloworld"), &["helloworld"]);
 8587
 8588    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
 8589}
 8590
 8591#[gpui::test]
 8592async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
 8593    init_test(cx, |_| {});
 8594
 8595    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
 8596    let mut assert = |before, after| {
 8597        let _state_context = cx.set_state(before);
 8598        cx.update_editor(|editor, cx| {
 8599            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
 8600        });
 8601        cx.assert_editor_state(after);
 8602    };
 8603
 8604    // Outside bracket jumps to outside of matching bracket
 8605    assert("console.logˇ(var);", "console.log(var)ˇ;");
 8606    assert("console.log(var)ˇ;", "console.logˇ(var);");
 8607
 8608    // Inside bracket jumps to inside of matching bracket
 8609    assert("console.log(ˇvar);", "console.log(varˇ);");
 8610    assert("console.log(varˇ);", "console.log(ˇvar);");
 8611
 8612    // When outside a bracket and inside, favor jumping to the inside bracket
 8613    assert(
 8614        "console.log('foo', [1, 2, 3]ˇ);",
 8615        "console.log(ˇ'foo', [1, 2, 3]);",
 8616    );
 8617    assert(
 8618        "console.log(ˇ'foo', [1, 2, 3]);",
 8619        "console.log('foo', [1, 2, 3]ˇ);",
 8620    );
 8621
 8622    // Bias forward if two options are equally likely
 8623    assert(
 8624        "let result = curried_fun()ˇ();",
 8625        "let result = curried_fun()()ˇ;",
 8626    );
 8627
 8628    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
 8629    assert(
 8630        indoc! {"
 8631            function test() {
 8632                console.log('test')ˇ
 8633            }"},
 8634        indoc! {"
 8635            function test() {
 8636                console.logˇ('test')
 8637            }"},
 8638    );
 8639}
 8640
 8641#[gpui::test]
 8642async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
 8643    init_test(cx, |_| {});
 8644
 8645    let fs = FakeFs::new(cx.executor());
 8646    fs.insert_tree(
 8647        "/a",
 8648        json!({
 8649            "main.rs": "fn main() { let a = 5; }",
 8650            "other.rs": "// Test file",
 8651        }),
 8652    )
 8653    .await;
 8654    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8655
 8656    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8657    language_registry.add(Arc::new(Language::new(
 8658        LanguageConfig {
 8659            name: "Rust".into(),
 8660            matcher: LanguageMatcher {
 8661                path_suffixes: vec!["rs".to_string()],
 8662                ..Default::default()
 8663            },
 8664            brackets: BracketPairConfig {
 8665                pairs: vec![BracketPair {
 8666                    start: "{".to_string(),
 8667                    end: "}".to_string(),
 8668                    close: true,
 8669                    surround: true,
 8670                    newline: true,
 8671                }],
 8672                disabled_scopes_by_bracket_ix: Vec::new(),
 8673            },
 8674            ..Default::default()
 8675        },
 8676        Some(tree_sitter_rust::language()),
 8677    )));
 8678    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8679        "Rust",
 8680        FakeLspAdapter {
 8681            capabilities: lsp::ServerCapabilities {
 8682                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
 8683                    first_trigger_character: "{".to_string(),
 8684                    more_trigger_character: None,
 8685                }),
 8686                ..Default::default()
 8687            },
 8688            ..Default::default()
 8689        },
 8690    );
 8691
 8692    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8693
 8694    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 8695
 8696    let worktree_id = workspace
 8697        .update(cx, |workspace, cx| {
 8698            workspace.project().update(cx, |project, cx| {
 8699                project.worktrees().next().unwrap().read(cx).id()
 8700            })
 8701        })
 8702        .unwrap();
 8703
 8704    let buffer = project
 8705        .update(cx, |project, cx| {
 8706            project.open_local_buffer("/a/main.rs", cx)
 8707        })
 8708        .await
 8709        .unwrap();
 8710    cx.executor().run_until_parked();
 8711    cx.executor().start_waiting();
 8712    let fake_server = fake_servers.next().await.unwrap();
 8713    let editor_handle = workspace
 8714        .update(cx, |workspace, cx| {
 8715            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 8716        })
 8717        .unwrap()
 8718        .await
 8719        .unwrap()
 8720        .downcast::<Editor>()
 8721        .unwrap();
 8722
 8723    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
 8724        assert_eq!(
 8725            params.text_document_position.text_document.uri,
 8726            lsp::Url::from_file_path("/a/main.rs").unwrap(),
 8727        );
 8728        assert_eq!(
 8729            params.text_document_position.position,
 8730            lsp::Position::new(0, 21),
 8731        );
 8732
 8733        Ok(Some(vec![lsp::TextEdit {
 8734            new_text: "]".to_string(),
 8735            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
 8736        }]))
 8737    });
 8738
 8739    editor_handle.update(cx, |editor, cx| {
 8740        editor.focus(cx);
 8741        editor.change_selections(None, cx, |s| {
 8742            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
 8743        });
 8744        editor.handle_input("{", cx);
 8745    });
 8746
 8747    cx.executor().run_until_parked();
 8748
 8749    _ = buffer.update(cx, |buffer, _| {
 8750        assert_eq!(
 8751            buffer.text(),
 8752            "fn main() { let a = {5}; }",
 8753            "No extra braces from on type formatting should appear in the buffer"
 8754        )
 8755    });
 8756}
 8757
 8758#[gpui::test]
 8759async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
 8760    init_test(cx, |_| {});
 8761
 8762    let fs = FakeFs::new(cx.executor());
 8763    fs.insert_tree(
 8764        "/a",
 8765        json!({
 8766            "main.rs": "fn main() { let a = 5; }",
 8767            "other.rs": "// Test file",
 8768        }),
 8769    )
 8770    .await;
 8771
 8772    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8773
 8774    let server_restarts = Arc::new(AtomicUsize::new(0));
 8775    let closure_restarts = Arc::clone(&server_restarts);
 8776    let language_server_name = "test language server";
 8777    let language_name: Arc<str> = "Rust".into();
 8778
 8779    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8780    language_registry.add(Arc::new(Language::new(
 8781        LanguageConfig {
 8782            name: Arc::clone(&language_name),
 8783            matcher: LanguageMatcher {
 8784                path_suffixes: vec!["rs".to_string()],
 8785                ..Default::default()
 8786            },
 8787            ..Default::default()
 8788        },
 8789        Some(tree_sitter_rust::language()),
 8790    )));
 8791    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8792        "Rust",
 8793        FakeLspAdapter {
 8794            name: language_server_name,
 8795            initialization_options: Some(json!({
 8796                "testOptionValue": true
 8797            })),
 8798            initializer: Some(Box::new(move |fake_server| {
 8799                let task_restarts = Arc::clone(&closure_restarts);
 8800                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
 8801                    task_restarts.fetch_add(1, atomic::Ordering::Release);
 8802                    futures::future::ready(Ok(()))
 8803                });
 8804            })),
 8805            ..Default::default()
 8806        },
 8807    );
 8808
 8809    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8810    let _buffer = project
 8811        .update(cx, |project, cx| {
 8812            project.open_local_buffer("/a/main.rs", cx)
 8813        })
 8814        .await
 8815        .unwrap();
 8816    let _fake_server = fake_servers.next().await.unwrap();
 8817    update_test_language_settings(cx, |language_settings| {
 8818        language_settings.languages.insert(
 8819            Arc::clone(&language_name),
 8820            LanguageSettingsContent {
 8821                tab_size: NonZeroU32::new(8),
 8822                ..Default::default()
 8823            },
 8824        );
 8825    });
 8826    cx.executor().run_until_parked();
 8827    assert_eq!(
 8828        server_restarts.load(atomic::Ordering::Acquire),
 8829        0,
 8830        "Should not restart LSP server on an unrelated change"
 8831    );
 8832
 8833    update_test_project_settings(cx, |project_settings| {
 8834        project_settings.lsp.insert(
 8835            "Some other server name".into(),
 8836            LspSettings {
 8837                binary: None,
 8838                settings: None,
 8839                initialization_options: Some(json!({
 8840                    "some other init value": false
 8841                })),
 8842            },
 8843        );
 8844    });
 8845    cx.executor().run_until_parked();
 8846    assert_eq!(
 8847        server_restarts.load(atomic::Ordering::Acquire),
 8848        0,
 8849        "Should not restart LSP server on an unrelated LSP settings change"
 8850    );
 8851
 8852    update_test_project_settings(cx, |project_settings| {
 8853        project_settings.lsp.insert(
 8854            language_server_name.into(),
 8855            LspSettings {
 8856                binary: None,
 8857                settings: None,
 8858                initialization_options: Some(json!({
 8859                    "anotherInitValue": false
 8860                })),
 8861            },
 8862        );
 8863    });
 8864    cx.executor().run_until_parked();
 8865    assert_eq!(
 8866        server_restarts.load(atomic::Ordering::Acquire),
 8867        1,
 8868        "Should restart LSP server on a related LSP settings change"
 8869    );
 8870
 8871    update_test_project_settings(cx, |project_settings| {
 8872        project_settings.lsp.insert(
 8873            language_server_name.into(),
 8874            LspSettings {
 8875                binary: None,
 8876                settings: None,
 8877                initialization_options: Some(json!({
 8878                    "anotherInitValue": false
 8879                })),
 8880            },
 8881        );
 8882    });
 8883    cx.executor().run_until_parked();
 8884    assert_eq!(
 8885        server_restarts.load(atomic::Ordering::Acquire),
 8886        1,
 8887        "Should not restart LSP server on a related LSP settings change that is the same"
 8888    );
 8889
 8890    update_test_project_settings(cx, |project_settings| {
 8891        project_settings.lsp.insert(
 8892            language_server_name.into(),
 8893            LspSettings {
 8894                binary: None,
 8895                settings: None,
 8896                initialization_options: None,
 8897            },
 8898        );
 8899    });
 8900    cx.executor().run_until_parked();
 8901    assert_eq!(
 8902        server_restarts.load(atomic::Ordering::Acquire),
 8903        2,
 8904        "Should restart LSP server on another related LSP settings change"
 8905    );
 8906}
 8907
 8908#[gpui::test]
 8909async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
 8910    init_test(cx, |_| {});
 8911
 8912    let mut cx = EditorLspTestContext::new_rust(
 8913        lsp::ServerCapabilities {
 8914            completion_provider: Some(lsp::CompletionOptions {
 8915                trigger_characters: Some(vec![".".to_string()]),
 8916                resolve_provider: Some(true),
 8917                ..Default::default()
 8918            }),
 8919            ..Default::default()
 8920        },
 8921        cx,
 8922    )
 8923    .await;
 8924
 8925    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 8926    cx.simulate_keystroke(".");
 8927    let completion_item = lsp::CompletionItem {
 8928        label: "some".into(),
 8929        kind: Some(lsp::CompletionItemKind::SNIPPET),
 8930        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 8931        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 8932            kind: lsp::MarkupKind::Markdown,
 8933            value: "```rust\nSome(2)\n```".to_string(),
 8934        })),
 8935        deprecated: Some(false),
 8936        sort_text: Some("fffffff2".to_string()),
 8937        filter_text: Some("some".to_string()),
 8938        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 8939        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 8940            range: lsp::Range {
 8941                start: lsp::Position {
 8942                    line: 0,
 8943                    character: 22,
 8944                },
 8945                end: lsp::Position {
 8946                    line: 0,
 8947                    character: 22,
 8948                },
 8949            },
 8950            new_text: "Some(2)".to_string(),
 8951        })),
 8952        additional_text_edits: Some(vec![lsp::TextEdit {
 8953            range: lsp::Range {
 8954                start: lsp::Position {
 8955                    line: 0,
 8956                    character: 20,
 8957                },
 8958                end: lsp::Position {
 8959                    line: 0,
 8960                    character: 22,
 8961                },
 8962            },
 8963            new_text: "".to_string(),
 8964        }]),
 8965        ..Default::default()
 8966    };
 8967
 8968    let closure_completion_item = completion_item.clone();
 8969    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 8970        let task_completion_item = closure_completion_item.clone();
 8971        async move {
 8972            Ok(Some(lsp::CompletionResponse::Array(vec![
 8973                task_completion_item,
 8974            ])))
 8975        }
 8976    });
 8977
 8978    request.next().await;
 8979
 8980    cx.condition(|editor, _| editor.context_menu_visible())
 8981        .await;
 8982    let apply_additional_edits = cx.update_editor(|editor, cx| {
 8983        editor
 8984            .confirm_completion(&ConfirmCompletion::default(), cx)
 8985            .unwrap()
 8986    });
 8987    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
 8988
 8989    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
 8990        let task_completion_item = completion_item.clone();
 8991        async move { Ok(task_completion_item) }
 8992    })
 8993    .next()
 8994    .await
 8995    .unwrap();
 8996    apply_additional_edits.await.unwrap();
 8997    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
 8998}
 8999
 9000#[gpui::test]
 9001async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
 9002    init_test(cx, |_| {});
 9003
 9004    let mut cx = EditorLspTestContext::new(
 9005        Language::new(
 9006            LanguageConfig {
 9007                matcher: LanguageMatcher {
 9008                    path_suffixes: vec!["jsx".into()],
 9009                    ..Default::default()
 9010                },
 9011                overrides: [(
 9012                    "element".into(),
 9013                    LanguageConfigOverride {
 9014                        word_characters: Override::Set(['-'].into_iter().collect()),
 9015                        ..Default::default()
 9016                    },
 9017                )]
 9018                .into_iter()
 9019                .collect(),
 9020                ..Default::default()
 9021            },
 9022            Some(tree_sitter_typescript::language_tsx()),
 9023        )
 9024        .with_override_query("(jsx_self_closing_element) @element")
 9025        .unwrap(),
 9026        lsp::ServerCapabilities {
 9027            completion_provider: Some(lsp::CompletionOptions {
 9028                trigger_characters: Some(vec![":".to_string()]),
 9029                ..Default::default()
 9030            }),
 9031            ..Default::default()
 9032        },
 9033        cx,
 9034    )
 9035    .await;
 9036
 9037    cx.lsp
 9038        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 9039            Ok(Some(lsp::CompletionResponse::Array(vec![
 9040                lsp::CompletionItem {
 9041                    label: "bg-blue".into(),
 9042                    ..Default::default()
 9043                },
 9044                lsp::CompletionItem {
 9045                    label: "bg-red".into(),
 9046                    ..Default::default()
 9047                },
 9048                lsp::CompletionItem {
 9049                    label: "bg-yellow".into(),
 9050                    ..Default::default()
 9051                },
 9052            ])))
 9053        });
 9054
 9055    cx.set_state(r#"<p class="bgˇ" />"#);
 9056
 9057    // Trigger completion when typing a dash, because the dash is an extra
 9058    // word character in the 'element' scope, which contains the cursor.
 9059    cx.simulate_keystroke("-");
 9060    cx.executor().run_until_parked();
 9061    cx.update_editor(|editor, _| {
 9062        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 9063            assert_eq!(
 9064                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 9065                &["bg-red", "bg-blue", "bg-yellow"]
 9066            );
 9067        } else {
 9068            panic!("expected completion menu to be open");
 9069        }
 9070    });
 9071
 9072    cx.simulate_keystroke("l");
 9073    cx.executor().run_until_parked();
 9074    cx.update_editor(|editor, _| {
 9075        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 9076            assert_eq!(
 9077                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 9078                &["bg-blue", "bg-yellow"]
 9079            );
 9080        } else {
 9081            panic!("expected completion menu to be open");
 9082        }
 9083    });
 9084
 9085    // When filtering completions, consider the character after the '-' to
 9086    // be the start of a subword.
 9087    cx.set_state(r#"<p class="yelˇ" />"#);
 9088    cx.simulate_keystroke("l");
 9089    cx.executor().run_until_parked();
 9090    cx.update_editor(|editor, _| {
 9091        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 9092            assert_eq!(
 9093                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 9094                &["bg-yellow"]
 9095            );
 9096        } else {
 9097            panic!("expected completion menu to be open");
 9098        }
 9099    });
 9100}
 9101
 9102#[gpui::test]
 9103async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
 9104    init_test(cx, |settings| {
 9105        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
 9106    });
 9107
 9108    let fs = FakeFs::new(cx.executor());
 9109    fs.insert_file("/file.ts", Default::default()).await;
 9110
 9111    let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
 9112    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9113
 9114    language_registry.add(Arc::new(Language::new(
 9115        LanguageConfig {
 9116            name: "TypeScript".into(),
 9117            matcher: LanguageMatcher {
 9118                path_suffixes: vec!["ts".to_string()],
 9119                ..Default::default()
 9120            },
 9121            ..Default::default()
 9122        },
 9123        Some(tree_sitter_rust::language()),
 9124    )));
 9125    update_test_language_settings(cx, |settings| {
 9126        settings.defaults.prettier = Some(PrettierSettings {
 9127            allowed: true,
 9128            ..PrettierSettings::default()
 9129        });
 9130    });
 9131
 9132    let test_plugin = "test_plugin";
 9133    let _ = language_registry.register_fake_lsp_adapter(
 9134        "TypeScript",
 9135        FakeLspAdapter {
 9136            prettier_plugins: vec![test_plugin],
 9137            ..Default::default()
 9138        },
 9139    );
 9140
 9141    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
 9142    let buffer = project
 9143        .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
 9144        .await
 9145        .unwrap();
 9146
 9147    let buffer_text = "one\ntwo\nthree\n";
 9148    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 9149    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 9150    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
 9151
 9152    editor
 9153        .update(cx, |editor, cx| {
 9154            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 9155        })
 9156        .unwrap()
 9157        .await;
 9158    assert_eq!(
 9159        editor.update(cx, |editor, cx| editor.text(cx)),
 9160        buffer_text.to_string() + prettier_format_suffix,
 9161        "Test prettier formatting was not applied to the original buffer text",
 9162    );
 9163
 9164    update_test_language_settings(cx, |settings| {
 9165        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 9166    });
 9167    let format = editor.update(cx, |editor, cx| {
 9168        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 9169    });
 9170    format.await.unwrap();
 9171    assert_eq!(
 9172        editor.update(cx, |editor, cx| editor.text(cx)),
 9173        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
 9174        "Autoformatting (via test prettier) was not applied to the original buffer text",
 9175    );
 9176}
 9177
 9178#[gpui::test]
 9179async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
 9180    init_test(cx, |_| {});
 9181    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9182    let base_text = indoc! {r#"struct Row;
 9183struct Row1;
 9184struct Row2;
 9185
 9186struct Row4;
 9187struct Row5;
 9188struct Row6;
 9189
 9190struct Row8;
 9191struct Row9;
 9192struct Row10;"#};
 9193
 9194    // When addition hunks are not adjacent to carets, no hunk revert is performed
 9195    assert_hunk_revert(
 9196        indoc! {r#"struct Row;
 9197                   struct Row1;
 9198                   struct Row1.1;
 9199                   struct Row1.2;
 9200                   struct Row2;ˇ
 9201
 9202                   struct Row4;
 9203                   struct Row5;
 9204                   struct Row6;
 9205
 9206                   struct Row8;
 9207                   ˇstruct Row9;
 9208                   struct Row9.1;
 9209                   struct Row9.2;
 9210                   struct Row9.3;
 9211                   struct Row10;"#},
 9212        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 9213        indoc! {r#"struct Row;
 9214                   struct Row1;
 9215                   struct Row1.1;
 9216                   struct Row1.2;
 9217                   struct Row2;ˇ
 9218
 9219                   struct Row4;
 9220                   struct Row5;
 9221                   struct Row6;
 9222
 9223                   struct Row8;
 9224                   ˇstruct Row9;
 9225                   struct Row9.1;
 9226                   struct Row9.2;
 9227                   struct Row9.3;
 9228                   struct Row10;"#},
 9229        base_text,
 9230        &mut cx,
 9231    );
 9232    // Same for selections
 9233    assert_hunk_revert(
 9234        indoc! {r#"struct Row;
 9235                   struct Row1;
 9236                   struct Row2;
 9237                   struct Row2.1;
 9238                   struct Row2.2;
 9239                   «ˇ
 9240                   struct Row4;
 9241                   struct» Row5;
 9242                   «struct Row6;
 9243                   ˇ»
 9244                   struct Row9.1;
 9245                   struct Row9.2;
 9246                   struct Row9.3;
 9247                   struct Row8;
 9248                   struct Row9;
 9249                   struct Row10;"#},
 9250        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 9251        indoc! {r#"struct Row;
 9252                   struct Row1;
 9253                   struct Row2;
 9254                   struct Row2.1;
 9255                   struct Row2.2;
 9256                   «ˇ
 9257                   struct Row4;
 9258                   struct» Row5;
 9259                   «struct Row6;
 9260                   ˇ»
 9261                   struct Row9.1;
 9262                   struct Row9.2;
 9263                   struct Row9.3;
 9264                   struct Row8;
 9265                   struct Row9;
 9266                   struct Row10;"#},
 9267        base_text,
 9268        &mut cx,
 9269    );
 9270
 9271    // When carets and selections intersect the addition hunks, those are reverted.
 9272    // Adjacent carets got merged.
 9273    assert_hunk_revert(
 9274        indoc! {r#"struct Row;
 9275                   ˇ// something on the top
 9276                   struct Row1;
 9277                   struct Row2;
 9278                   struct Roˇw3.1;
 9279                   struct Row2.2;
 9280                   struct Row2.3;ˇ
 9281
 9282                   struct Row4;
 9283                   struct ˇRow5.1;
 9284                   struct Row5.2;
 9285                   struct «Rowˇ»5.3;
 9286                   struct Row5;
 9287                   struct Row6;
 9288                   ˇ
 9289                   struct Row9.1;
 9290                   struct «Rowˇ»9.2;
 9291                   struct «ˇRow»9.3;
 9292                   struct Row8;
 9293                   struct Row9;
 9294                   «ˇ// something on bottom»
 9295                   struct Row10;"#},
 9296        vec![
 9297            DiffHunkStatus::Added,
 9298            DiffHunkStatus::Added,
 9299            DiffHunkStatus::Added,
 9300            DiffHunkStatus::Added,
 9301            DiffHunkStatus::Added,
 9302        ],
 9303        indoc! {r#"struct Row;
 9304                   ˇstruct Row1;
 9305                   struct Row2;
 9306                   ˇ
 9307                   struct Row4;
 9308                   ˇstruct Row5;
 9309                   struct Row6;
 9310                   ˇ
 9311                   ˇstruct Row8;
 9312                   struct Row9;
 9313                   ˇstruct Row10;"#},
 9314        base_text,
 9315        &mut cx,
 9316    );
 9317}
 9318
 9319#[gpui::test]
 9320async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
 9321    init_test(cx, |_| {});
 9322    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9323    let base_text = indoc! {r#"struct Row;
 9324struct Row1;
 9325struct Row2;
 9326
 9327struct Row4;
 9328struct Row5;
 9329struct Row6;
 9330
 9331struct Row8;
 9332struct Row9;
 9333struct Row10;"#};
 9334
 9335    // Modification hunks behave the same as the addition ones.
 9336    assert_hunk_revert(
 9337        indoc! {r#"struct Row;
 9338                   struct Row1;
 9339                   struct Row33;
 9340                   ˇ
 9341                   struct Row4;
 9342                   struct Row5;
 9343                   struct Row6;
 9344                   ˇ
 9345                   struct Row99;
 9346                   struct Row9;
 9347                   struct Row10;"#},
 9348        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 9349        indoc! {r#"struct Row;
 9350                   struct Row1;
 9351                   struct Row33;
 9352                   ˇ
 9353                   struct Row4;
 9354                   struct Row5;
 9355                   struct Row6;
 9356                   ˇ
 9357                   struct Row99;
 9358                   struct Row9;
 9359                   struct Row10;"#},
 9360        base_text,
 9361        &mut cx,
 9362    );
 9363    assert_hunk_revert(
 9364        indoc! {r#"struct Row;
 9365                   struct Row1;
 9366                   struct Row33;
 9367                   «ˇ
 9368                   struct Row4;
 9369                   struct» Row5;
 9370                   «struct Row6;
 9371                   ˇ»
 9372                   struct Row99;
 9373                   struct Row9;
 9374                   struct Row10;"#},
 9375        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 9376        indoc! {r#"struct Row;
 9377                   struct Row1;
 9378                   struct Row33;
 9379                   «ˇ
 9380                   struct Row4;
 9381                   struct» Row5;
 9382                   «struct Row6;
 9383                   ˇ»
 9384                   struct Row99;
 9385                   struct Row9;
 9386                   struct Row10;"#},
 9387        base_text,
 9388        &mut cx,
 9389    );
 9390
 9391    assert_hunk_revert(
 9392        indoc! {r#"ˇstruct Row1.1;
 9393                   struct Row1;
 9394                   «ˇstr»uct Row22;
 9395
 9396                   struct ˇRow44;
 9397                   struct Row5;
 9398                   struct «Rˇ»ow66;ˇ
 9399
 9400                   «struˇ»ct Row88;
 9401                   struct Row9;
 9402                   struct Row1011;ˇ"#},
 9403        vec![
 9404            DiffHunkStatus::Modified,
 9405            DiffHunkStatus::Modified,
 9406            DiffHunkStatus::Modified,
 9407            DiffHunkStatus::Modified,
 9408            DiffHunkStatus::Modified,
 9409            DiffHunkStatus::Modified,
 9410        ],
 9411        indoc! {r#"struct Row;
 9412                   ˇstruct Row1;
 9413                   struct Row2;
 9414                   ˇ
 9415                   struct Row4;
 9416                   ˇstruct Row5;
 9417                   struct Row6;
 9418                   ˇ
 9419                   struct Row8;
 9420                   ˇstruct Row9;
 9421                   struct Row10;ˇ"#},
 9422        base_text,
 9423        &mut cx,
 9424    );
 9425}
 9426
 9427#[gpui::test]
 9428async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
 9429    init_test(cx, |_| {});
 9430    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9431    let base_text = indoc! {r#"struct Row;
 9432struct Row1;
 9433struct Row2;
 9434
 9435struct Row4;
 9436struct Row5;
 9437struct Row6;
 9438
 9439struct Row8;
 9440struct Row9;
 9441struct Row10;"#};
 9442
 9443    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
 9444    assert_hunk_revert(
 9445        indoc! {r#"struct Row;
 9446                   struct Row2;
 9447
 9448                   ˇstruct Row4;
 9449                   struct Row5;
 9450                   struct Row6;
 9451                   ˇ
 9452                   struct Row8;
 9453                   struct Row10;"#},
 9454        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9455        indoc! {r#"struct Row;
 9456                   struct Row2;
 9457
 9458                   ˇstruct Row4;
 9459                   struct Row5;
 9460                   struct Row6;
 9461                   ˇ
 9462                   struct Row8;
 9463                   struct Row10;"#},
 9464        base_text,
 9465        &mut cx,
 9466    );
 9467    assert_hunk_revert(
 9468        indoc! {r#"struct Row;
 9469                   struct Row2;
 9470
 9471                   «ˇstruct Row4;
 9472                   struct» Row5;
 9473                   «struct Row6;
 9474                   ˇ»
 9475                   struct Row8;
 9476                   struct Row10;"#},
 9477        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9478        indoc! {r#"struct Row;
 9479                   struct Row2;
 9480
 9481                   «ˇstruct Row4;
 9482                   struct» Row5;
 9483                   «struct Row6;
 9484                   ˇ»
 9485                   struct Row8;
 9486                   struct Row10;"#},
 9487        base_text,
 9488        &mut cx,
 9489    );
 9490
 9491    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
 9492    assert_hunk_revert(
 9493        indoc! {r#"struct Row;
 9494                   ˇstruct Row2;
 9495
 9496                   struct Row4;
 9497                   struct Row5;
 9498                   struct Row6;
 9499
 9500                   struct Row8;ˇ
 9501                   struct Row10;"#},
 9502        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 9503        indoc! {r#"struct Row;
 9504                   struct Row1;
 9505                   ˇstruct Row2;
 9506
 9507                   struct Row4;
 9508                   struct Row5;
 9509                   struct Row6;
 9510
 9511                   struct Row8;ˇ
 9512                   struct Row9;
 9513                   struct Row10;"#},
 9514        base_text,
 9515        &mut cx,
 9516    );
 9517    assert_hunk_revert(
 9518        indoc! {r#"struct Row;
 9519                   struct Row2«ˇ;
 9520                   struct Row4;
 9521                   struct» Row5;
 9522                   «struct Row6;
 9523
 9524                   struct Row8;ˇ»
 9525                   struct Row10;"#},
 9526        vec![
 9527            DiffHunkStatus::Removed,
 9528            DiffHunkStatus::Removed,
 9529            DiffHunkStatus::Removed,
 9530        ],
 9531        indoc! {r#"struct Row;
 9532                   struct Row1;
 9533                   struct Row2«ˇ;
 9534
 9535                   struct Row4;
 9536                   struct» Row5;
 9537                   «struct Row6;
 9538
 9539                   struct Row8;ˇ»
 9540                   struct Row9;
 9541                   struct Row10;"#},
 9542        base_text,
 9543        &mut cx,
 9544    );
 9545}
 9546
 9547#[gpui::test]
 9548async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
 9549    init_test(cx, |_| {});
 9550
 9551    let cols = 4;
 9552    let rows = 10;
 9553    let sample_text_1 = sample_text(rows, cols, 'a');
 9554    assert_eq!(
 9555        sample_text_1,
 9556        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9557    );
 9558    let sample_text_2 = sample_text(rows, cols, 'l');
 9559    assert_eq!(
 9560        sample_text_2,
 9561        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9562    );
 9563    let sample_text_3 = sample_text(rows, cols, 'v');
 9564    assert_eq!(
 9565        sample_text_3,
 9566        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9567    );
 9568
 9569    fn diff_every_buffer_row(
 9570        buffer: &Model<Buffer>,
 9571        sample_text: String,
 9572        cols: usize,
 9573        cx: &mut gpui::TestAppContext,
 9574    ) {
 9575        // revert first character in each row, creating one large diff hunk per buffer
 9576        let is_first_char = |offset: usize| offset % cols == 0;
 9577        buffer.update(cx, |buffer, cx| {
 9578            buffer.set_text(
 9579                sample_text
 9580                    .chars()
 9581                    .enumerate()
 9582                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
 9583                    .collect::<String>(),
 9584                cx,
 9585            );
 9586            buffer.set_diff_base(Some(sample_text), cx);
 9587        });
 9588        cx.executor().run_until_parked();
 9589    }
 9590
 9591    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9592    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9593
 9594    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9595    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9596
 9597    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9598    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9599
 9600    let multibuffer = cx.new_model(|cx| {
 9601        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9602        multibuffer.push_excerpts(
 9603            buffer_1.clone(),
 9604            [
 9605                ExcerptRange {
 9606                    context: Point::new(0, 0)..Point::new(3, 0),
 9607                    primary: None,
 9608                },
 9609                ExcerptRange {
 9610                    context: Point::new(5, 0)..Point::new(7, 0),
 9611                    primary: None,
 9612                },
 9613                ExcerptRange {
 9614                    context: Point::new(9, 0)..Point::new(10, 4),
 9615                    primary: None,
 9616                },
 9617            ],
 9618            cx,
 9619        );
 9620        multibuffer.push_excerpts(
 9621            buffer_2.clone(),
 9622            [
 9623                ExcerptRange {
 9624                    context: Point::new(0, 0)..Point::new(3, 0),
 9625                    primary: None,
 9626                },
 9627                ExcerptRange {
 9628                    context: Point::new(5, 0)..Point::new(7, 0),
 9629                    primary: None,
 9630                },
 9631                ExcerptRange {
 9632                    context: Point::new(9, 0)..Point::new(10, 4),
 9633                    primary: None,
 9634                },
 9635            ],
 9636            cx,
 9637        );
 9638        multibuffer.push_excerpts(
 9639            buffer_3.clone(),
 9640            [
 9641                ExcerptRange {
 9642                    context: Point::new(0, 0)..Point::new(3, 0),
 9643                    primary: None,
 9644                },
 9645                ExcerptRange {
 9646                    context: Point::new(5, 0)..Point::new(7, 0),
 9647                    primary: None,
 9648                },
 9649                ExcerptRange {
 9650                    context: Point::new(9, 0)..Point::new(10, 4),
 9651                    primary: None,
 9652                },
 9653            ],
 9654            cx,
 9655        );
 9656        multibuffer
 9657    });
 9658
 9659    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 9660    editor.update(cx, |editor, cx| {
 9661        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");
 9662        editor.select_all(&SelectAll, cx);
 9663        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9664    });
 9665    cx.executor().run_until_parked();
 9666    // When all ranges are selected, all buffer hunks are reverted.
 9667    editor.update(cx, |editor, cx| {
 9668        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");
 9669    });
 9670    buffer_1.update(cx, |buffer, _| {
 9671        assert_eq!(buffer.text(), sample_text_1);
 9672    });
 9673    buffer_2.update(cx, |buffer, _| {
 9674        assert_eq!(buffer.text(), sample_text_2);
 9675    });
 9676    buffer_3.update(cx, |buffer, _| {
 9677        assert_eq!(buffer.text(), sample_text_3);
 9678    });
 9679
 9680    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9681    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9682    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9683    editor.update(cx, |editor, cx| {
 9684        editor.change_selections(None, cx, |s| {
 9685            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
 9686        });
 9687        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9688    });
 9689    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
 9690    // but not affect buffer_2 and its related excerpts.
 9691    editor.update(cx, |editor, cx| {
 9692        assert_eq!(
 9693            editor.text(cx),
 9694            "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"
 9695        );
 9696    });
 9697    buffer_1.update(cx, |buffer, _| {
 9698        assert_eq!(buffer.text(), sample_text_1);
 9699    });
 9700    buffer_2.update(cx, |buffer, _| {
 9701        assert_eq!(
 9702            buffer.text(),
 9703            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
 9704        );
 9705    });
 9706    buffer_3.update(cx, |buffer, _| {
 9707        assert_eq!(
 9708            buffer.text(),
 9709            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
 9710        );
 9711    });
 9712}
 9713
 9714#[gpui::test]
 9715async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
 9716    init_test(cx, |_| {});
 9717
 9718    let cols = 4;
 9719    let rows = 10;
 9720    let sample_text_1 = sample_text(rows, cols, 'a');
 9721    assert_eq!(
 9722        sample_text_1,
 9723        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9724    );
 9725    let sample_text_2 = sample_text(rows, cols, 'l');
 9726    assert_eq!(
 9727        sample_text_2,
 9728        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9729    );
 9730    let sample_text_3 = sample_text(rows, cols, 'v');
 9731    assert_eq!(
 9732        sample_text_3,
 9733        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9734    );
 9735
 9736    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9737    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9738    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9739
 9740    let multi_buffer = cx.new_model(|cx| {
 9741        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9742        multibuffer.push_excerpts(
 9743            buffer_1.clone(),
 9744            [
 9745                ExcerptRange {
 9746                    context: Point::new(0, 0)..Point::new(3, 0),
 9747                    primary: None,
 9748                },
 9749                ExcerptRange {
 9750                    context: Point::new(5, 0)..Point::new(7, 0),
 9751                    primary: None,
 9752                },
 9753                ExcerptRange {
 9754                    context: Point::new(9, 0)..Point::new(10, 4),
 9755                    primary: None,
 9756                },
 9757            ],
 9758            cx,
 9759        );
 9760        multibuffer.push_excerpts(
 9761            buffer_2.clone(),
 9762            [
 9763                ExcerptRange {
 9764                    context: Point::new(0, 0)..Point::new(3, 0),
 9765                    primary: None,
 9766                },
 9767                ExcerptRange {
 9768                    context: Point::new(5, 0)..Point::new(7, 0),
 9769                    primary: None,
 9770                },
 9771                ExcerptRange {
 9772                    context: Point::new(9, 0)..Point::new(10, 4),
 9773                    primary: None,
 9774                },
 9775            ],
 9776            cx,
 9777        );
 9778        multibuffer.push_excerpts(
 9779            buffer_3.clone(),
 9780            [
 9781                ExcerptRange {
 9782                    context: Point::new(0, 0)..Point::new(3, 0),
 9783                    primary: None,
 9784                },
 9785                ExcerptRange {
 9786                    context: Point::new(5, 0)..Point::new(7, 0),
 9787                    primary: None,
 9788                },
 9789                ExcerptRange {
 9790                    context: Point::new(9, 0)..Point::new(10, 4),
 9791                    primary: None,
 9792                },
 9793            ],
 9794            cx,
 9795        );
 9796        multibuffer
 9797    });
 9798
 9799    let fs = FakeFs::new(cx.executor());
 9800    fs.insert_tree(
 9801        "/a",
 9802        json!({
 9803            "main.rs": sample_text_1,
 9804            "other.rs": sample_text_2,
 9805            "lib.rs": sample_text_3,
 9806        }),
 9807    )
 9808    .await;
 9809    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 9810    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 9811    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9812    let multi_buffer_editor = cx.new_view(|cx| {
 9813        Editor::new(
 9814            EditorMode::Full,
 9815            multi_buffer,
 9816            Some(project.clone()),
 9817            true,
 9818            cx,
 9819        )
 9820    });
 9821    let multibuffer_item_id = workspace
 9822        .update(cx, |workspace, cx| {
 9823            assert!(
 9824                workspace.active_item(cx).is_none(),
 9825                "active item should be None before the first item is added"
 9826            );
 9827            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
 9828            let active_item = workspace
 9829                .active_item(cx)
 9830                .expect("should have an active item after adding the multi buffer");
 9831            assert!(
 9832                !active_item.is_singleton(cx),
 9833                "A multi buffer was expected to active after adding"
 9834            );
 9835            active_item.item_id()
 9836        })
 9837        .unwrap();
 9838    cx.executor().run_until_parked();
 9839
 9840    multi_buffer_editor.update(cx, |editor, cx| {
 9841        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 9842        editor.open_excerpts(&OpenExcerpts, cx);
 9843    });
 9844    cx.executor().run_until_parked();
 9845    let first_item_id = workspace
 9846        .update(cx, |workspace, cx| {
 9847            let active_item = workspace
 9848                .active_item(cx)
 9849                .expect("should have an active item after navigating into the 1st buffer");
 9850            let first_item_id = active_item.item_id();
 9851            assert_ne!(
 9852                first_item_id, multibuffer_item_id,
 9853                "Should navigate into the 1st buffer and activate it"
 9854            );
 9855            assert!(
 9856                active_item.is_singleton(cx),
 9857                "New active item should be a singleton buffer"
 9858            );
 9859            assert_eq!(
 9860                active_item
 9861                    .act_as::<Editor>(cx)
 9862                    .expect("should have navigated into an editor for the 1st buffer")
 9863                    .read(cx)
 9864                    .text(cx),
 9865                sample_text_1
 9866            );
 9867
 9868            workspace
 9869                .go_back(workspace.active_pane().downgrade(), cx)
 9870                .detach_and_log_err(cx);
 9871
 9872            first_item_id
 9873        })
 9874        .unwrap();
 9875    cx.executor().run_until_parked();
 9876    workspace
 9877        .update(cx, |workspace, cx| {
 9878            let active_item = workspace
 9879                .active_item(cx)
 9880                .expect("should have an active item after navigating back");
 9881            assert_eq!(
 9882                active_item.item_id(),
 9883                multibuffer_item_id,
 9884                "Should navigate back to the multi buffer"
 9885            );
 9886            assert!(!active_item.is_singleton(cx));
 9887        })
 9888        .unwrap();
 9889
 9890    multi_buffer_editor.update(cx, |editor, cx| {
 9891        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9892            s.select_ranges(Some(39..40))
 9893        });
 9894        editor.open_excerpts(&OpenExcerpts, cx);
 9895    });
 9896    cx.executor().run_until_parked();
 9897    let second_item_id = workspace
 9898        .update(cx, |workspace, cx| {
 9899            let active_item = workspace
 9900                .active_item(cx)
 9901                .expect("should have an active item after navigating into the 2nd buffer");
 9902            let second_item_id = active_item.item_id();
 9903            assert_ne!(
 9904                second_item_id, multibuffer_item_id,
 9905                "Should navigate away from the multibuffer"
 9906            );
 9907            assert_ne!(
 9908                second_item_id, first_item_id,
 9909                "Should navigate into the 2nd buffer and activate it"
 9910            );
 9911            assert!(
 9912                active_item.is_singleton(cx),
 9913                "New active item should be a singleton buffer"
 9914            );
 9915            assert_eq!(
 9916                active_item
 9917                    .act_as::<Editor>(cx)
 9918                    .expect("should have navigated into an editor")
 9919                    .read(cx)
 9920                    .text(cx),
 9921                sample_text_2
 9922            );
 9923
 9924            workspace
 9925                .go_back(workspace.active_pane().downgrade(), cx)
 9926                .detach_and_log_err(cx);
 9927
 9928            second_item_id
 9929        })
 9930        .unwrap();
 9931    cx.executor().run_until_parked();
 9932    workspace
 9933        .update(cx, |workspace, cx| {
 9934            let active_item = workspace
 9935                .active_item(cx)
 9936                .expect("should have an active item after navigating back from the 2nd buffer");
 9937            assert_eq!(
 9938                active_item.item_id(),
 9939                multibuffer_item_id,
 9940                "Should navigate back from the 2nd buffer to the multi buffer"
 9941            );
 9942            assert!(!active_item.is_singleton(cx));
 9943        })
 9944        .unwrap();
 9945
 9946    multi_buffer_editor.update(cx, |editor, cx| {
 9947        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9948            s.select_ranges(Some(60..70))
 9949        });
 9950        editor.open_excerpts(&OpenExcerpts, cx);
 9951    });
 9952    cx.executor().run_until_parked();
 9953    workspace
 9954        .update(cx, |workspace, cx| {
 9955            let active_item = workspace
 9956                .active_item(cx)
 9957                .expect("should have an active item after navigating into the 3rd buffer");
 9958            let third_item_id = active_item.item_id();
 9959            assert_ne!(
 9960                third_item_id, multibuffer_item_id,
 9961                "Should navigate into the 3rd buffer and activate it"
 9962            );
 9963            assert_ne!(third_item_id, first_item_id);
 9964            assert_ne!(third_item_id, second_item_id);
 9965            assert!(
 9966                active_item.is_singleton(cx),
 9967                "New active item should be a singleton buffer"
 9968            );
 9969            assert_eq!(
 9970                active_item
 9971                    .act_as::<Editor>(cx)
 9972                    .expect("should have navigated into an editor")
 9973                    .read(cx)
 9974                    .text(cx),
 9975                sample_text_3
 9976            );
 9977
 9978            workspace
 9979                .go_back(workspace.active_pane().downgrade(), cx)
 9980                .detach_and_log_err(cx);
 9981        })
 9982        .unwrap();
 9983    cx.executor().run_until_parked();
 9984    workspace
 9985        .update(cx, |workspace, cx| {
 9986            let active_item = workspace
 9987                .active_item(cx)
 9988                .expect("should have an active item after navigating back from the 3rd buffer");
 9989            assert_eq!(
 9990                active_item.item_id(),
 9991                multibuffer_item_id,
 9992                "Should navigate back from the 3rd buffer to the multi buffer"
 9993            );
 9994            assert!(!active_item.is_singleton(cx));
 9995        })
 9996        .unwrap();
 9997}
 9998
 9999#[gpui::test]
10000async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10001    init_test(cx, |_| {});
10002
10003    let mut cx = EditorTestContext::new(cx).await;
10004
10005    let diff_base = r#"
10006        use some::mod;
10007
10008        const A: u32 = 42;
10009
10010        fn main() {
10011            println!("hello");
10012
10013            println!("world");
10014        }
10015        "#
10016    .unindent();
10017
10018    cx.set_state(
10019        &r#"
10020        use some::modified;
10021
10022        ˇ
10023        fn main() {
10024            println!("hello there");
10025
10026            println!("around the");
10027            println!("world");
10028        }
10029        "#
10030        .unindent(),
10031    );
10032
10033    cx.set_diff_base(Some(&diff_base));
10034    executor.run_until_parked();
10035    let unexpanded_hunks = vec![
10036        (
10037            "use some::mod;\n".to_string(),
10038            DiffHunkStatus::Modified,
10039            DisplayRow(0)..DisplayRow(1),
10040        ),
10041        (
10042            "const A: u32 = 42;\n".to_string(),
10043            DiffHunkStatus::Removed,
10044            DisplayRow(2)..DisplayRow(2),
10045        ),
10046        (
10047            "    println!(\"hello\");\n".to_string(),
10048            DiffHunkStatus::Modified,
10049            DisplayRow(4)..DisplayRow(5),
10050        ),
10051        (
10052            "".to_string(),
10053            DiffHunkStatus::Added,
10054            DisplayRow(6)..DisplayRow(7),
10055        ),
10056    ];
10057    cx.update_editor(|editor, cx| {
10058        let snapshot = editor.snapshot(cx);
10059        let all_hunks = editor_hunks(editor, &snapshot, cx);
10060        assert_eq!(all_hunks, unexpanded_hunks);
10061    });
10062
10063    cx.update_editor(|editor, cx| {
10064        for _ in 0..4 {
10065            editor.go_to_hunk(&GoToHunk, cx);
10066            editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10067        }
10068    });
10069    executor.run_until_parked();
10070    cx.assert_editor_state(
10071        &r#"
10072        use some::modified;
10073
10074        ˇ
10075        fn main() {
10076            println!("hello there");
10077
10078            println!("around the");
10079            println!("world");
10080        }
10081        "#
10082        .unindent(),
10083    );
10084    cx.update_editor(|editor, cx| {
10085        let snapshot = editor.snapshot(cx);
10086        let all_hunks = editor_hunks(editor, &snapshot, cx);
10087        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10088        assert_eq!(
10089            expanded_hunks_background_highlights(editor, cx),
10090            vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10091            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10092        );
10093        assert_eq!(
10094            all_hunks,
10095            vec![
10096                ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10097                ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10098                ("    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10099                ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10100            ],
10101            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10102            (from modified and removed hunks)"
10103        );
10104        assert_eq!(
10105            all_hunks, all_expanded_hunks,
10106            "Editor hunks should not change and all be expanded"
10107        );
10108    });
10109
10110    cx.update_editor(|editor, cx| {
10111        editor.cancel(&Cancel, cx);
10112
10113        let snapshot = editor.snapshot(cx);
10114        let all_hunks = editor_hunks(editor, &snapshot, cx);
10115        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10116        assert_eq!(
10117            expanded_hunks_background_highlights(editor, cx),
10118            Vec::new(),
10119            "After cancelling in editor, no git highlights should be left"
10120        );
10121        assert_eq!(
10122            all_expanded_hunks,
10123            Vec::new(),
10124            "After cancelling in editor, no hunks should be expanded"
10125        );
10126        assert_eq!(
10127            all_hunks, unexpanded_hunks,
10128            "After cancelling in editor, regular hunks' coordinates should get back to normal"
10129        );
10130    });
10131}
10132
10133#[gpui::test]
10134async fn test_toggled_diff_base_change(
10135    executor: BackgroundExecutor,
10136    cx: &mut gpui::TestAppContext,
10137) {
10138    init_test(cx, |_| {});
10139
10140    let mut cx = EditorTestContext::new(cx).await;
10141
10142    let diff_base = r#"
10143        use some::mod1;
10144        use some::mod2;
10145
10146        const A: u32 = 42;
10147        const B: u32 = 42;
10148        const C: u32 = 42;
10149
10150        fn main(ˇ) {
10151            println!("hello");
10152
10153            println!("world");
10154        }
10155        "#
10156    .unindent();
10157
10158    cx.set_state(
10159        &r#"
10160        use some::mod2;
10161
10162        const A: u32 = 42;
10163        const C: u32 = 42;
10164
10165        fn main(ˇ) {
10166            //println!("hello");
10167
10168            println!("world");
10169            //
10170            //
10171        }
10172        "#
10173        .unindent(),
10174    );
10175
10176    cx.set_diff_base(Some(&diff_base));
10177    executor.run_until_parked();
10178    cx.update_editor(|editor, cx| {
10179        let snapshot = editor.snapshot(cx);
10180        let all_hunks = editor_hunks(editor, &snapshot, cx);
10181        assert_eq!(
10182            all_hunks,
10183            vec![
10184                (
10185                    "use some::mod1;\n".to_string(),
10186                    DiffHunkStatus::Removed,
10187                    DisplayRow(0)..DisplayRow(0)
10188                ),
10189                (
10190                    "const B: u32 = 42;\n".to_string(),
10191                    DiffHunkStatus::Removed,
10192                    DisplayRow(3)..DisplayRow(3)
10193                ),
10194                (
10195                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10196                    DiffHunkStatus::Modified,
10197                    DisplayRow(5)..DisplayRow(7)
10198                ),
10199                (
10200                    "".to_string(),
10201                    DiffHunkStatus::Added,
10202                    DisplayRow(9)..DisplayRow(11)
10203                ),
10204            ]
10205        );
10206    });
10207
10208    cx.update_editor(|editor, cx| {
10209        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10210    });
10211    executor.run_until_parked();
10212    cx.assert_editor_state(
10213        &r#"
10214        use some::mod2;
10215
10216        const A: u32 = 42;
10217        const C: u32 = 42;
10218
10219        fn main(ˇ) {
10220            //println!("hello");
10221
10222            println!("world");
10223            //
10224            //
10225        }
10226        "#
10227        .unindent(),
10228    );
10229    cx.update_editor(|editor, cx| {
10230        let snapshot = editor.snapshot(cx);
10231        let all_hunks = editor_hunks(editor, &snapshot, cx);
10232        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10233        assert_eq!(
10234            expanded_hunks_background_highlights(editor, cx),
10235            vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10236            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10237        );
10238        assert_eq!(
10239            all_hunks,
10240            vec![
10241                ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10242                ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10243                ("fn main(ˇ) {\n    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10244                ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10245            ],
10246            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10247            (from modified and removed hunks)"
10248        );
10249        assert_eq!(
10250            all_hunks, all_expanded_hunks,
10251            "Editor hunks should not change and all be expanded"
10252        );
10253    });
10254
10255    cx.set_diff_base(Some("new diff base!"));
10256    executor.run_until_parked();
10257
10258    cx.update_editor(|editor, cx| {
10259        let snapshot = editor.snapshot(cx);
10260        let all_hunks = editor_hunks(editor, &snapshot, cx);
10261        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10262        assert_eq!(
10263            expanded_hunks_background_highlights(editor, cx),
10264            Vec::new(),
10265            "After diff base is changed, old git highlights should be removed"
10266        );
10267        assert_eq!(
10268            all_expanded_hunks,
10269            Vec::new(),
10270            "After diff base is changed, old git hunk expansions should be removed"
10271        );
10272        assert_eq!(
10273            all_hunks,
10274            vec![(
10275                "new diff base!".to_string(),
10276                DiffHunkStatus::Modified,
10277                DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10278            )],
10279            "After diff base is changed, hunks should update"
10280        );
10281    });
10282}
10283
10284#[gpui::test]
10285async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10286    init_test(cx, |_| {});
10287
10288    let mut cx = EditorTestContext::new(cx).await;
10289
10290    let diff_base = r#"
10291        use some::mod1;
10292        use some::mod2;
10293
10294        const A: u32 = 42;
10295        const B: u32 = 42;
10296        const C: u32 = 42;
10297
10298        fn main(ˇ) {
10299            println!("hello");
10300
10301            println!("world");
10302        }
10303
10304        fn another() {
10305            println!("another");
10306        }
10307
10308        fn another2() {
10309            println!("another2");
10310        }
10311        "#
10312    .unindent();
10313
10314    cx.set_state(
10315        &r#"
10316        «use some::mod2;
10317
10318        const A: u32 = 42;
10319        const C: u32 = 42;
10320
10321        fn main() {
10322            //println!("hello");
10323
10324            println!("world");
10325            //
10326            //ˇ»
10327        }
10328
10329        fn another() {
10330            println!("another");
10331            println!("another");
10332        }
10333
10334            println!("another2");
10335        }
10336        "#
10337        .unindent(),
10338    );
10339
10340    cx.set_diff_base(Some(&diff_base));
10341    executor.run_until_parked();
10342    cx.update_editor(|editor, cx| {
10343        let snapshot = editor.snapshot(cx);
10344        let all_hunks = editor_hunks(editor, &snapshot, cx);
10345        assert_eq!(
10346            all_hunks,
10347            vec![
10348                (
10349                    "use some::mod1;\n".to_string(),
10350                    DiffHunkStatus::Removed,
10351                    DisplayRow(0)..DisplayRow(0)
10352                ),
10353                (
10354                    "const B: u32 = 42;\n".to_string(),
10355                    DiffHunkStatus::Removed,
10356                    DisplayRow(3)..DisplayRow(3)
10357                ),
10358                (
10359                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10360                    DiffHunkStatus::Modified,
10361                    DisplayRow(5)..DisplayRow(7)
10362                ),
10363                (
10364                    "".to_string(),
10365                    DiffHunkStatus::Added,
10366                    DisplayRow(9)..DisplayRow(11)
10367                ),
10368                (
10369                    "".to_string(),
10370                    DiffHunkStatus::Added,
10371                    DisplayRow(15)..DisplayRow(16)
10372                ),
10373                (
10374                    "fn another2() {\n".to_string(),
10375                    DiffHunkStatus::Removed,
10376                    DisplayRow(18)..DisplayRow(18)
10377                ),
10378            ]
10379        );
10380    });
10381
10382    cx.update_editor(|editor, cx| {
10383        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10384    });
10385    executor.run_until_parked();
10386    cx.assert_editor_state(
10387        &r#"
10388        «use some::mod2;
10389
10390        const A: u32 = 42;
10391        const C: u32 = 42;
10392
10393        fn main() {
10394            //println!("hello");
10395
10396            println!("world");
10397            //
10398            //ˇ»
10399        }
10400
10401        fn another() {
10402            println!("another");
10403            println!("another");
10404        }
10405
10406            println!("another2");
10407        }
10408        "#
10409        .unindent(),
10410    );
10411    cx.update_editor(|editor, cx| {
10412        let snapshot = editor.snapshot(cx);
10413        let all_hunks = editor_hunks(editor, &snapshot, cx);
10414        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10415        assert_eq!(
10416            expanded_hunks_background_highlights(editor, cx),
10417            vec![
10418                DisplayRow(9)..=DisplayRow(10),
10419                DisplayRow(13)..=DisplayRow(14),
10420                DisplayRow(19)..=DisplayRow(19)
10421            ]
10422        );
10423        assert_eq!(
10424            all_hunks,
10425            vec![
10426                (
10427                    "use some::mod1;\n".to_string(),
10428                    DiffHunkStatus::Removed,
10429                    DisplayRow(1)..DisplayRow(1)
10430                ),
10431                (
10432                    "const B: u32 = 42;\n".to_string(),
10433                    DiffHunkStatus::Removed,
10434                    DisplayRow(5)..DisplayRow(5)
10435                ),
10436                (
10437                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10438                    DiffHunkStatus::Modified,
10439                    DisplayRow(9)..DisplayRow(11)
10440                ),
10441                (
10442                    "".to_string(),
10443                    DiffHunkStatus::Added,
10444                    DisplayRow(13)..DisplayRow(15)
10445                ),
10446                (
10447                    "".to_string(),
10448                    DiffHunkStatus::Added,
10449                    DisplayRow(19)..DisplayRow(20)
10450                ),
10451                (
10452                    "fn another2() {\n".to_string(),
10453                    DiffHunkStatus::Removed,
10454                    DisplayRow(23)..DisplayRow(23)
10455                ),
10456            ],
10457        );
10458        assert_eq!(all_hunks, all_expanded_hunks);
10459    });
10460
10461    cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10462    cx.executor().run_until_parked();
10463    cx.assert_editor_state(
10464        &r#"
10465        «use some::mod2;
10466
10467        const A: u32 = 42;
10468        const C: u32 = 42;
10469
10470        fn main() {
10471            //println!("hello");
10472
10473            println!("world");
10474            //
10475            //ˇ»
10476        }
10477
10478        fn another() {
10479            println!("another");
10480            println!("another");
10481        }
10482
10483            println!("another2");
10484        }
10485        "#
10486        .unindent(),
10487    );
10488    cx.update_editor(|editor, cx| {
10489        let snapshot = editor.snapshot(cx);
10490        let all_hunks = editor_hunks(editor, &snapshot, cx);
10491        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10492        assert_eq!(
10493            expanded_hunks_background_highlights(editor, cx),
10494            vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10495            "Only one hunk is left not folded, its highlight should be visible"
10496        );
10497        assert_eq!(
10498            all_hunks,
10499            vec![
10500                (
10501                    "use some::mod1;\n".to_string(),
10502                    DiffHunkStatus::Removed,
10503                    DisplayRow(0)..DisplayRow(0)
10504                ),
10505                (
10506                    "const B: u32 = 42;\n".to_string(),
10507                    DiffHunkStatus::Removed,
10508                    DisplayRow(0)..DisplayRow(0)
10509                ),
10510                (
10511                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10512                    DiffHunkStatus::Modified,
10513                    DisplayRow(0)..DisplayRow(0)
10514                ),
10515                (
10516                    "".to_string(),
10517                    DiffHunkStatus::Added,
10518                    DisplayRow(0)..DisplayRow(1)
10519                ),
10520                (
10521                    "".to_string(),
10522                    DiffHunkStatus::Added,
10523                    DisplayRow(5)..DisplayRow(6)
10524                ),
10525                (
10526                    "fn another2() {\n".to_string(),
10527                    DiffHunkStatus::Removed,
10528                    DisplayRow(9)..DisplayRow(9)
10529                ),
10530            ],
10531            "Hunk list should still return shifted folded hunks"
10532        );
10533        assert_eq!(
10534            all_expanded_hunks,
10535            vec![
10536                (
10537                    "".to_string(),
10538                    DiffHunkStatus::Added,
10539                    DisplayRow(5)..DisplayRow(6)
10540                ),
10541                (
10542                    "fn another2() {\n".to_string(),
10543                    DiffHunkStatus::Removed,
10544                    DisplayRow(9)..DisplayRow(9)
10545                ),
10546            ],
10547            "Only non-folded hunks should be left expanded"
10548        );
10549    });
10550
10551    cx.update_editor(|editor, cx| {
10552        editor.select_all(&SelectAll, cx);
10553        editor.unfold_lines(&UnfoldLines, cx);
10554    });
10555    cx.executor().run_until_parked();
10556    cx.assert_editor_state(
10557        &r#"
10558        «use some::mod2;
10559
10560        const A: u32 = 42;
10561        const C: u32 = 42;
10562
10563        fn main() {
10564            //println!("hello");
10565
10566            println!("world");
10567            //
10568            //
10569        }
10570
10571        fn another() {
10572            println!("another");
10573            println!("another");
10574        }
10575
10576            println!("another2");
10577        }
10578        ˇ»"#
10579        .unindent(),
10580    );
10581    cx.update_editor(|editor, cx| {
10582        let snapshot = editor.snapshot(cx);
10583        let all_hunks = editor_hunks(editor, &snapshot, cx);
10584        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10585        assert_eq!(
10586            expanded_hunks_background_highlights(editor, cx),
10587            vec![
10588                DisplayRow(9)..=DisplayRow(10),
10589                DisplayRow(13)..=DisplayRow(14),
10590                DisplayRow(19)..=DisplayRow(19)
10591            ],
10592            "After unfolding, all hunk diffs should be visible again"
10593        );
10594        assert_eq!(
10595            all_hunks,
10596            vec![
10597                (
10598                    "use some::mod1;\n".to_string(),
10599                    DiffHunkStatus::Removed,
10600                    DisplayRow(1)..DisplayRow(1)
10601                ),
10602                (
10603                    "const B: u32 = 42;\n".to_string(),
10604                    DiffHunkStatus::Removed,
10605                    DisplayRow(5)..DisplayRow(5)
10606                ),
10607                (
10608                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10609                    DiffHunkStatus::Modified,
10610                    DisplayRow(9)..DisplayRow(11)
10611                ),
10612                (
10613                    "".to_string(),
10614                    DiffHunkStatus::Added,
10615                    DisplayRow(13)..DisplayRow(15)
10616                ),
10617                (
10618                    "".to_string(),
10619                    DiffHunkStatus::Added,
10620                    DisplayRow(19)..DisplayRow(20)
10621                ),
10622                (
10623                    "fn another2() {\n".to_string(),
10624                    DiffHunkStatus::Removed,
10625                    DisplayRow(23)..DisplayRow(23)
10626                ),
10627            ],
10628        );
10629        assert_eq!(all_hunks, all_expanded_hunks);
10630    });
10631}
10632
10633#[gpui::test]
10634async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10635    init_test(cx, |_| {});
10636
10637    let cols = 4;
10638    let rows = 10;
10639    let sample_text_1 = sample_text(rows, cols, 'a');
10640    assert_eq!(
10641        sample_text_1,
10642        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10643    );
10644    let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10645    let sample_text_2 = sample_text(rows, cols, 'l');
10646    assert_eq!(
10647        sample_text_2,
10648        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10649    );
10650    let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10651    let sample_text_3 = sample_text(rows, cols, 'v');
10652    assert_eq!(
10653        sample_text_3,
10654        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10655    );
10656    let modified_sample_text_3 =
10657        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10658    let buffer_1 = cx.new_model(|cx| {
10659        let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10660        buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10661        buffer
10662    });
10663    let buffer_2 = cx.new_model(|cx| {
10664        let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10665        buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10666        buffer
10667    });
10668    let buffer_3 = cx.new_model(|cx| {
10669        let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10670        buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10671        buffer
10672    });
10673
10674    let multi_buffer = cx.new_model(|cx| {
10675        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10676        multibuffer.push_excerpts(
10677            buffer_1.clone(),
10678            [
10679                ExcerptRange {
10680                    context: Point::new(0, 0)..Point::new(3, 0),
10681                    primary: None,
10682                },
10683                ExcerptRange {
10684                    context: Point::new(5, 0)..Point::new(7, 0),
10685                    primary: None,
10686                },
10687                ExcerptRange {
10688                    context: Point::new(9, 0)..Point::new(10, 4),
10689                    primary: None,
10690                },
10691            ],
10692            cx,
10693        );
10694        multibuffer.push_excerpts(
10695            buffer_2.clone(),
10696            [
10697                ExcerptRange {
10698                    context: Point::new(0, 0)..Point::new(3, 0),
10699                    primary: None,
10700                },
10701                ExcerptRange {
10702                    context: Point::new(5, 0)..Point::new(7, 0),
10703                    primary: None,
10704                },
10705                ExcerptRange {
10706                    context: Point::new(9, 0)..Point::new(10, 4),
10707                    primary: None,
10708                },
10709            ],
10710            cx,
10711        );
10712        multibuffer.push_excerpts(
10713            buffer_3.clone(),
10714            [
10715                ExcerptRange {
10716                    context: Point::new(0, 0)..Point::new(3, 0),
10717                    primary: None,
10718                },
10719                ExcerptRange {
10720                    context: Point::new(5, 0)..Point::new(7, 0),
10721                    primary: None,
10722                },
10723                ExcerptRange {
10724                    context: Point::new(9, 0)..Point::new(10, 4),
10725                    primary: None,
10726                },
10727            ],
10728            cx,
10729        );
10730        multibuffer
10731    });
10732
10733    let fs = FakeFs::new(cx.executor());
10734    fs.insert_tree(
10735        "/a",
10736        json!({
10737            "main.rs": modified_sample_text_1,
10738            "other.rs": modified_sample_text_2,
10739            "lib.rs": modified_sample_text_3,
10740        }),
10741    )
10742    .await;
10743
10744    let project = Project::test(fs, ["/a".as_ref()], cx).await;
10745    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10746    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10747    let multi_buffer_editor = cx.new_view(|cx| {
10748        Editor::new(
10749            EditorMode::Full,
10750            multi_buffer,
10751            Some(project.clone()),
10752            true,
10753            cx,
10754        )
10755    });
10756    cx.executor().run_until_parked();
10757
10758    let expected_all_hunks = vec![
10759        (
10760            "bbbb\n".to_string(),
10761            DiffHunkStatus::Removed,
10762            DisplayRow(4)..DisplayRow(4),
10763        ),
10764        (
10765            "nnnn\n".to_string(),
10766            DiffHunkStatus::Modified,
10767            DisplayRow(21)..DisplayRow(22),
10768        ),
10769        (
10770            "".to_string(),
10771            DiffHunkStatus::Added,
10772            DisplayRow(41)..DisplayRow(42),
10773        ),
10774    ];
10775    let expected_all_hunks_shifted = vec![
10776        (
10777            "bbbb\n".to_string(),
10778            DiffHunkStatus::Removed,
10779            DisplayRow(5)..DisplayRow(5),
10780        ),
10781        (
10782            "nnnn\n".to_string(),
10783            DiffHunkStatus::Modified,
10784            DisplayRow(23)..DisplayRow(24),
10785        ),
10786        (
10787            "".to_string(),
10788            DiffHunkStatus::Added,
10789            DisplayRow(43)..DisplayRow(44),
10790        ),
10791    ];
10792
10793    multi_buffer_editor.update(cx, |editor, cx| {
10794        let snapshot = editor.snapshot(cx);
10795        let all_hunks = editor_hunks(editor, &snapshot, cx);
10796        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10797        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10798        assert_eq!(all_hunks, expected_all_hunks);
10799        assert_eq!(all_expanded_hunks, Vec::new());
10800    });
10801
10802    multi_buffer_editor.update(cx, |editor, cx| {
10803        editor.select_all(&SelectAll, cx);
10804        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10805    });
10806    cx.executor().run_until_parked();
10807    multi_buffer_editor.update(cx, |editor, cx| {
10808        let snapshot = editor.snapshot(cx);
10809        let all_hunks = editor_hunks(editor, &snapshot, cx);
10810        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10811        assert_eq!(
10812            expanded_hunks_background_highlights(editor, cx),
10813            vec![
10814                DisplayRow(23)..=DisplayRow(23),
10815                DisplayRow(43)..=DisplayRow(43)
10816            ],
10817        );
10818        assert_eq!(all_hunks, expected_all_hunks_shifted);
10819        assert_eq!(all_hunks, all_expanded_hunks);
10820    });
10821
10822    multi_buffer_editor.update(cx, |editor, cx| {
10823        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10824    });
10825    cx.executor().run_until_parked();
10826    multi_buffer_editor.update(cx, |editor, cx| {
10827        let snapshot = editor.snapshot(cx);
10828        let all_hunks = editor_hunks(editor, &snapshot, cx);
10829        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10830        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10831        assert_eq!(all_hunks, expected_all_hunks);
10832        assert_eq!(all_expanded_hunks, Vec::new());
10833    });
10834
10835    multi_buffer_editor.update(cx, |editor, cx| {
10836        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10837    });
10838    cx.executor().run_until_parked();
10839    multi_buffer_editor.update(cx, |editor, cx| {
10840        let snapshot = editor.snapshot(cx);
10841        let all_hunks = editor_hunks(editor, &snapshot, cx);
10842        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10843        assert_eq!(
10844            expanded_hunks_background_highlights(editor, cx),
10845            vec![
10846                DisplayRow(23)..=DisplayRow(23),
10847                DisplayRow(43)..=DisplayRow(43)
10848            ],
10849        );
10850        assert_eq!(all_hunks, expected_all_hunks_shifted);
10851        assert_eq!(all_hunks, all_expanded_hunks);
10852    });
10853
10854    multi_buffer_editor.update(cx, |editor, cx| {
10855        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10856    });
10857    cx.executor().run_until_parked();
10858    multi_buffer_editor.update(cx, |editor, cx| {
10859        let snapshot = editor.snapshot(cx);
10860        let all_hunks = editor_hunks(editor, &snapshot, cx);
10861        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10862        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10863        assert_eq!(all_hunks, expected_all_hunks);
10864        assert_eq!(all_expanded_hunks, Vec::new());
10865    });
10866}
10867
10868#[gpui::test]
10869async fn test_edits_around_toggled_additions(
10870    executor: BackgroundExecutor,
10871    cx: &mut gpui::TestAppContext,
10872) {
10873    init_test(cx, |_| {});
10874
10875    let mut cx = EditorTestContext::new(cx).await;
10876
10877    let diff_base = r#"
10878        use some::mod1;
10879        use some::mod2;
10880
10881        const A: u32 = 42;
10882
10883        fn main() {
10884            println!("hello");
10885
10886            println!("world");
10887        }
10888        "#
10889    .unindent();
10890    executor.run_until_parked();
10891    cx.set_state(
10892        &r#"
10893        use some::mod1;
10894        use some::mod2;
10895
10896        const A: u32 = 42;
10897        const B: u32 = 42;
10898        const C: u32 = 42;
10899        ˇ
10900
10901        fn main() {
10902            println!("hello");
10903
10904            println!("world");
10905        }
10906        "#
10907        .unindent(),
10908    );
10909
10910    cx.set_diff_base(Some(&diff_base));
10911    executor.run_until_parked();
10912    cx.update_editor(|editor, cx| {
10913        let snapshot = editor.snapshot(cx);
10914        let all_hunks = editor_hunks(editor, &snapshot, cx);
10915        assert_eq!(
10916            all_hunks,
10917            vec![(
10918                "".to_string(),
10919                DiffHunkStatus::Added,
10920                DisplayRow(4)..DisplayRow(7)
10921            )]
10922        );
10923    });
10924    cx.update_editor(|editor, cx| {
10925        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10926    });
10927    executor.run_until_parked();
10928    cx.assert_editor_state(
10929        &r#"
10930        use some::mod1;
10931        use some::mod2;
10932
10933        const A: u32 = 42;
10934        const B: u32 = 42;
10935        const C: u32 = 42;
10936        ˇ
10937
10938        fn main() {
10939            println!("hello");
10940
10941            println!("world");
10942        }
10943        "#
10944        .unindent(),
10945    );
10946    cx.update_editor(|editor, cx| {
10947        let snapshot = editor.snapshot(cx);
10948        let all_hunks = editor_hunks(editor, &snapshot, cx);
10949        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10950        assert_eq!(
10951            all_hunks,
10952            vec![(
10953                "".to_string(),
10954                DiffHunkStatus::Added,
10955                DisplayRow(4)..DisplayRow(7)
10956            )]
10957        );
10958        assert_eq!(
10959            expanded_hunks_background_highlights(editor, cx),
10960            vec![DisplayRow(4)..=DisplayRow(6)]
10961        );
10962        assert_eq!(all_hunks, all_expanded_hunks);
10963    });
10964
10965    cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10966    executor.run_until_parked();
10967    cx.assert_editor_state(
10968        &r#"
10969        use some::mod1;
10970        use some::mod2;
10971
10972        const A: u32 = 42;
10973        const B: u32 = 42;
10974        const C: u32 = 42;
10975        const D: u32 = 42;
10976        ˇ
10977
10978        fn main() {
10979            println!("hello");
10980
10981            println!("world");
10982        }
10983        "#
10984        .unindent(),
10985    );
10986    cx.update_editor(|editor, cx| {
10987        let snapshot = editor.snapshot(cx);
10988        let all_hunks = editor_hunks(editor, &snapshot, cx);
10989        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10990        assert_eq!(
10991            all_hunks,
10992            vec![(
10993                "".to_string(),
10994                DiffHunkStatus::Added,
10995                DisplayRow(4)..DisplayRow(8)
10996            )]
10997        );
10998        assert_eq!(
10999            expanded_hunks_background_highlights(editor, cx),
11000            vec![DisplayRow(4)..=DisplayRow(6)],
11001            "Edited hunk should have one more line added"
11002        );
11003        assert_eq!(
11004            all_hunks, all_expanded_hunks,
11005            "Expanded hunk should also grow with the addition"
11006        );
11007    });
11008
11009    cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11010    executor.run_until_parked();
11011    cx.assert_editor_state(
11012        &r#"
11013        use some::mod1;
11014        use some::mod2;
11015
11016        const A: u32 = 42;
11017        const B: u32 = 42;
11018        const C: u32 = 42;
11019        const D: u32 = 42;
11020        const E: u32 = 42;
11021        ˇ
11022
11023        fn main() {
11024            println!("hello");
11025
11026            println!("world");
11027        }
11028        "#
11029        .unindent(),
11030    );
11031    cx.update_editor(|editor, cx| {
11032        let snapshot = editor.snapshot(cx);
11033        let all_hunks = editor_hunks(editor, &snapshot, cx);
11034        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11035        assert_eq!(
11036            all_hunks,
11037            vec![(
11038                "".to_string(),
11039                DiffHunkStatus::Added,
11040                DisplayRow(4)..DisplayRow(9)
11041            )]
11042        );
11043        assert_eq!(
11044            expanded_hunks_background_highlights(editor, cx),
11045            vec![DisplayRow(4)..=DisplayRow(6)],
11046            "Edited hunk should have one more line added"
11047        );
11048        assert_eq!(all_hunks, all_expanded_hunks);
11049    });
11050
11051    cx.update_editor(|editor, cx| {
11052        editor.move_up(&MoveUp, cx);
11053        editor.delete_line(&DeleteLine, cx);
11054    });
11055    executor.run_until_parked();
11056    cx.assert_editor_state(
11057        &r#"
11058        use some::mod1;
11059        use some::mod2;
11060
11061        const A: u32 = 42;
11062        const B: u32 = 42;
11063        const C: u32 = 42;
11064        const D: u32 = 42;
11065        ˇ
11066
11067        fn main() {
11068            println!("hello");
11069
11070            println!("world");
11071        }
11072        "#
11073        .unindent(),
11074    );
11075    cx.update_editor(|editor, cx| {
11076        let snapshot = editor.snapshot(cx);
11077        let all_hunks = editor_hunks(editor, &snapshot, cx);
11078        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11079        assert_eq!(
11080            all_hunks,
11081            vec![(
11082                "".to_string(),
11083                DiffHunkStatus::Added,
11084                DisplayRow(4)..DisplayRow(8)
11085            )]
11086        );
11087        assert_eq!(
11088            expanded_hunks_background_highlights(editor, cx),
11089            vec![DisplayRow(4)..=DisplayRow(6)],
11090            "Deleting a line should shrint the hunk"
11091        );
11092        assert_eq!(
11093            all_hunks, all_expanded_hunks,
11094            "Expanded hunk should also shrink with the addition"
11095        );
11096    });
11097
11098    cx.update_editor(|editor, cx| {
11099        editor.move_up(&MoveUp, cx);
11100        editor.delete_line(&DeleteLine, cx);
11101        editor.move_up(&MoveUp, cx);
11102        editor.delete_line(&DeleteLine, cx);
11103        editor.move_up(&MoveUp, cx);
11104        editor.delete_line(&DeleteLine, cx);
11105    });
11106    executor.run_until_parked();
11107    cx.assert_editor_state(
11108        &r#"
11109        use some::mod1;
11110        use some::mod2;
11111
11112        const A: u32 = 42;
11113        ˇ
11114
11115        fn main() {
11116            println!("hello");
11117
11118            println!("world");
11119        }
11120        "#
11121        .unindent(),
11122    );
11123    cx.update_editor(|editor, cx| {
11124        let snapshot = editor.snapshot(cx);
11125        let all_hunks = editor_hunks(editor, &snapshot, cx);
11126        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11127        assert_eq!(
11128            all_hunks,
11129            vec![(
11130                "".to_string(),
11131                DiffHunkStatus::Added,
11132                DisplayRow(5)..DisplayRow(6)
11133            )]
11134        );
11135        assert_eq!(
11136            expanded_hunks_background_highlights(editor, cx),
11137            vec![DisplayRow(5)..=DisplayRow(5)]
11138        );
11139        assert_eq!(all_hunks, all_expanded_hunks);
11140    });
11141
11142    cx.update_editor(|editor, cx| {
11143        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11144        editor.delete_line(&DeleteLine, cx);
11145    });
11146    executor.run_until_parked();
11147    cx.assert_editor_state(
11148        &r#"
11149        ˇ
11150
11151        fn main() {
11152            println!("hello");
11153
11154            println!("world");
11155        }
11156        "#
11157        .unindent(),
11158    );
11159    cx.update_editor(|editor, cx| {
11160        let snapshot = editor.snapshot(cx);
11161        let all_hunks = editor_hunks(editor, &snapshot, cx);
11162        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11163        assert_eq!(
11164            all_hunks,
11165            vec![
11166                (
11167                    "use some::mod1;\nuse some::mod2;\n".to_string(),
11168                    DiffHunkStatus::Removed,
11169                    DisplayRow(0)..DisplayRow(0)
11170                ),
11171                (
11172                    "const A: u32 = 42;\n".to_string(),
11173                    DiffHunkStatus::Removed,
11174                    DisplayRow(2)..DisplayRow(2)
11175                )
11176            ]
11177        );
11178        assert_eq!(
11179            expanded_hunks_background_highlights(editor, cx),
11180            Vec::new(),
11181            "Should close all stale expanded addition hunks"
11182        );
11183        assert_eq!(
11184            all_expanded_hunks,
11185            vec![(
11186                "const A: u32 = 42;\n".to_string(),
11187                DiffHunkStatus::Removed,
11188                DisplayRow(2)..DisplayRow(2)
11189            )],
11190            "Should open hunks that were adjacent to the stale addition one"
11191        );
11192    });
11193}
11194
11195#[gpui::test]
11196async fn test_edits_around_toggled_deletions(
11197    executor: BackgroundExecutor,
11198    cx: &mut gpui::TestAppContext,
11199) {
11200    init_test(cx, |_| {});
11201
11202    let mut cx = EditorTestContext::new(cx).await;
11203
11204    let diff_base = r#"
11205        use some::mod1;
11206        use some::mod2;
11207
11208        const A: u32 = 42;
11209        const B: u32 = 42;
11210        const C: u32 = 42;
11211
11212
11213        fn main() {
11214            println!("hello");
11215
11216            println!("world");
11217        }
11218        "#
11219    .unindent();
11220    executor.run_until_parked();
11221    cx.set_state(
11222        &r#"
11223        use some::mod1;
11224        use some::mod2;
11225
11226        ˇconst B: u32 = 42;
11227        const C: u32 = 42;
11228
11229
11230        fn main() {
11231            println!("hello");
11232
11233            println!("world");
11234        }
11235        "#
11236        .unindent(),
11237    );
11238
11239    cx.set_diff_base(Some(&diff_base));
11240    executor.run_until_parked();
11241    cx.update_editor(|editor, cx| {
11242        let snapshot = editor.snapshot(cx);
11243        let all_hunks = editor_hunks(editor, &snapshot, cx);
11244        assert_eq!(
11245            all_hunks,
11246            vec![(
11247                "const A: u32 = 42;\n".to_string(),
11248                DiffHunkStatus::Removed,
11249                DisplayRow(3)..DisplayRow(3)
11250            )]
11251        );
11252    });
11253    cx.update_editor(|editor, cx| {
11254        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11255    });
11256    executor.run_until_parked();
11257    cx.assert_editor_state(
11258        &r#"
11259        use some::mod1;
11260        use some::mod2;
11261
11262        ˇconst B: u32 = 42;
11263        const C: u32 = 42;
11264
11265
11266        fn main() {
11267            println!("hello");
11268
11269            println!("world");
11270        }
11271        "#
11272        .unindent(),
11273    );
11274    cx.update_editor(|editor, cx| {
11275        let snapshot = editor.snapshot(cx);
11276        let all_hunks = editor_hunks(editor, &snapshot, cx);
11277        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11278        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11279        assert_eq!(
11280            all_hunks,
11281            vec![(
11282                "const A: u32 = 42;\n".to_string(),
11283                DiffHunkStatus::Removed,
11284                DisplayRow(4)..DisplayRow(4)
11285            )]
11286        );
11287        assert_eq!(all_hunks, all_expanded_hunks);
11288    });
11289
11290    cx.update_editor(|editor, cx| {
11291        editor.delete_line(&DeleteLine, cx);
11292    });
11293    executor.run_until_parked();
11294    cx.assert_editor_state(
11295        &r#"
11296        use some::mod1;
11297        use some::mod2;
11298
11299        ˇconst C: u32 = 42;
11300
11301
11302        fn main() {
11303            println!("hello");
11304
11305            println!("world");
11306        }
11307        "#
11308        .unindent(),
11309    );
11310    cx.update_editor(|editor, cx| {
11311        let snapshot = editor.snapshot(cx);
11312        let all_hunks = editor_hunks(editor, &snapshot, cx);
11313        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11314        assert_eq!(
11315            expanded_hunks_background_highlights(editor, cx),
11316            Vec::new(),
11317            "Deleted hunks do not highlight current editor's background"
11318        );
11319        assert_eq!(
11320            all_hunks,
11321            vec![(
11322                "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11323                DiffHunkStatus::Removed,
11324                DisplayRow(5)..DisplayRow(5)
11325            )]
11326        );
11327        assert_eq!(all_hunks, all_expanded_hunks);
11328    });
11329
11330    cx.update_editor(|editor, cx| {
11331        editor.delete_line(&DeleteLine, cx);
11332    });
11333    executor.run_until_parked();
11334    cx.assert_editor_state(
11335        &r#"
11336        use some::mod1;
11337        use some::mod2;
11338
11339        ˇ
11340
11341        fn main() {
11342            println!("hello");
11343
11344            println!("world");
11345        }
11346        "#
11347        .unindent(),
11348    );
11349    cx.update_editor(|editor, cx| {
11350        let snapshot = editor.snapshot(cx);
11351        let all_hunks = editor_hunks(editor, &snapshot, cx);
11352        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11353        assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11354        assert_eq!(
11355            all_hunks,
11356            vec![(
11357                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11358                DiffHunkStatus::Removed,
11359                DisplayRow(6)..DisplayRow(6)
11360            )]
11361        );
11362        assert_eq!(all_hunks, all_expanded_hunks);
11363    });
11364
11365    cx.update_editor(|editor, cx| {
11366        editor.handle_input("replacement", cx);
11367    });
11368    executor.run_until_parked();
11369    cx.assert_editor_state(
11370        &r#"
11371        use some::mod1;
11372        use some::mod2;
11373
11374        replacementˇ
11375
11376        fn main() {
11377            println!("hello");
11378
11379            println!("world");
11380        }
11381        "#
11382        .unindent(),
11383    );
11384    cx.update_editor(|editor, cx| {
11385        let snapshot = editor.snapshot(cx);
11386        let all_hunks = editor_hunks(editor, &snapshot, cx);
11387        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11388        assert_eq!(
11389            all_hunks,
11390            vec![(
11391                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
11392                DiffHunkStatus::Modified,
11393                DisplayRow(7)..DisplayRow(8)
11394            )]
11395        );
11396        assert_eq!(
11397            expanded_hunks_background_highlights(editor, cx),
11398            vec![DisplayRow(7)..=DisplayRow(7)],
11399            "Modified expanded hunks should display additions and highlight their background"
11400        );
11401        assert_eq!(all_hunks, all_expanded_hunks);
11402    });
11403}
11404
11405#[gpui::test]
11406async fn test_edits_around_toggled_modifications(
11407    executor: BackgroundExecutor,
11408    cx: &mut gpui::TestAppContext,
11409) {
11410    init_test(cx, |_| {});
11411
11412    let mut cx = EditorTestContext::new(cx).await;
11413
11414    let diff_base = r#"
11415        use some::mod1;
11416        use some::mod2;
11417
11418        const A: u32 = 42;
11419        const B: u32 = 42;
11420        const C: u32 = 42;
11421        const D: u32 = 42;
11422
11423
11424        fn main() {
11425            println!("hello");
11426
11427            println!("world");
11428        }"#
11429    .unindent();
11430    executor.run_until_parked();
11431    cx.set_state(
11432        &r#"
11433        use some::mod1;
11434        use some::mod2;
11435
11436        const A: u32 = 42;
11437        const B: u32 = 42;
11438        const C: u32 = 43ˇ
11439        const D: u32 = 42;
11440
11441
11442        fn main() {
11443            println!("hello");
11444
11445            println!("world");
11446        }"#
11447        .unindent(),
11448    );
11449
11450    cx.set_diff_base(Some(&diff_base));
11451    executor.run_until_parked();
11452    cx.update_editor(|editor, cx| {
11453        let snapshot = editor.snapshot(cx);
11454        let all_hunks = editor_hunks(editor, &snapshot, cx);
11455        assert_eq!(
11456            all_hunks,
11457            vec![(
11458                "const C: u32 = 42;\n".to_string(),
11459                DiffHunkStatus::Modified,
11460                DisplayRow(5)..DisplayRow(6)
11461            )]
11462        );
11463    });
11464    cx.update_editor(|editor, cx| {
11465        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11466    });
11467    executor.run_until_parked();
11468    cx.assert_editor_state(
11469        &r#"
11470        use some::mod1;
11471        use some::mod2;
11472
11473        const A: u32 = 42;
11474        const B: u32 = 42;
11475        const C: u32 = 43ˇ
11476        const D: u32 = 42;
11477
11478
11479        fn main() {
11480            println!("hello");
11481
11482            println!("world");
11483        }"#
11484        .unindent(),
11485    );
11486    cx.update_editor(|editor, cx| {
11487        let snapshot = editor.snapshot(cx);
11488        let all_hunks = editor_hunks(editor, &snapshot, cx);
11489        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11490        assert_eq!(
11491            expanded_hunks_background_highlights(editor, cx),
11492            vec![DisplayRow(6)..=DisplayRow(6)],
11493        );
11494        assert_eq!(
11495            all_hunks,
11496            vec![(
11497                "const C: u32 = 42;\n".to_string(),
11498                DiffHunkStatus::Modified,
11499                DisplayRow(6)..DisplayRow(7)
11500            )]
11501        );
11502        assert_eq!(all_hunks, all_expanded_hunks);
11503    });
11504
11505    cx.update_editor(|editor, cx| {
11506        editor.handle_input("\nnew_line\n", cx);
11507    });
11508    executor.run_until_parked();
11509    cx.assert_editor_state(
11510        &r#"
11511            use some::mod1;
11512            use some::mod2;
11513
11514            const A: u32 = 42;
11515            const B: u32 = 42;
11516            const C: u32 = 43
11517            new_line
11518            ˇ
11519            const D: u32 = 42;
11520
11521
11522            fn main() {
11523                println!("hello");
11524
11525                println!("world");
11526            }"#
11527        .unindent(),
11528    );
11529    cx.update_editor(|editor, cx| {
11530        let snapshot = editor.snapshot(cx);
11531        let all_hunks = editor_hunks(editor, &snapshot, cx);
11532        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11533        assert_eq!(
11534            expanded_hunks_background_highlights(editor, cx),
11535            vec![DisplayRow(6)..=DisplayRow(6)],
11536            "Modified hunk should grow highlighted lines on more text additions"
11537        );
11538        assert_eq!(
11539            all_hunks,
11540            vec![(
11541                "const C: u32 = 42;\n".to_string(),
11542                DiffHunkStatus::Modified,
11543                DisplayRow(6)..DisplayRow(9)
11544            )]
11545        );
11546        assert_eq!(all_hunks, all_expanded_hunks);
11547    });
11548
11549    cx.update_editor(|editor, cx| {
11550        editor.move_up(&MoveUp, cx);
11551        editor.move_up(&MoveUp, cx);
11552        editor.move_up(&MoveUp, cx);
11553        editor.delete_line(&DeleteLine, cx);
11554    });
11555    executor.run_until_parked();
11556    cx.assert_editor_state(
11557        &r#"
11558            use some::mod1;
11559            use some::mod2;
11560
11561            const A: u32 = 42;
11562            ˇconst C: u32 = 43
11563            new_line
11564
11565            const D: u32 = 42;
11566
11567
11568            fn main() {
11569                println!("hello");
11570
11571                println!("world");
11572            }"#
11573        .unindent(),
11574    );
11575    cx.update_editor(|editor, cx| {
11576        let snapshot = editor.snapshot(cx);
11577        let all_hunks = editor_hunks(editor, &snapshot, cx);
11578        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11579        assert_eq!(
11580            expanded_hunks_background_highlights(editor, cx),
11581            vec![DisplayRow(6)..=DisplayRow(8)],
11582        );
11583        assert_eq!(
11584            all_hunks,
11585            vec![(
11586                "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11587                DiffHunkStatus::Modified,
11588                DisplayRow(6)..DisplayRow(9)
11589            )],
11590            "Modified hunk should grow deleted lines on text deletions above"
11591        );
11592        assert_eq!(all_hunks, all_expanded_hunks);
11593    });
11594
11595    cx.update_editor(|editor, cx| {
11596        editor.move_up(&MoveUp, cx);
11597        editor.handle_input("v", cx);
11598    });
11599    executor.run_until_parked();
11600    cx.assert_editor_state(
11601        &r#"
11602            use some::mod1;
11603            use some::mod2;
11604
11605            vˇconst A: u32 = 42;
11606            const C: u32 = 43
11607            new_line
11608
11609            const D: u32 = 42;
11610
11611
11612            fn main() {
11613                println!("hello");
11614
11615                println!("world");
11616            }"#
11617        .unindent(),
11618    );
11619    cx.update_editor(|editor, cx| {
11620        let snapshot = editor.snapshot(cx);
11621        let all_hunks = editor_hunks(editor, &snapshot, cx);
11622        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11623        assert_eq!(
11624            expanded_hunks_background_highlights(editor, cx),
11625            vec![DisplayRow(6)..=DisplayRow(9)],
11626            "Modified hunk should grow deleted lines on text modifications above"
11627        );
11628        assert_eq!(
11629            all_hunks,
11630            vec![(
11631                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11632                DiffHunkStatus::Modified,
11633                DisplayRow(6)..DisplayRow(10)
11634            )]
11635        );
11636        assert_eq!(all_hunks, all_expanded_hunks);
11637    });
11638
11639    cx.update_editor(|editor, cx| {
11640        editor.move_down(&MoveDown, cx);
11641        editor.move_down(&MoveDown, cx);
11642        editor.delete_line(&DeleteLine, cx)
11643    });
11644    executor.run_until_parked();
11645    cx.assert_editor_state(
11646        &r#"
11647            use some::mod1;
11648            use some::mod2;
11649
11650            vconst A: u32 = 42;
11651            const C: u32 = 43
11652            ˇ
11653            const D: u32 = 42;
11654
11655
11656            fn main() {
11657                println!("hello");
11658
11659                println!("world");
11660            }"#
11661        .unindent(),
11662    );
11663    cx.update_editor(|editor, cx| {
11664        let snapshot = editor.snapshot(cx);
11665        let all_hunks = editor_hunks(editor, &snapshot, cx);
11666        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11667        assert_eq!(
11668            expanded_hunks_background_highlights(editor, cx),
11669            vec![DisplayRow(6)..=DisplayRow(8)],
11670            "Modified hunk should grow shrink lines on modification lines removal"
11671        );
11672        assert_eq!(
11673            all_hunks,
11674            vec![(
11675                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11676                DiffHunkStatus::Modified,
11677                DisplayRow(6)..DisplayRow(9)
11678            )]
11679        );
11680        assert_eq!(all_hunks, all_expanded_hunks);
11681    });
11682
11683    cx.update_editor(|editor, cx| {
11684        editor.move_up(&MoveUp, cx);
11685        editor.move_up(&MoveUp, cx);
11686        editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11687        editor.delete_line(&DeleteLine, cx)
11688    });
11689    executor.run_until_parked();
11690    cx.assert_editor_state(
11691        &r#"
11692            use some::mod1;
11693            use some::mod2;
11694
11695            ˇ
11696
11697            fn main() {
11698                println!("hello");
11699
11700                println!("world");
11701            }"#
11702        .unindent(),
11703    );
11704    cx.update_editor(|editor, cx| {
11705        let snapshot = editor.snapshot(cx);
11706        let all_hunks = editor_hunks(editor, &snapshot, cx);
11707        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11708        assert_eq!(
11709            expanded_hunks_background_highlights(editor, cx),
11710            Vec::new(),
11711            "Modified hunk should turn into a removed one on all modified lines removal"
11712        );
11713        assert_eq!(
11714            all_hunks,
11715            vec![(
11716                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11717                    .to_string(),
11718                DiffHunkStatus::Removed,
11719                DisplayRow(7)..DisplayRow(7)
11720            )]
11721        );
11722        assert_eq!(all_hunks, all_expanded_hunks);
11723    });
11724}
11725
11726#[gpui::test]
11727async fn test_multiple_expanded_hunks_merge(
11728    executor: BackgroundExecutor,
11729    cx: &mut gpui::TestAppContext,
11730) {
11731    init_test(cx, |_| {});
11732
11733    let mut cx = EditorTestContext::new(cx).await;
11734
11735    let diff_base = r#"
11736        use some::mod1;
11737        use some::mod2;
11738
11739        const A: u32 = 42;
11740        const B: u32 = 42;
11741        const C: u32 = 42;
11742        const D: u32 = 42;
11743
11744
11745        fn main() {
11746            println!("hello");
11747
11748            println!("world");
11749        }"#
11750    .unindent();
11751    executor.run_until_parked();
11752    cx.set_state(
11753        &r#"
11754        use some::mod1;
11755        use some::mod2;
11756
11757        const A: u32 = 42;
11758        const B: u32 = 42;
11759        const C: u32 = 43ˇ
11760        const D: u32 = 42;
11761
11762
11763        fn main() {
11764            println!("hello");
11765
11766            println!("world");
11767        }"#
11768        .unindent(),
11769    );
11770
11771    cx.set_diff_base(Some(&diff_base));
11772    executor.run_until_parked();
11773    cx.update_editor(|editor, cx| {
11774        let snapshot = editor.snapshot(cx);
11775        let all_hunks = editor_hunks(editor, &snapshot, cx);
11776        assert_eq!(
11777            all_hunks,
11778            vec![(
11779                "const C: u32 = 42;\n".to_string(),
11780                DiffHunkStatus::Modified,
11781                DisplayRow(5)..DisplayRow(6)
11782            )]
11783        );
11784    });
11785    cx.update_editor(|editor, cx| {
11786        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11787    });
11788    executor.run_until_parked();
11789    cx.assert_editor_state(
11790        &r#"
11791        use some::mod1;
11792        use some::mod2;
11793
11794        const A: u32 = 42;
11795        const B: u32 = 42;
11796        const C: u32 = 43ˇ
11797        const D: u32 = 42;
11798
11799
11800        fn main() {
11801            println!("hello");
11802
11803            println!("world");
11804        }"#
11805        .unindent(),
11806    );
11807    cx.update_editor(|editor, cx| {
11808        let snapshot = editor.snapshot(cx);
11809        let all_hunks = editor_hunks(editor, &snapshot, cx);
11810        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11811        assert_eq!(
11812            expanded_hunks_background_highlights(editor, cx),
11813            vec![DisplayRow(6)..=DisplayRow(6)],
11814        );
11815        assert_eq!(
11816            all_hunks,
11817            vec![(
11818                "const C: u32 = 42;\n".to_string(),
11819                DiffHunkStatus::Modified,
11820                DisplayRow(6)..DisplayRow(7)
11821            )]
11822        );
11823        assert_eq!(all_hunks, all_expanded_hunks);
11824    });
11825
11826    cx.update_editor(|editor, cx| {
11827        editor.handle_input("\nnew_line\n", cx);
11828    });
11829    executor.run_until_parked();
11830    cx.assert_editor_state(
11831        &r#"
11832            use some::mod1;
11833            use some::mod2;
11834
11835            const A: u32 = 42;
11836            const B: u32 = 42;
11837            const C: u32 = 43
11838            new_line
11839            ˇ
11840            const D: u32 = 42;
11841
11842
11843            fn main() {
11844                println!("hello");
11845
11846                println!("world");
11847            }"#
11848        .unindent(),
11849    );
11850}
11851
11852async fn setup_indent_guides_editor(
11853    text: &str,
11854    cx: &mut gpui::TestAppContext,
11855) -> (BufferId, EditorTestContext) {
11856    init_test(cx, |_| {});
11857
11858    let mut cx = EditorTestContext::new(cx).await;
11859
11860    let buffer_id = cx.update_editor(|editor, cx| {
11861        editor.set_text(text, cx);
11862        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11863        let buffer_id = buffer_ids[0];
11864        buffer_id
11865    });
11866
11867    (buffer_id, cx)
11868}
11869
11870fn assert_indent_guides(
11871    range: Range<u32>,
11872    expected: Vec<IndentGuide>,
11873    active_indices: Option<Vec<usize>>,
11874    cx: &mut EditorTestContext,
11875) {
11876    let indent_guides = cx.update_editor(|editor, cx| {
11877        let snapshot = editor.snapshot(cx).display_snapshot;
11878        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11879            MultiBufferRow(range.start)..MultiBufferRow(range.end),
11880            true,
11881            &snapshot,
11882            cx,
11883        );
11884
11885        indent_guides.sort_by(|a, b| {
11886            a.depth.cmp(&b.depth).then(
11887                a.start_row
11888                    .cmp(&b.start_row)
11889                    .then(a.end_row.cmp(&b.end_row)),
11890            )
11891        });
11892        indent_guides
11893    });
11894
11895    if let Some(expected) = active_indices {
11896        let active_indices = cx.update_editor(|editor, cx| {
11897            let snapshot = editor.snapshot(cx).display_snapshot;
11898            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11899        });
11900
11901        assert_eq!(
11902            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11903            expected,
11904            "Active indent guide indices do not match"
11905        );
11906    }
11907
11908    let expected: Vec<_> = expected
11909        .into_iter()
11910        .map(|guide| MultiBufferIndentGuide {
11911            multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11912            buffer: guide,
11913        })
11914        .collect();
11915
11916    assert_eq!(indent_guides, expected, "Indent guides do not match");
11917}
11918
11919fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
11920    IndentGuide {
11921        buffer_id,
11922        start_row,
11923        end_row,
11924        depth,
11925        tab_size: 4,
11926        settings: IndentGuideSettings {
11927            enabled: true,
11928            line_width: 1,
11929            active_line_width: 1,
11930            ..Default::default()
11931        },
11932    }
11933}
11934
11935#[gpui::test]
11936async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11937    let (buffer_id, mut cx) = setup_indent_guides_editor(
11938        &"
11939    fn main() {
11940        let a = 1;
11941    }"
11942        .unindent(),
11943        cx,
11944    )
11945    .await;
11946
11947    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11948}
11949
11950#[gpui::test]
11951async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11952    let (buffer_id, mut cx) = setup_indent_guides_editor(
11953        &"
11954    fn main() {
11955        let a = 1;
11956        let b = 2;
11957    }"
11958        .unindent(),
11959        cx,
11960    )
11961    .await;
11962
11963    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
11964}
11965
11966#[gpui::test]
11967async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11968    let (buffer_id, mut cx) = setup_indent_guides_editor(
11969        &"
11970    fn main() {
11971        let a = 1;
11972        if a == 3 {
11973            let b = 2;
11974        } else {
11975            let c = 3;
11976        }
11977    }"
11978        .unindent(),
11979        cx,
11980    )
11981    .await;
11982
11983    assert_indent_guides(
11984        0..8,
11985        vec![
11986            indent_guide(buffer_id, 1, 6, 0),
11987            indent_guide(buffer_id, 3, 3, 1),
11988            indent_guide(buffer_id, 5, 5, 1),
11989        ],
11990        None,
11991        &mut cx,
11992    );
11993}
11994
11995#[gpui::test]
11996async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11997    let (buffer_id, mut cx) = setup_indent_guides_editor(
11998        &"
11999    fn main() {
12000        let a = 1;
12001            let b = 2;
12002        let c = 3;
12003    }"
12004        .unindent(),
12005        cx,
12006    )
12007    .await;
12008
12009    assert_indent_guides(
12010        0..5,
12011        vec![
12012            indent_guide(buffer_id, 1, 3, 0),
12013            indent_guide(buffer_id, 2, 2, 1),
12014        ],
12015        None,
12016        &mut cx,
12017    );
12018}
12019
12020#[gpui::test]
12021async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12022    let (buffer_id, mut cx) = setup_indent_guides_editor(
12023        &"
12024        fn main() {
12025            let a = 1;
12026
12027            let c = 3;
12028        }"
12029        .unindent(),
12030        cx,
12031    )
12032    .await;
12033
12034    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12035}
12036
12037#[gpui::test]
12038async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12039    let (buffer_id, mut cx) = setup_indent_guides_editor(
12040        &"
12041        fn main() {
12042            let a = 1;
12043
12044            let c = 3;
12045
12046            if a == 3 {
12047                let b = 2;
12048            } else {
12049                let c = 3;
12050            }
12051        }"
12052        .unindent(),
12053        cx,
12054    )
12055    .await;
12056
12057    assert_indent_guides(
12058        0..11,
12059        vec![
12060            indent_guide(buffer_id, 1, 9, 0),
12061            indent_guide(buffer_id, 6, 6, 1),
12062            indent_guide(buffer_id, 8, 8, 1),
12063        ],
12064        None,
12065        &mut cx,
12066    );
12067}
12068
12069#[gpui::test]
12070async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12071    let (buffer_id, mut cx) = setup_indent_guides_editor(
12072        &"
12073        fn main() {
12074            let a = 1;
12075
12076            let c = 3;
12077
12078            if a == 3 {
12079                let b = 2;
12080            } else {
12081                let c = 3;
12082            }
12083        }"
12084        .unindent(),
12085        cx,
12086    )
12087    .await;
12088
12089    assert_indent_guides(
12090        1..11,
12091        vec![
12092            indent_guide(buffer_id, 1, 9, 0),
12093            indent_guide(buffer_id, 6, 6, 1),
12094            indent_guide(buffer_id, 8, 8, 1),
12095        ],
12096        None,
12097        &mut cx,
12098    );
12099}
12100
12101#[gpui::test]
12102async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12103    let (buffer_id, mut cx) = setup_indent_guides_editor(
12104        &"
12105        fn main() {
12106            let a = 1;
12107
12108            let c = 3;
12109
12110            if a == 3 {
12111                let b = 2;
12112            } else {
12113                let c = 3;
12114            }
12115        }"
12116        .unindent(),
12117        cx,
12118    )
12119    .await;
12120
12121    assert_indent_guides(
12122        1..10,
12123        vec![
12124            indent_guide(buffer_id, 1, 9, 0),
12125            indent_guide(buffer_id, 6, 6, 1),
12126            indent_guide(buffer_id, 8, 8, 1),
12127        ],
12128        None,
12129        &mut cx,
12130    );
12131}
12132
12133#[gpui::test]
12134async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12135    let (buffer_id, mut cx) = setup_indent_guides_editor(
12136        &"
12137        block1
12138            block2
12139                block3
12140                    block4
12141            block2
12142        block1
12143        block1"
12144            .unindent(),
12145        cx,
12146    )
12147    .await;
12148
12149    assert_indent_guides(
12150        1..10,
12151        vec![
12152            indent_guide(buffer_id, 1, 4, 0),
12153            indent_guide(buffer_id, 2, 3, 1),
12154            indent_guide(buffer_id, 3, 3, 2),
12155        ],
12156        None,
12157        &mut cx,
12158    );
12159}
12160
12161#[gpui::test]
12162async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12163    let (buffer_id, mut cx) = setup_indent_guides_editor(
12164        &"
12165        block1
12166            block2
12167                block3
12168
12169        block1
12170        block1"
12171            .unindent(),
12172        cx,
12173    )
12174    .await;
12175
12176    assert_indent_guides(
12177        0..6,
12178        vec![
12179            indent_guide(buffer_id, 1, 2, 0),
12180            indent_guide(buffer_id, 2, 2, 1),
12181        ],
12182        None,
12183        &mut cx,
12184    );
12185}
12186
12187#[gpui::test]
12188async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12189    let (buffer_id, mut cx) = setup_indent_guides_editor(
12190        &"
12191        block1
12192
12193
12194
12195            block2
12196        "
12197        .unindent(),
12198        cx,
12199    )
12200    .await;
12201
12202    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12203}
12204
12205#[gpui::test]
12206async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12207    let (buffer_id, mut cx) = setup_indent_guides_editor(
12208        &"
12209        def a:
12210        \tb = 3
12211        \tif True:
12212        \t\tc = 4
12213        \t\td = 5
12214        \tprint(b)
12215        "
12216        .unindent(),
12217        cx,
12218    )
12219    .await;
12220
12221    assert_indent_guides(
12222        0..6,
12223        vec![
12224            indent_guide(buffer_id, 1, 6, 0),
12225            indent_guide(buffer_id, 3, 4, 1),
12226        ],
12227        None,
12228        &mut cx,
12229    );
12230}
12231
12232#[gpui::test]
12233async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12234    let (buffer_id, mut cx) = setup_indent_guides_editor(
12235        &"
12236    fn main() {
12237        let a = 1;
12238    }"
12239        .unindent(),
12240        cx,
12241    )
12242    .await;
12243
12244    cx.update_editor(|editor, cx| {
12245        editor.change_selections(None, cx, |s| {
12246            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12247        });
12248    });
12249
12250    assert_indent_guides(
12251        0..3,
12252        vec![indent_guide(buffer_id, 1, 1, 0)],
12253        Some(vec![0]),
12254        &mut cx,
12255    );
12256}
12257
12258#[gpui::test]
12259async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12260    let (buffer_id, mut cx) = setup_indent_guides_editor(
12261        &"
12262    fn main() {
12263        if 1 == 2 {
12264            let a = 1;
12265        }
12266    }"
12267        .unindent(),
12268        cx,
12269    )
12270    .await;
12271
12272    cx.update_editor(|editor, cx| {
12273        editor.change_selections(None, cx, |s| {
12274            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12275        });
12276    });
12277
12278    assert_indent_guides(
12279        0..4,
12280        vec![
12281            indent_guide(buffer_id, 1, 3, 0),
12282            indent_guide(buffer_id, 2, 2, 1),
12283        ],
12284        Some(vec![1]),
12285        &mut cx,
12286    );
12287
12288    cx.update_editor(|editor, cx| {
12289        editor.change_selections(None, cx, |s| {
12290            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12291        });
12292    });
12293
12294    assert_indent_guides(
12295        0..4,
12296        vec![
12297            indent_guide(buffer_id, 1, 3, 0),
12298            indent_guide(buffer_id, 2, 2, 1),
12299        ],
12300        Some(vec![1]),
12301        &mut cx,
12302    );
12303
12304    cx.update_editor(|editor, cx| {
12305        editor.change_selections(None, cx, |s| {
12306            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12307        });
12308    });
12309
12310    assert_indent_guides(
12311        0..4,
12312        vec![
12313            indent_guide(buffer_id, 1, 3, 0),
12314            indent_guide(buffer_id, 2, 2, 1),
12315        ],
12316        Some(vec![0]),
12317        &mut cx,
12318    );
12319}
12320
12321#[gpui::test]
12322async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12323    let (buffer_id, mut cx) = setup_indent_guides_editor(
12324        &"
12325    fn main() {
12326        let a = 1;
12327
12328        let b = 2;
12329    }"
12330        .unindent(),
12331        cx,
12332    )
12333    .await;
12334
12335    cx.update_editor(|editor, cx| {
12336        editor.change_selections(None, cx, |s| {
12337            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12338        });
12339    });
12340
12341    assert_indent_guides(
12342        0..5,
12343        vec![indent_guide(buffer_id, 1, 3, 0)],
12344        Some(vec![0]),
12345        &mut cx,
12346    );
12347}
12348
12349#[gpui::test]
12350async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12351    let (buffer_id, mut cx) = setup_indent_guides_editor(
12352        &"
12353    def m:
12354        a = 1
12355        pass"
12356            .unindent(),
12357        cx,
12358    )
12359    .await;
12360
12361    cx.update_editor(|editor, cx| {
12362        editor.change_selections(None, cx, |s| {
12363            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12364        });
12365    });
12366
12367    assert_indent_guides(
12368        0..3,
12369        vec![indent_guide(buffer_id, 1, 2, 0)],
12370        Some(vec![0]),
12371        &mut cx,
12372    );
12373}
12374
12375#[gpui::test]
12376fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12377    init_test(cx, |_| {});
12378
12379    let editor = cx.add_window(|cx| {
12380        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12381        build_editor(buffer, cx)
12382    });
12383
12384    let render_args = Arc::new(Mutex::new(None));
12385    let snapshot = editor
12386        .update(cx, |editor, cx| {
12387            let snapshot = editor.buffer().read(cx).snapshot(cx);
12388            let range =
12389                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12390
12391            struct RenderArgs {
12392                row: MultiBufferRow,
12393                folded: bool,
12394                callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12395            }
12396
12397            let crease = Crease::new(
12398                range,
12399                FoldPlaceholder::test(),
12400                {
12401                    let toggle_callback = render_args.clone();
12402                    move |row, folded, callback, _cx| {
12403                        *toggle_callback.lock() = Some(RenderArgs {
12404                            row,
12405                            folded,
12406                            callback,
12407                        });
12408                        div()
12409                    }
12410                },
12411                |_row, _folded, _cx| div(),
12412            );
12413
12414            editor.insert_creases(Some(crease), cx);
12415            let snapshot = editor.snapshot(cx);
12416            let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12417            snapshot
12418        })
12419        .unwrap();
12420
12421    let render_args = render_args.lock().take().unwrap();
12422    assert_eq!(render_args.row, MultiBufferRow(1));
12423    assert_eq!(render_args.folded, false);
12424    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12425
12426    cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12427        .unwrap();
12428    let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12429    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12430
12431    cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12432        .unwrap();
12433    let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12434    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12435}
12436
12437fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12438    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12439    point..point
12440}
12441
12442fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12443    let (text, ranges) = marked_text_ranges(marked_text, true);
12444    assert_eq!(view.text(cx), text);
12445    assert_eq!(
12446        view.selections.ranges(cx),
12447        ranges,
12448        "Assert selections are {}",
12449        marked_text
12450    );
12451}
12452
12453/// Handle completion request passing a marked string specifying where the completion
12454/// should be triggered from using '|' character, what range should be replaced, and what completions
12455/// should be returned using '<' and '>' to delimit the range
12456pub fn handle_completion_request(
12457    cx: &mut EditorLspTestContext,
12458    marked_string: &str,
12459    completions: Vec<&'static str>,
12460    counter: Arc<AtomicUsize>,
12461) -> impl Future<Output = ()> {
12462    let complete_from_marker: TextRangeMarker = '|'.into();
12463    let replace_range_marker: TextRangeMarker = ('<', '>').into();
12464    let (_, mut marked_ranges) = marked_text_ranges_by(
12465        marked_string,
12466        vec![complete_from_marker.clone(), replace_range_marker.clone()],
12467    );
12468
12469    let complete_from_position =
12470        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12471    let replace_range =
12472        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12473
12474    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12475        let completions = completions.clone();
12476        counter.fetch_add(1, atomic::Ordering::Release);
12477        async move {
12478            assert_eq!(params.text_document_position.text_document.uri, url.clone());
12479            assert_eq!(
12480                params.text_document_position.position,
12481                complete_from_position
12482            );
12483            Ok(Some(lsp::CompletionResponse::Array(
12484                completions
12485                    .iter()
12486                    .map(|completion_text| lsp::CompletionItem {
12487                        label: completion_text.to_string(),
12488                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12489                            range: replace_range,
12490                            new_text: completion_text.to_string(),
12491                        })),
12492                        ..Default::default()
12493                    })
12494                    .collect(),
12495            )))
12496        }
12497    });
12498
12499    async move {
12500        request.next().await;
12501    }
12502}
12503
12504fn handle_resolve_completion_request(
12505    cx: &mut EditorLspTestContext,
12506    edits: Option<Vec<(&'static str, &'static str)>>,
12507) -> impl Future<Output = ()> {
12508    let edits = edits.map(|edits| {
12509        edits
12510            .iter()
12511            .map(|(marked_string, new_text)| {
12512                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12513                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12514                lsp::TextEdit::new(replace_range, new_text.to_string())
12515            })
12516            .collect::<Vec<_>>()
12517    });
12518
12519    let mut request =
12520        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12521            let edits = edits.clone();
12522            async move {
12523                Ok(lsp::CompletionItem {
12524                    additional_text_edits: edits,
12525                    ..Default::default()
12526                })
12527            }
12528        });
12529
12530    async move {
12531        request.next().await;
12532    }
12533}
12534
12535pub(crate) fn update_test_language_settings(
12536    cx: &mut TestAppContext,
12537    f: impl Fn(&mut AllLanguageSettingsContent),
12538) {
12539    _ = cx.update(|cx| {
12540        SettingsStore::update_global(cx, |store, cx| {
12541            store.update_user_settings::<AllLanguageSettings>(cx, f);
12542        });
12543    });
12544}
12545
12546pub(crate) fn update_test_project_settings(
12547    cx: &mut TestAppContext,
12548    f: impl Fn(&mut ProjectSettings),
12549) {
12550    _ = cx.update(|cx| {
12551        SettingsStore::update_global(cx, |store, cx| {
12552            store.update_user_settings::<ProjectSettings>(cx, f);
12553        });
12554    });
12555}
12556
12557pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12558    _ = cx.update(|cx| {
12559        assets::Assets.load_test_fonts(cx);
12560        let store = SettingsStore::test(cx);
12561        cx.set_global(store);
12562        theme::init(theme::LoadThemes::JustBase, cx);
12563        release_channel::init(SemanticVersion::default(), cx);
12564        client::init_settings(cx);
12565        language::init(cx);
12566        Project::init_settings(cx);
12567        workspace::init_settings(cx);
12568        crate::init(cx);
12569    });
12570
12571    update_test_language_settings(cx, f);
12572}
12573
12574pub(crate) fn rust_lang() -> Arc<Language> {
12575    Arc::new(Language::new(
12576        LanguageConfig {
12577            name: "Rust".into(),
12578            matcher: LanguageMatcher {
12579                path_suffixes: vec!["rs".to_string()],
12580                ..Default::default()
12581            },
12582            ..Default::default()
12583        },
12584        Some(tree_sitter_rust::language()),
12585    ))
12586}
12587
12588#[track_caller]
12589fn assert_hunk_revert(
12590    not_reverted_text_with_selections: &str,
12591    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12592    expected_reverted_text_with_selections: &str,
12593    base_text: &str,
12594    cx: &mut EditorLspTestContext,
12595) {
12596    cx.set_state(not_reverted_text_with_selections);
12597    cx.update_editor(|editor, cx| {
12598        editor
12599            .buffer()
12600            .read(cx)
12601            .as_singleton()
12602            .unwrap()
12603            .update(cx, |buffer, cx| {
12604                buffer.set_diff_base(Some(base_text.into()), cx);
12605            });
12606    });
12607    cx.executor().run_until_parked();
12608
12609    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12610        let snapshot = editor.buffer().read(cx).snapshot(cx);
12611        let reverted_hunk_statuses = snapshot
12612            .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12613            .map(|hunk| hunk_status(&hunk))
12614            .collect::<Vec<_>>();
12615
12616        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12617        reverted_hunk_statuses
12618    });
12619    cx.executor().run_until_parked();
12620    cx.assert_editor_state(expected_reverted_text_with_selections);
12621    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12622}