editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   26    Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
   59    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   60};
   61
   62#[gpui::test]
   63fn test_edit_events(cx: &mut TestAppContext) {
   64    init_test(cx, |_| {});
   65
   66    let buffer = cx.new(|cx| {
   67        let mut buffer = language::Buffer::local("123456", cx);
   68        buffer.set_group_interval(Duration::from_secs(1));
   69        buffer
   70    });
   71
   72    let events = Rc::new(RefCell::new(Vec::new()));
   73    let editor1 = cx.add_window({
   74        let events = events.clone();
   75        |window, cx| {
   76            let entity = cx.entity().clone();
   77            cx.subscribe_in(
   78                &entity,
   79                window,
   80                move |_, _, event: &EditorEvent, _, _| match event {
   81                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   82                    EditorEvent::BufferEdited => {
   83                        events.borrow_mut().push(("editor1", "buffer edited"))
   84                    }
   85                    _ => {}
   86                },
   87            )
   88            .detach();
   89            Editor::for_buffer(buffer.clone(), None, window, cx)
   90        }
   91    });
   92
   93    let editor2 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            cx.subscribe_in(
   97                &cx.entity().clone(),
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor2", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  113
  114    // Mutating editor 1 will emit an `Edited` event only for that editor.
  115    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  116    assert_eq!(
  117        mem::take(&mut *events.borrow_mut()),
  118        [
  119            ("editor1", "edited"),
  120            ("editor1", "buffer edited"),
  121            ("editor2", "buffer edited"),
  122        ]
  123    );
  124
  125    // Mutating editor 2 will emit an `Edited` event only for that editor.
  126    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  127    assert_eq!(
  128        mem::take(&mut *events.borrow_mut()),
  129        [
  130            ("editor2", "edited"),
  131            ("editor1", "buffer edited"),
  132            ("editor2", "buffer edited"),
  133        ]
  134    );
  135
  136    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  137    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  138    assert_eq!(
  139        mem::take(&mut *events.borrow_mut()),
  140        [
  141            ("editor1", "edited"),
  142            ("editor1", "buffer edited"),
  143            ("editor2", "buffer edited"),
  144        ]
  145    );
  146
  147    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  148    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  149    assert_eq!(
  150        mem::take(&mut *events.borrow_mut()),
  151        [
  152            ("editor1", "edited"),
  153            ("editor1", "buffer edited"),
  154            ("editor2", "buffer edited"),
  155        ]
  156    );
  157
  158    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  159    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  160    assert_eq!(
  161        mem::take(&mut *events.borrow_mut()),
  162        [
  163            ("editor2", "edited"),
  164            ("editor1", "buffer edited"),
  165            ("editor2", "buffer edited"),
  166        ]
  167    );
  168
  169    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  170    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  171    assert_eq!(
  172        mem::take(&mut *events.borrow_mut()),
  173        [
  174            ("editor2", "edited"),
  175            ("editor1", "buffer edited"),
  176            ("editor2", "buffer edited"),
  177        ]
  178    );
  179
  180    // No event is emitted when the mutation is a no-op.
  181    _ = editor2.update(cx, |editor, window, cx| {
  182        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  183
  184        editor.backspace(&Backspace, window, cx);
  185    });
  186    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  187}
  188
  189#[gpui::test]
  190fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  191    init_test(cx, |_| {});
  192
  193    let mut now = Instant::now();
  194    let group_interval = Duration::from_millis(1);
  195    let buffer = cx.new(|cx| {
  196        let mut buf = language::Buffer::local("123456", cx);
  197        buf.set_group_interval(group_interval);
  198        buf
  199    });
  200    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  201    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  202
  203    _ = editor.update(cx, |editor, window, cx| {
  204        editor.start_transaction_at(now, window, cx);
  205        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  206
  207        editor.insert("cd", window, cx);
  208        editor.end_transaction_at(now, cx);
  209        assert_eq!(editor.text(cx), "12cd56");
  210        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  211
  212        editor.start_transaction_at(now, window, cx);
  213        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  214        editor.insert("e", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cde6");
  217        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  218
  219        now += group_interval + Duration::from_millis(1);
  220        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  221
  222        // Simulate an edit in another editor
  223        buffer.update(cx, |buffer, cx| {
  224            buffer.start_transaction_at(now, cx);
  225            buffer.edit([(0..1, "a")], None, cx);
  226            buffer.edit([(1..1, "b")], None, cx);
  227            buffer.end_transaction_at(now, cx);
  228        });
  229
  230        assert_eq!(editor.text(cx), "ab2cde6");
  231        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  232
  233        // Last transaction happened past the group interval in a different editor.
  234        // Undo it individually and don't restore selections.
  235        editor.undo(&Undo, window, cx);
  236        assert_eq!(editor.text(cx), "12cde6");
  237        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  238
  239        // First two transactions happened within the group interval in this editor.
  240        // Undo them together and restore selections.
  241        editor.undo(&Undo, window, cx);
  242        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  243        assert_eq!(editor.text(cx), "123456");
  244        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  245
  246        // Redo the first two transactions together.
  247        editor.redo(&Redo, window, cx);
  248        assert_eq!(editor.text(cx), "12cde6");
  249        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  250
  251        // Redo the last transaction on its own.
  252        editor.redo(&Redo, window, cx);
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  255
  256        // Test empty transactions.
  257        editor.start_transaction_at(now, window, cx);
  258        editor.end_transaction_at(now, cx);
  259        editor.undo(&Undo, window, cx);
  260        assert_eq!(editor.text(cx), "12cde6");
  261    });
  262}
  263
  264#[gpui::test]
  265fn test_ime_composition(cx: &mut TestAppContext) {
  266    init_test(cx, |_| {});
  267
  268    let buffer = cx.new(|cx| {
  269        let mut buffer = language::Buffer::local("abcde", cx);
  270        // Ensure automatic grouping doesn't occur.
  271        buffer.set_group_interval(Duration::ZERO);
  272        buffer
  273    });
  274
  275    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  276    cx.add_window(|window, cx| {
  277        let mut editor = build_editor(buffer.clone(), window, cx);
  278
  279        // Start a new IME composition.
  280        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  281        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  282        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  283        assert_eq!(editor.text(cx), "äbcde");
  284        assert_eq!(
  285            editor.marked_text_ranges(cx),
  286            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  287        );
  288
  289        // Finalize IME composition.
  290        editor.replace_text_in_range(None, "ā", window, cx);
  291        assert_eq!(editor.text(cx), "ābcde");
  292        assert_eq!(editor.marked_text_ranges(cx), None);
  293
  294        // IME composition edits are grouped and are undone/redone at once.
  295        editor.undo(&Default::default(), window, cx);
  296        assert_eq!(editor.text(cx), "abcde");
  297        assert_eq!(editor.marked_text_ranges(cx), None);
  298        editor.redo(&Default::default(), window, cx);
  299        assert_eq!(editor.text(cx), "ābcde");
  300        assert_eq!(editor.marked_text_ranges(cx), None);
  301
  302        // Start a new IME composition.
  303        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  304        assert_eq!(
  305            editor.marked_text_ranges(cx),
  306            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  307        );
  308
  309        // Undoing during an IME composition cancels it.
  310        editor.undo(&Default::default(), window, cx);
  311        assert_eq!(editor.text(cx), "ābcde");
  312        assert_eq!(editor.marked_text_ranges(cx), None);
  313
  314        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  315        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  316        assert_eq!(editor.text(cx), "ābcdè");
  317        assert_eq!(
  318            editor.marked_text_ranges(cx),
  319            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  320        );
  321
  322        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  323        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  324        assert_eq!(editor.text(cx), "ābcdę");
  325        assert_eq!(editor.marked_text_ranges(cx), None);
  326
  327        // Start a new IME composition with multiple cursors.
  328        editor.change_selections(None, window, cx, |s| {
  329            s.select_ranges([
  330                OffsetUtf16(1)..OffsetUtf16(1),
  331                OffsetUtf16(3)..OffsetUtf16(3),
  332                OffsetUtf16(5)..OffsetUtf16(5),
  333            ])
  334        });
  335        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  336        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  337        assert_eq!(
  338            editor.marked_text_ranges(cx),
  339            Some(vec![
  340                OffsetUtf16(0)..OffsetUtf16(3),
  341                OffsetUtf16(4)..OffsetUtf16(7),
  342                OffsetUtf16(8)..OffsetUtf16(11)
  343            ])
  344        );
  345
  346        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  347        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  348        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  349        assert_eq!(
  350            editor.marked_text_ranges(cx),
  351            Some(vec![
  352                OffsetUtf16(1)..OffsetUtf16(2),
  353                OffsetUtf16(5)..OffsetUtf16(6),
  354                OffsetUtf16(9)..OffsetUtf16(10)
  355            ])
  356        );
  357
  358        // Finalize IME composition with multiple cursors.
  359        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  360        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  361        assert_eq!(editor.marked_text_ranges(cx), None);
  362
  363        editor
  364    });
  365}
  366
  367#[gpui::test]
  368fn test_selection_with_mouse(cx: &mut TestAppContext) {
  369    init_test(cx, |_| {});
  370
  371    let editor = cx.add_window(|window, cx| {
  372        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  373        build_editor(buffer, window, cx)
  374    });
  375
  376    _ = editor.update(cx, |editor, window, cx| {
  377        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  378    });
  379    assert_eq!(
  380        editor
  381            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  382            .unwrap(),
  383        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  384    );
  385
  386    _ = editor.update(cx, |editor, window, cx| {
  387        editor.update_selection(
  388            DisplayPoint::new(DisplayRow(3), 3),
  389            0,
  390            gpui::Point::<f32>::default(),
  391            window,
  392            cx,
  393        );
  394    });
  395
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(1), 1),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.end_selection(window, cx);
  422        editor.update_selection(
  423            DisplayPoint::new(DisplayRow(3), 3),
  424            0,
  425            gpui::Point::<f32>::default(),
  426            window,
  427            cx,
  428        );
  429    });
  430
  431    assert_eq!(
  432        editor
  433            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  434            .unwrap(),
  435        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  436    );
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  440        editor.update_selection(
  441            DisplayPoint::new(DisplayRow(0), 0),
  442            0,
  443            gpui::Point::<f32>::default(),
  444            window,
  445            cx,
  446        );
  447    });
  448
  449    assert_eq!(
  450        editor
  451            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  452            .unwrap(),
  453        [
  454            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  455            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  456        ]
  457    );
  458
  459    _ = editor.update(cx, |editor, window, cx| {
  460        editor.end_selection(window, cx);
  461    });
  462
  463    assert_eq!(
  464        editor
  465            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  466            .unwrap(),
  467        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  468    );
  469}
  470
  471#[gpui::test]
  472fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  473    init_test(cx, |_| {});
  474
  475    let editor = cx.add_window(|window, cx| {
  476        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  477        build_editor(buffer, window, cx)
  478    });
  479
  480    _ = editor.update(cx, |editor, window, cx| {
  481        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  482    });
  483
  484    _ = editor.update(cx, |editor, window, cx| {
  485        editor.end_selection(window, cx);
  486    });
  487
  488    _ = editor.update(cx, |editor, window, cx| {
  489        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  490    });
  491
  492    _ = editor.update(cx, |editor, window, cx| {
  493        editor.end_selection(window, cx);
  494    });
  495
  496    assert_eq!(
  497        editor
  498            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  499            .unwrap(),
  500        [
  501            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  502            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  503        ]
  504    );
  505
  506    _ = editor.update(cx, |editor, window, cx| {
  507        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  508    });
  509
  510    _ = editor.update(cx, |editor, window, cx| {
  511        editor.end_selection(window, cx);
  512    });
  513
  514    assert_eq!(
  515        editor
  516            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  517            .unwrap(),
  518        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  519    );
  520}
  521
  522#[gpui::test]
  523fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  524    init_test(cx, |_| {});
  525
  526    let editor = cx.add_window(|window, cx| {
  527        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  528        build_editor(buffer, window, cx)
  529    });
  530
  531    _ = editor.update(cx, |editor, window, cx| {
  532        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  533        assert_eq!(
  534            editor.selections.display_ranges(cx),
  535            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  536        );
  537    });
  538
  539    _ = editor.update(cx, |editor, window, cx| {
  540        editor.update_selection(
  541            DisplayPoint::new(DisplayRow(3), 3),
  542            0,
  543            gpui::Point::<f32>::default(),
  544            window,
  545            cx,
  546        );
  547        assert_eq!(
  548            editor.selections.display_ranges(cx),
  549            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  550        );
  551    });
  552
  553    _ = editor.update(cx, |editor, window, cx| {
  554        editor.cancel(&Cancel, window, cx);
  555        editor.update_selection(
  556            DisplayPoint::new(DisplayRow(1), 1),
  557            0,
  558            gpui::Point::<f32>::default(),
  559            window,
  560            cx,
  561        );
  562        assert_eq!(
  563            editor.selections.display_ranges(cx),
  564            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  565        );
  566    });
  567}
  568
  569#[gpui::test]
  570fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  571    init_test(cx, |_| {});
  572
  573    let editor = cx.add_window(|window, cx| {
  574        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  575        build_editor(buffer, window, cx)
  576    });
  577
  578    _ = editor.update(cx, |editor, window, cx| {
  579        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  580        assert_eq!(
  581            editor.selections.display_ranges(cx),
  582            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  583        );
  584
  585        editor.move_down(&Default::default(), window, cx);
  586        assert_eq!(
  587            editor.selections.display_ranges(cx),
  588            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  589        );
  590
  591        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  592        assert_eq!(
  593            editor.selections.display_ranges(cx),
  594            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  595        );
  596
  597        editor.move_up(&Default::default(), window, cx);
  598        assert_eq!(
  599            editor.selections.display_ranges(cx),
  600            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  601        );
  602    });
  603}
  604
  605#[gpui::test]
  606fn test_clone(cx: &mut TestAppContext) {
  607    init_test(cx, |_| {});
  608
  609    let (text, selection_ranges) = marked_text_ranges(
  610        indoc! {"
  611            one
  612            two
  613            threeˇ
  614            four
  615            fiveˇ
  616        "},
  617        true,
  618    );
  619
  620    let editor = cx.add_window(|window, cx| {
  621        let buffer = MultiBuffer::build_simple(&text, cx);
  622        build_editor(buffer, window, cx)
  623    });
  624
  625    _ = editor.update(cx, |editor, window, cx| {
  626        editor.change_selections(None, window, cx, |s| {
  627            s.select_ranges(selection_ranges.clone())
  628        });
  629        editor.fold_creases(
  630            vec![
  631                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  632                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  633            ],
  634            true,
  635            window,
  636            cx,
  637        );
  638    });
  639
  640    let cloned_editor = editor
  641        .update(cx, |editor, _, cx| {
  642            cx.open_window(Default::default(), |window, cx| {
  643                cx.new(|cx| editor.clone(window, cx))
  644            })
  645        })
  646        .unwrap()
  647        .unwrap();
  648
  649    let snapshot = editor
  650        .update(cx, |e, window, cx| e.snapshot(window, cx))
  651        .unwrap();
  652    let cloned_snapshot = cloned_editor
  653        .update(cx, |e, window, cx| e.snapshot(window, cx))
  654        .unwrap();
  655
  656    assert_eq!(
  657        cloned_editor
  658            .update(cx, |e, _, cx| e.display_text(cx))
  659            .unwrap(),
  660        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  661    );
  662    assert_eq!(
  663        cloned_snapshot
  664            .folds_in_range(0..text.len())
  665            .collect::<Vec<_>>(),
  666        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  667    );
  668    assert_set_eq!(
  669        cloned_editor
  670            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  671            .unwrap(),
  672        editor
  673            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  674            .unwrap()
  675    );
  676    assert_set_eq!(
  677        cloned_editor
  678            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  679            .unwrap(),
  680        editor
  681            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  682            .unwrap()
  683    );
  684}
  685
  686#[gpui::test]
  687async fn test_navigation_history(cx: &mut TestAppContext) {
  688    init_test(cx, |_| {});
  689
  690    use workspace::item::Item;
  691
  692    let fs = FakeFs::new(cx.executor());
  693    let project = Project::test(fs, [], cx).await;
  694    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  695    let pane = workspace
  696        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  697        .unwrap();
  698
  699    _ = workspace.update(cx, |_v, window, cx| {
  700        cx.new(|cx| {
  701            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  702            let mut editor = build_editor(buffer.clone(), window, cx);
  703            let handle = cx.entity();
  704            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  705
  706            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  707                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  708            }
  709
  710            // Move the cursor a small distance.
  711            // Nothing is added to the navigation history.
  712            editor.change_selections(None, window, cx, |s| {
  713                s.select_display_ranges([
  714                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  715                ])
  716            });
  717            editor.change_selections(None, window, cx, |s| {
  718                s.select_display_ranges([
  719                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  720                ])
  721            });
  722            assert!(pop_history(&mut editor, cx).is_none());
  723
  724            // Move the cursor a large distance.
  725            // The history can jump back to the previous position.
  726            editor.change_selections(None, window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  729                ])
  730            });
  731            let nav_entry = pop_history(&mut editor, cx).unwrap();
  732            editor.navigate(nav_entry.data.unwrap(), window, cx);
  733            assert_eq!(nav_entry.item.id(), cx.entity_id());
  734            assert_eq!(
  735                editor.selections.display_ranges(cx),
  736                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  737            );
  738            assert!(pop_history(&mut editor, cx).is_none());
  739
  740            // Move the cursor a small distance via the mouse.
  741            // Nothing is added to the navigation history.
  742            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  743            editor.end_selection(window, cx);
  744            assert_eq!(
  745                editor.selections.display_ranges(cx),
  746                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  747            );
  748            assert!(pop_history(&mut editor, cx).is_none());
  749
  750            // Move the cursor a large distance via the mouse.
  751            // The history can jump back to the previous position.
  752            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  753            editor.end_selection(window, cx);
  754            assert_eq!(
  755                editor.selections.display_ranges(cx),
  756                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  757            );
  758            let nav_entry = pop_history(&mut editor, cx).unwrap();
  759            editor.navigate(nav_entry.data.unwrap(), window, cx);
  760            assert_eq!(nav_entry.item.id(), cx.entity_id());
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Set scroll position to check later
  768            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  769            let original_scroll_position = editor.scroll_manager.anchor();
  770
  771            // Jump to the end of the document and adjust scroll
  772            editor.move_to_end(&MoveToEnd, window, cx);
  773            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  774            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  775
  776            let nav_entry = pop_history(&mut editor, cx).unwrap();
  777            editor.navigate(nav_entry.data.unwrap(), window, cx);
  778            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  779
  780            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  781            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  782            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  783            let invalid_point = Point::new(9999, 0);
  784            editor.navigate(
  785                Box::new(NavigationData {
  786                    cursor_anchor: invalid_anchor,
  787                    cursor_position: invalid_point,
  788                    scroll_anchor: ScrollAnchor {
  789                        anchor: invalid_anchor,
  790                        offset: Default::default(),
  791                    },
  792                    scroll_top_row: invalid_point.row,
  793                }),
  794                window,
  795                cx,
  796            );
  797            assert_eq!(
  798                editor.selections.display_ranges(cx),
  799                &[editor.max_point(cx)..editor.max_point(cx)]
  800            );
  801            assert_eq!(
  802                editor.scroll_position(cx),
  803                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  804            );
  805
  806            editor
  807        })
  808    });
  809}
  810
  811#[gpui::test]
  812fn test_cancel(cx: &mut TestAppContext) {
  813    init_test(cx, |_| {});
  814
  815    let editor = cx.add_window(|window, cx| {
  816        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  817        build_editor(buffer, window, cx)
  818    });
  819
  820    _ = editor.update(cx, |editor, window, cx| {
  821        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  822        editor.update_selection(
  823            DisplayPoint::new(DisplayRow(1), 1),
  824            0,
  825            gpui::Point::<f32>::default(),
  826            window,
  827            cx,
  828        );
  829        editor.end_selection(window, cx);
  830
  831        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  832        editor.update_selection(
  833            DisplayPoint::new(DisplayRow(0), 3),
  834            0,
  835            gpui::Point::<f32>::default(),
  836            window,
  837            cx,
  838        );
  839        editor.end_selection(window, cx);
  840        assert_eq!(
  841            editor.selections.display_ranges(cx),
  842            [
  843                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  844                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  845            ]
  846        );
  847    });
  848
  849    _ = editor.update(cx, |editor, window, cx| {
  850        editor.cancel(&Cancel, window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  854        );
  855    });
  856
  857    _ = editor.update(cx, |editor, window, cx| {
  858        editor.cancel(&Cancel, window, cx);
  859        assert_eq!(
  860            editor.selections.display_ranges(cx),
  861            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  862        );
  863    });
  864}
  865
  866#[gpui::test]
  867fn test_fold_action(cx: &mut TestAppContext) {
  868    init_test(cx, |_| {});
  869
  870    let editor = cx.add_window(|window, cx| {
  871        let buffer = MultiBuffer::build_simple(
  872            &"
  873                impl Foo {
  874                    // Hello!
  875
  876                    fn a() {
  877                        1
  878                    }
  879
  880                    fn b() {
  881                        2
  882                    }
  883
  884                    fn c() {
  885                        3
  886                    }
  887                }
  888            "
  889            .unindent(),
  890            cx,
  891        );
  892        build_editor(buffer.clone(), window, cx)
  893    });
  894
  895    _ = editor.update(cx, |editor, window, cx| {
  896        editor.change_selections(None, window, cx, |s| {
  897            s.select_display_ranges([
  898                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  899            ]);
  900        });
  901        editor.fold(&Fold, window, cx);
  902        assert_eq!(
  903            editor.display_text(cx),
  904            "
  905                impl Foo {
  906                    // Hello!
  907
  908                    fn a() {
  909                        1
  910                    }
  911
  912                    fn b() {⋯
  913                    }
  914
  915                    fn c() {⋯
  916                    }
  917                }
  918            "
  919            .unindent(),
  920        );
  921
  922        editor.fold(&Fold, window, cx);
  923        assert_eq!(
  924            editor.display_text(cx),
  925            "
  926                impl Foo {⋯
  927                }
  928            "
  929            .unindent(),
  930        );
  931
  932        editor.unfold_lines(&UnfoldLines, window, cx);
  933        assert_eq!(
  934            editor.display_text(cx),
  935            "
  936                impl Foo {
  937                    // Hello!
  938
  939                    fn a() {
  940                        1
  941                    }
  942
  943                    fn b() {⋯
  944                    }
  945
  946                    fn c() {⋯
  947                    }
  948                }
  949            "
  950            .unindent(),
  951        );
  952
  953        editor.unfold_lines(&UnfoldLines, window, cx);
  954        assert_eq!(
  955            editor.display_text(cx),
  956            editor.buffer.read(cx).read(cx).text()
  957        );
  958    });
  959}
  960
  961#[gpui::test]
  962fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  963    init_test(cx, |_| {});
  964
  965    let editor = cx.add_window(|window, cx| {
  966        let buffer = MultiBuffer::build_simple(
  967            &"
  968                class Foo:
  969                    # Hello!
  970
  971                    def a():
  972                        print(1)
  973
  974                    def b():
  975                        print(2)
  976
  977                    def c():
  978                        print(3)
  979            "
  980            .unindent(),
  981            cx,
  982        );
  983        build_editor(buffer.clone(), window, cx)
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.change_selections(None, window, cx, |s| {
  988            s.select_display_ranges([
  989                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  990            ]);
  991        });
  992        editor.fold(&Fold, window, cx);
  993        assert_eq!(
  994            editor.display_text(cx),
  995            "
  996                class Foo:
  997                    # Hello!
  998
  999                    def a():
 1000                        print(1)
 1001
 1002                    def b():⋯
 1003
 1004                    def c():⋯
 1005            "
 1006            .unindent(),
 1007        );
 1008
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.unfold_lines(&UnfoldLines, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:
 1023                    # Hello!
 1024
 1025                    def a():
 1026                        print(1)
 1027
 1028                    def b():⋯
 1029
 1030                    def c():⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            editor.buffer.read(cx).read(cx).text()
 1039        );
 1040    });
 1041}
 1042
 1043#[gpui::test]
 1044fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1045    init_test(cx, |_| {});
 1046
 1047    let editor = cx.add_window(|window, cx| {
 1048        let buffer = MultiBuffer::build_simple(
 1049            &"
 1050                class Foo:
 1051                    # Hello!
 1052
 1053                    def a():
 1054                        print(1)
 1055
 1056                    def b():
 1057                        print(2)
 1058
 1059
 1060                    def c():
 1061                        print(3)
 1062
 1063
 1064            "
 1065            .unindent(),
 1066            cx,
 1067        );
 1068        build_editor(buffer.clone(), window, cx)
 1069    });
 1070
 1071    _ = editor.update(cx, |editor, window, cx| {
 1072        editor.change_selections(None, window, cx, |s| {
 1073            s.select_display_ranges([
 1074                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1075            ]);
 1076        });
 1077        editor.fold(&Fold, window, cx);
 1078        assert_eq!(
 1079            editor.display_text(cx),
 1080            "
 1081                class Foo:
 1082                    # Hello!
 1083
 1084                    def a():
 1085                        print(1)
 1086
 1087                    def b():⋯
 1088
 1089
 1090                    def c():⋯
 1091
 1092
 1093            "
 1094            .unindent(),
 1095        );
 1096
 1097        editor.fold(&Fold, window, cx);
 1098        assert_eq!(
 1099            editor.display_text(cx),
 1100            "
 1101                class Foo:⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.unfold_lines(&UnfoldLines, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:
 1113                    # Hello!
 1114
 1115                    def a():
 1116                        print(1)
 1117
 1118                    def b():⋯
 1119
 1120
 1121                    def c():⋯
 1122
 1123
 1124            "
 1125            .unindent(),
 1126        );
 1127
 1128        editor.unfold_lines(&UnfoldLines, window, cx);
 1129        assert_eq!(
 1130            editor.display_text(cx),
 1131            editor.buffer.read(cx).read(cx).text()
 1132        );
 1133    });
 1134}
 1135
 1136#[gpui::test]
 1137fn test_fold_at_level(cx: &mut TestAppContext) {
 1138    init_test(cx, |_| {});
 1139
 1140    let editor = cx.add_window(|window, cx| {
 1141        let buffer = MultiBuffer::build_simple(
 1142            &"
 1143                class Foo:
 1144                    # Hello!
 1145
 1146                    def a():
 1147                        print(1)
 1148
 1149                    def b():
 1150                        print(2)
 1151
 1152
 1153                class Bar:
 1154                    # World!
 1155
 1156                    def a():
 1157                        print(1)
 1158
 1159                    def b():
 1160                        print(2)
 1161
 1162
 1163            "
 1164            .unindent(),
 1165            cx,
 1166        );
 1167        build_editor(buffer.clone(), window, cx)
 1168    });
 1169
 1170    _ = editor.update(cx, |editor, window, cx| {
 1171        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1172        assert_eq!(
 1173            editor.display_text(cx),
 1174            "
 1175                class Foo:
 1176                    # Hello!
 1177
 1178                    def a():⋯
 1179
 1180                    def b():⋯
 1181
 1182
 1183                class Bar:
 1184                    # World!
 1185
 1186                    def a():⋯
 1187
 1188                    def b():⋯
 1189
 1190
 1191            "
 1192            .unindent(),
 1193        );
 1194
 1195        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1196        assert_eq!(
 1197            editor.display_text(cx),
 1198            "
 1199                class Foo:⋯
 1200
 1201
 1202                class Bar:⋯
 1203
 1204
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_all(&UnfoldAll, window, cx);
 1210        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1211        assert_eq!(
 1212            editor.display_text(cx),
 1213            "
 1214                class Foo:
 1215                    # Hello!
 1216
 1217                    def a():
 1218                        print(1)
 1219
 1220                    def b():
 1221                        print(2)
 1222
 1223
 1224                class Bar:
 1225                    # World!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234            "
 1235            .unindent(),
 1236        );
 1237
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            editor.buffer.read(cx).read(cx).text()
 1241        );
 1242    });
 1243}
 1244
 1245#[gpui::test]
 1246fn test_move_cursor(cx: &mut TestAppContext) {
 1247    init_test(cx, |_| {});
 1248
 1249    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1250    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1251
 1252    buffer.update(cx, |buffer, cx| {
 1253        buffer.edit(
 1254            vec![
 1255                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1256                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1257            ],
 1258            None,
 1259            cx,
 1260        );
 1261    });
 1262    _ = editor.update(cx, |editor, window, cx| {
 1263        assert_eq!(
 1264            editor.selections.display_ranges(cx),
 1265            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1266        );
 1267
 1268        editor.move_down(&MoveDown, window, cx);
 1269        assert_eq!(
 1270            editor.selections.display_ranges(cx),
 1271            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1272        );
 1273
 1274        editor.move_right(&MoveRight, window, cx);
 1275        assert_eq!(
 1276            editor.selections.display_ranges(cx),
 1277            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1278        );
 1279
 1280        editor.move_left(&MoveLeft, window, cx);
 1281        assert_eq!(
 1282            editor.selections.display_ranges(cx),
 1283            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1284        );
 1285
 1286        editor.move_up(&MoveUp, window, cx);
 1287        assert_eq!(
 1288            editor.selections.display_ranges(cx),
 1289            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1290        );
 1291
 1292        editor.move_to_end(&MoveToEnd, window, cx);
 1293        assert_eq!(
 1294            editor.selections.display_ranges(cx),
 1295            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1296        );
 1297
 1298        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1299        assert_eq!(
 1300            editor.selections.display_ranges(cx),
 1301            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1302        );
 1303
 1304        editor.change_selections(None, window, cx, |s| {
 1305            s.select_display_ranges([
 1306                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1307            ]);
 1308        });
 1309        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.select_to_end(&SelectToEnd, window, cx);
 1316        assert_eq!(
 1317            editor.selections.display_ranges(cx),
 1318            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1319        );
 1320    });
 1321}
 1322
 1323#[gpui::test]
 1324fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1325    init_test(cx, |_| {});
 1326
 1327    let editor = cx.add_window(|window, cx| {
 1328        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1329        build_editor(buffer.clone(), window, cx)
 1330    });
 1331
 1332    assert_eq!('🟥'.len_utf8(), 4);
 1333    assert_eq!('α'.len_utf8(), 2);
 1334
 1335    _ = editor.update(cx, |editor, window, cx| {
 1336        editor.fold_creases(
 1337            vec![
 1338                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1339                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1340                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1341            ],
 1342            true,
 1343            window,
 1344            cx,
 1345        );
 1346        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1347
 1348        editor.move_right(&MoveRight, window, cx);
 1349        assert_eq!(
 1350            editor.selections.display_ranges(cx),
 1351            &[empty_range(0, "🟥".len())]
 1352        );
 1353        editor.move_right(&MoveRight, window, cx);
 1354        assert_eq!(
 1355            editor.selections.display_ranges(cx),
 1356            &[empty_range(0, "🟥🟧".len())]
 1357        );
 1358        editor.move_right(&MoveRight, window, cx);
 1359        assert_eq!(
 1360            editor.selections.display_ranges(cx),
 1361            &[empty_range(0, "🟥🟧⋯".len())]
 1362        );
 1363
 1364        editor.move_down(&MoveDown, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(1, "ab⋯e".len())]
 1368        );
 1369        editor.move_left(&MoveLeft, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(1, "ab⋯".len())]
 1373        );
 1374        editor.move_left(&MoveLeft, window, cx);
 1375        assert_eq!(
 1376            editor.selections.display_ranges(cx),
 1377            &[empty_range(1, "ab".len())]
 1378        );
 1379        editor.move_left(&MoveLeft, window, cx);
 1380        assert_eq!(
 1381            editor.selections.display_ranges(cx),
 1382            &[empty_range(1, "a".len())]
 1383        );
 1384
 1385        editor.move_down(&MoveDown, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(2, "α".len())]
 1389        );
 1390        editor.move_right(&MoveRight, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(2, "αβ".len())]
 1394        );
 1395        editor.move_right(&MoveRight, window, cx);
 1396        assert_eq!(
 1397            editor.selections.display_ranges(cx),
 1398            &[empty_range(2, "αβ⋯".len())]
 1399        );
 1400        editor.move_right(&MoveRight, window, cx);
 1401        assert_eq!(
 1402            editor.selections.display_ranges(cx),
 1403            &[empty_range(2, "αβ⋯ε".len())]
 1404        );
 1405
 1406        editor.move_up(&MoveUp, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(1, "ab⋯e".len())]
 1410        );
 1411        editor.move_down(&MoveDown, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416        editor.move_up(&MoveUp, window, cx);
 1417        assert_eq!(
 1418            editor.selections.display_ranges(cx),
 1419            &[empty_range(1, "ab⋯e".len())]
 1420        );
 1421
 1422        editor.move_up(&MoveUp, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(0, "🟥🟧".len())]
 1426        );
 1427        editor.move_left(&MoveLeft, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(0, "🟥".len())]
 1431        );
 1432        editor.move_left(&MoveLeft, window, cx);
 1433        assert_eq!(
 1434            editor.selections.display_ranges(cx),
 1435            &[empty_range(0, "".len())]
 1436        );
 1437    });
 1438}
 1439
 1440#[gpui::test]
 1441fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1442    init_test(cx, |_| {});
 1443
 1444    let editor = cx.add_window(|window, cx| {
 1445        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1446        build_editor(buffer.clone(), window, cx)
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        editor.change_selections(None, window, cx, |s| {
 1450            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1451        });
 1452
 1453        // moving above start of document should move selection to start of document,
 1454        // but the next move down should still be at the original goal_x
 1455        editor.move_up(&MoveUp, window, cx);
 1456        assert_eq!(
 1457            editor.selections.display_ranges(cx),
 1458            &[empty_range(0, "".len())]
 1459        );
 1460
 1461        editor.move_down(&MoveDown, window, cx);
 1462        assert_eq!(
 1463            editor.selections.display_ranges(cx),
 1464            &[empty_range(1, "abcd".len())]
 1465        );
 1466
 1467        editor.move_down(&MoveDown, window, cx);
 1468        assert_eq!(
 1469            editor.selections.display_ranges(cx),
 1470            &[empty_range(2, "αβγ".len())]
 1471        );
 1472
 1473        editor.move_down(&MoveDown, window, cx);
 1474        assert_eq!(
 1475            editor.selections.display_ranges(cx),
 1476            &[empty_range(3, "abcd".len())]
 1477        );
 1478
 1479        editor.move_down(&MoveDown, window, cx);
 1480        assert_eq!(
 1481            editor.selections.display_ranges(cx),
 1482            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1483        );
 1484
 1485        // moving past end of document should not change goal_x
 1486        editor.move_down(&MoveDown, window, cx);
 1487        assert_eq!(
 1488            editor.selections.display_ranges(cx),
 1489            &[empty_range(5, "".len())]
 1490        );
 1491
 1492        editor.move_down(&MoveDown, window, cx);
 1493        assert_eq!(
 1494            editor.selections.display_ranges(cx),
 1495            &[empty_range(5, "".len())]
 1496        );
 1497
 1498        editor.move_up(&MoveUp, window, cx);
 1499        assert_eq!(
 1500            editor.selections.display_ranges(cx),
 1501            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1502        );
 1503
 1504        editor.move_up(&MoveUp, window, cx);
 1505        assert_eq!(
 1506            editor.selections.display_ranges(cx),
 1507            &[empty_range(3, "abcd".len())]
 1508        );
 1509
 1510        editor.move_up(&MoveUp, window, cx);
 1511        assert_eq!(
 1512            editor.selections.display_ranges(cx),
 1513            &[empty_range(2, "αβγ".len())]
 1514        );
 1515    });
 1516}
 1517
 1518#[gpui::test]
 1519fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1520    init_test(cx, |_| {});
 1521    let move_to_beg = MoveToBeginningOfLine {
 1522        stop_at_soft_wraps: true,
 1523        stop_at_indent: true,
 1524    };
 1525
 1526    let delete_to_beg = DeleteToBeginningOfLine {
 1527        stop_at_indent: false,
 1528    };
 1529
 1530    let move_to_end = MoveToEndOfLine {
 1531        stop_at_soft_wraps: true,
 1532    };
 1533
 1534    let editor = cx.add_window(|window, cx| {
 1535        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1536        build_editor(buffer, window, cx)
 1537    });
 1538    _ = editor.update(cx, |editor, window, cx| {
 1539        editor.change_selections(None, window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1542                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1543            ]);
 1544        });
 1545    });
 1546
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1549        assert_eq!(
 1550            editor.selections.display_ranges(cx),
 1551            &[
 1552                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1553                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1554            ]
 1555        );
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_end_of_line(&move_to_end, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1586                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1587            ]
 1588        );
 1589    });
 1590
 1591    // Moving to the end of line again is a no-op.
 1592    _ = editor.update(cx, |editor, window, cx| {
 1593        editor.move_to_end_of_line(&move_to_end, window, cx);
 1594        assert_eq!(
 1595            editor.selections.display_ranges(cx),
 1596            &[
 1597                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1598                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1599            ]
 1600        );
 1601    });
 1602
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_left(&MoveLeft, window, cx);
 1605        editor.select_to_beginning_of_line(
 1606            &SelectToBeginningOfLine {
 1607                stop_at_soft_wraps: true,
 1608                stop_at_indent: true,
 1609            },
 1610            window,
 1611            cx,
 1612        );
 1613        assert_eq!(
 1614            editor.selections.display_ranges(cx),
 1615            &[
 1616                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1617                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1618            ]
 1619        );
 1620    });
 1621
 1622    _ = editor.update(cx, |editor, window, cx| {
 1623        editor.select_to_beginning_of_line(
 1624            &SelectToBeginningOfLine {
 1625                stop_at_soft_wraps: true,
 1626                stop_at_indent: true,
 1627            },
 1628            window,
 1629            cx,
 1630        );
 1631        assert_eq!(
 1632            editor.selections.display_ranges(cx),
 1633            &[
 1634                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1635                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1636            ]
 1637        );
 1638    });
 1639
 1640    _ = editor.update(cx, |editor, window, cx| {
 1641        editor.select_to_beginning_of_line(
 1642            &SelectToBeginningOfLine {
 1643                stop_at_soft_wraps: true,
 1644                stop_at_indent: true,
 1645            },
 1646            window,
 1647            cx,
 1648        );
 1649        assert_eq!(
 1650            editor.selections.display_ranges(cx),
 1651            &[
 1652                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1653                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1654            ]
 1655        );
 1656    });
 1657
 1658    _ = editor.update(cx, |editor, window, cx| {
 1659        editor.select_to_end_of_line(
 1660            &SelectToEndOfLine {
 1661                stop_at_soft_wraps: true,
 1662            },
 1663            window,
 1664            cx,
 1665        );
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[
 1669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1670                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1671            ]
 1672        );
 1673    });
 1674
 1675    _ = editor.update(cx, |editor, window, cx| {
 1676        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1677        assert_eq!(editor.display_text(cx), "ab\n  de");
 1678        assert_eq!(
 1679            editor.selections.display_ranges(cx),
 1680            &[
 1681                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1682                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1683            ]
 1684        );
 1685    });
 1686
 1687    _ = editor.update(cx, |editor, window, cx| {
 1688        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1689        assert_eq!(editor.display_text(cx), "\n");
 1690        assert_eq!(
 1691            editor.selections.display_ranges(cx),
 1692            &[
 1693                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1694                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1695            ]
 1696        );
 1697    });
 1698}
 1699
 1700#[gpui::test]
 1701fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1702    init_test(cx, |_| {});
 1703    let move_to_beg = MoveToBeginningOfLine {
 1704        stop_at_soft_wraps: false,
 1705        stop_at_indent: false,
 1706    };
 1707
 1708    let move_to_end = MoveToEndOfLine {
 1709        stop_at_soft_wraps: false,
 1710    };
 1711
 1712    let editor = cx.add_window(|window, cx| {
 1713        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1714        build_editor(buffer, window, cx)
 1715    });
 1716
 1717    _ = editor.update(cx, |editor, window, cx| {
 1718        editor.set_wrap_width(Some(140.0.into()), cx);
 1719
 1720        // We expect the following lines after wrapping
 1721        // ```
 1722        // thequickbrownfox
 1723        // jumpedoverthelazydo
 1724        // gs
 1725        // ```
 1726        // The final `gs` was soft-wrapped onto a new line.
 1727        assert_eq!(
 1728            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1729            editor.display_text(cx),
 1730        );
 1731
 1732        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1733        // Start the cursor at the `k` on the first line
 1734        editor.change_selections(None, window, cx, |s| {
 1735            s.select_display_ranges([
 1736                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1737            ]);
 1738        });
 1739
 1740        // Moving to the beginning of the line should put us at the beginning of the line.
 1741        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1742        assert_eq!(
 1743            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1744            editor.selections.display_ranges(cx)
 1745        );
 1746
 1747        // Moving to the end of the line should put us at the end of the line.
 1748        editor.move_to_end_of_line(&move_to_end, window, cx);
 1749        assert_eq!(
 1750            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1751            editor.selections.display_ranges(cx)
 1752        );
 1753
 1754        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1755        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1756        editor.change_selections(None, window, cx, |s| {
 1757            s.select_display_ranges([
 1758                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1759            ]);
 1760        });
 1761
 1762        // Moving to the beginning of the line should put us at the start of the second line of
 1763        // display text, i.e., the `j`.
 1764        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1765        assert_eq!(
 1766            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1767            editor.selections.display_ranges(cx)
 1768        );
 1769
 1770        // Moving to the beginning of the line again should be a no-op.
 1771        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1772        assert_eq!(
 1773            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1774            editor.selections.display_ranges(cx)
 1775        );
 1776
 1777        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1778        // next display line.
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1782            editor.selections.display_ranges(cx)
 1783        );
 1784
 1785        // Moving to the end of the line again should be a no-op.
 1786        editor.move_to_end_of_line(&move_to_end, window, cx);
 1787        assert_eq!(
 1788            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1789            editor.selections.display_ranges(cx)
 1790        );
 1791    });
 1792}
 1793
 1794#[gpui::test]
 1795fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1796    init_test(cx, |_| {});
 1797
 1798    let move_to_beg = MoveToBeginningOfLine {
 1799        stop_at_soft_wraps: true,
 1800        stop_at_indent: true,
 1801    };
 1802
 1803    let select_to_beg = SelectToBeginningOfLine {
 1804        stop_at_soft_wraps: true,
 1805        stop_at_indent: true,
 1806    };
 1807
 1808    let delete_to_beg = DeleteToBeginningOfLine {
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let move_to_end = MoveToEndOfLine {
 1813        stop_at_soft_wraps: false,
 1814    };
 1815
 1816    let editor = cx.add_window(|window, cx| {
 1817        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1818        build_editor(buffer, window, cx)
 1819    });
 1820
 1821    _ = editor.update(cx, |editor, window, cx| {
 1822        editor.change_selections(None, window, cx, |s| {
 1823            s.select_display_ranges([
 1824                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1825                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1826            ]);
 1827        });
 1828
 1829        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1830        // and the second cursor at the first non-whitespace character in the line.
 1831        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1832        assert_eq!(
 1833            editor.selections.display_ranges(cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1836                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1837            ]
 1838        );
 1839
 1840        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1841        // and should move the second cursor to the beginning of the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1852        // and should move the second cursor back to the first non-whitespace character in the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1859            ]
 1860        );
 1861
 1862        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1863        // and to the first non-whitespace character in the line for the second cursor.
 1864        editor.move_to_end_of_line(&move_to_end, window, cx);
 1865        editor.move_left(&MoveLeft, window, cx);
 1866        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1867        assert_eq!(
 1868            editor.selections.display_ranges(cx),
 1869            &[
 1870                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1871                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1872            ]
 1873        );
 1874
 1875        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1876        // and should select to the beginning of the line for the second cursor.
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1883            ]
 1884        );
 1885
 1886        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1887        // and should delete to the first non-whitespace character in the line for the second cursor.
 1888        editor.move_to_end_of_line(&move_to_end, window, cx);
 1889        editor.move_left(&MoveLeft, window, cx);
 1890        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1891        assert_eq!(editor.text(cx), "c\n  f");
 1892    });
 1893}
 1894
 1895#[gpui::test]
 1896fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1897    init_test(cx, |_| {});
 1898
 1899    let editor = cx.add_window(|window, cx| {
 1900        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1901        build_editor(buffer, window, cx)
 1902    });
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.change_selections(None, window, cx, |s| {
 1905            s.select_display_ranges([
 1906                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1907                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1908            ])
 1909        });
 1910        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1911        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1912
 1913        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1914        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1915
 1916        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1917        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1918
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1926        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1929        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1932        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_right(&MoveRight, window, cx);
 1935        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1936        assert_selection_ranges(
 1937            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1938            editor,
 1939            cx,
 1940        );
 1941
 1942        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1943        assert_selection_ranges(
 1944            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1945            editor,
 1946            cx,
 1947        );
 1948
 1949        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1950        assert_selection_ranges(
 1951            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1952            editor,
 1953            cx,
 1954        );
 1955    });
 1956}
 1957
 1958#[gpui::test]
 1959fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1960    init_test(cx, |_| {});
 1961
 1962    let editor = cx.add_window(|window, cx| {
 1963        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1964        build_editor(buffer, window, cx)
 1965    });
 1966
 1967    _ = editor.update(cx, |editor, window, cx| {
 1968        editor.set_wrap_width(Some(140.0.into()), cx);
 1969        assert_eq!(
 1970            editor.display_text(cx),
 1971            "use one::{\n    two::three::\n    four::five\n};"
 1972        );
 1973
 1974        editor.change_selections(None, window, cx, |s| {
 1975            s.select_display_ranges([
 1976                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1977            ]);
 1978        });
 1979
 1980        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1981        assert_eq!(
 1982            editor.selections.display_ranges(cx),
 1983            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1984        );
 1985
 1986        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1987        assert_eq!(
 1988            editor.selections.display_ranges(cx),
 1989            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1990        );
 1991
 1992        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1996        );
 1997
 1998        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1999        assert_eq!(
 2000            editor.selections.display_ranges(cx),
 2001            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2002        );
 2003
 2004        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2005        assert_eq!(
 2006            editor.selections.display_ranges(cx),
 2007            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2008        );
 2009
 2010        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2011        assert_eq!(
 2012            editor.selections.display_ranges(cx),
 2013            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2014        );
 2015    });
 2016}
 2017
 2018#[gpui::test]
 2019async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2020    init_test(cx, |_| {});
 2021    let mut cx = EditorTestContext::new(cx).await;
 2022
 2023    let line_height = cx.editor(|editor, window, _| {
 2024        editor
 2025            .style()
 2026            .unwrap()
 2027            .text
 2028            .line_height_in_pixels(window.rem_size())
 2029    });
 2030    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2031
 2032    cx.set_state(
 2033        &r#"ˇone
 2034        two
 2035
 2036        three
 2037        fourˇ
 2038        five
 2039
 2040        six"#
 2041            .unindent(),
 2042    );
 2043
 2044    cx.update_editor(|editor, window, cx| {
 2045        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2046    });
 2047    cx.assert_editor_state(
 2048        &r#"one
 2049        two
 2050        ˇ
 2051        three
 2052        four
 2053        five
 2054        ˇ
 2055        six"#
 2056            .unindent(),
 2057    );
 2058
 2059    cx.update_editor(|editor, window, cx| {
 2060        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2061    });
 2062    cx.assert_editor_state(
 2063        &r#"one
 2064        two
 2065
 2066        three
 2067        four
 2068        five
 2069        ˇ
 2070        sixˇ"#
 2071            .unindent(),
 2072    );
 2073
 2074    cx.update_editor(|editor, window, cx| {
 2075        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2076    });
 2077    cx.assert_editor_state(
 2078        &r#"one
 2079        two
 2080
 2081        three
 2082        four
 2083        five
 2084
 2085        sixˇ"#
 2086            .unindent(),
 2087    );
 2088
 2089    cx.update_editor(|editor, window, cx| {
 2090        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2091    });
 2092    cx.assert_editor_state(
 2093        &r#"one
 2094        two
 2095
 2096        three
 2097        four
 2098        five
 2099        ˇ
 2100        six"#
 2101            .unindent(),
 2102    );
 2103
 2104    cx.update_editor(|editor, window, cx| {
 2105        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2106    });
 2107    cx.assert_editor_state(
 2108        &r#"one
 2109        two
 2110        ˇ
 2111        three
 2112        four
 2113        five
 2114
 2115        six"#
 2116            .unindent(),
 2117    );
 2118
 2119    cx.update_editor(|editor, window, cx| {
 2120        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2121    });
 2122    cx.assert_editor_state(
 2123        &r#"ˇone
 2124        two
 2125
 2126        three
 2127        four
 2128        five
 2129
 2130        six"#
 2131            .unindent(),
 2132    );
 2133}
 2134
 2135#[gpui::test]
 2136async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2137    init_test(cx, |_| {});
 2138    let mut cx = EditorTestContext::new(cx).await;
 2139    let line_height = cx.editor(|editor, window, _| {
 2140        editor
 2141            .style()
 2142            .unwrap()
 2143            .text
 2144            .line_height_in_pixels(window.rem_size())
 2145    });
 2146    let window = cx.window;
 2147    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2148
 2149    cx.set_state(
 2150        r#"ˇone
 2151        two
 2152        three
 2153        four
 2154        five
 2155        six
 2156        seven
 2157        eight
 2158        nine
 2159        ten
 2160        "#,
 2161    );
 2162
 2163    cx.update_editor(|editor, window, cx| {
 2164        assert_eq!(
 2165            editor.snapshot(window, cx).scroll_position(),
 2166            gpui::Point::new(0., 0.)
 2167        );
 2168        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2169        assert_eq!(
 2170            editor.snapshot(window, cx).scroll_position(),
 2171            gpui::Point::new(0., 3.)
 2172        );
 2173        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2174        assert_eq!(
 2175            editor.snapshot(window, cx).scroll_position(),
 2176            gpui::Point::new(0., 6.)
 2177        );
 2178        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2179        assert_eq!(
 2180            editor.snapshot(window, cx).scroll_position(),
 2181            gpui::Point::new(0., 3.)
 2182        );
 2183
 2184        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2185        assert_eq!(
 2186            editor.snapshot(window, cx).scroll_position(),
 2187            gpui::Point::new(0., 1.)
 2188        );
 2189        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2190        assert_eq!(
 2191            editor.snapshot(window, cx).scroll_position(),
 2192            gpui::Point::new(0., 3.)
 2193        );
 2194    });
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_autoscroll(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201
 2202    let line_height = cx.update_editor(|editor, window, cx| {
 2203        editor.set_vertical_scroll_margin(2, cx);
 2204        editor
 2205            .style()
 2206            .unwrap()
 2207            .text
 2208            .line_height_in_pixels(window.rem_size())
 2209    });
 2210    let window = cx.window;
 2211    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2212
 2213    cx.set_state(
 2214        r#"ˇone
 2215            two
 2216            three
 2217            four
 2218            five
 2219            six
 2220            seven
 2221            eight
 2222            nine
 2223            ten
 2224        "#,
 2225    );
 2226    cx.update_editor(|editor, window, cx| {
 2227        assert_eq!(
 2228            editor.snapshot(window, cx).scroll_position(),
 2229            gpui::Point::new(0., 0.0)
 2230        );
 2231    });
 2232
 2233    // Add a cursor below the visible area. Since both cursors cannot fit
 2234    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2235    // allows the vertical scroll margin below that cursor.
 2236    cx.update_editor(|editor, window, cx| {
 2237        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2238            selections.select_ranges([
 2239                Point::new(0, 0)..Point::new(0, 0),
 2240                Point::new(6, 0)..Point::new(6, 0),
 2241            ]);
 2242        })
 2243    });
 2244    cx.update_editor(|editor, window, cx| {
 2245        assert_eq!(
 2246            editor.snapshot(window, cx).scroll_position(),
 2247            gpui::Point::new(0., 3.0)
 2248        );
 2249    });
 2250
 2251    // Move down. The editor cursor scrolls down to track the newest cursor.
 2252    cx.update_editor(|editor, window, cx| {
 2253        editor.move_down(&Default::default(), window, cx);
 2254    });
 2255    cx.update_editor(|editor, window, cx| {
 2256        assert_eq!(
 2257            editor.snapshot(window, cx).scroll_position(),
 2258            gpui::Point::new(0., 4.0)
 2259        );
 2260    });
 2261
 2262    // Add a cursor above the visible area. Since both cursors fit on screen,
 2263    // the editor scrolls to show both.
 2264    cx.update_editor(|editor, window, cx| {
 2265        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2266            selections.select_ranges([
 2267                Point::new(1, 0)..Point::new(1, 0),
 2268                Point::new(6, 0)..Point::new(6, 0),
 2269            ]);
 2270        })
 2271    });
 2272    cx.update_editor(|editor, window, cx| {
 2273        assert_eq!(
 2274            editor.snapshot(window, cx).scroll_position(),
 2275            gpui::Point::new(0., 1.0)
 2276        );
 2277    });
 2278}
 2279
 2280#[gpui::test]
 2281async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2282    init_test(cx, |_| {});
 2283    let mut cx = EditorTestContext::new(cx).await;
 2284
 2285    let line_height = cx.editor(|editor, window, _cx| {
 2286        editor
 2287            .style()
 2288            .unwrap()
 2289            .text
 2290            .line_height_in_pixels(window.rem_size())
 2291    });
 2292    let window = cx.window;
 2293    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2294    cx.set_state(
 2295        &r#"
 2296        ˇone
 2297        two
 2298        threeˇ
 2299        four
 2300        five
 2301        six
 2302        seven
 2303        eight
 2304        nine
 2305        ten
 2306        "#
 2307        .unindent(),
 2308    );
 2309
 2310    cx.update_editor(|editor, window, cx| {
 2311        editor.move_page_down(&MovePageDown::default(), window, cx)
 2312    });
 2313    cx.assert_editor_state(
 2314        &r#"
 2315        one
 2316        two
 2317        three
 2318        ˇfour
 2319        five
 2320        sixˇ
 2321        seven
 2322        eight
 2323        nine
 2324        ten
 2325        "#
 2326        .unindent(),
 2327    );
 2328
 2329    cx.update_editor(|editor, window, cx| {
 2330        editor.move_page_down(&MovePageDown::default(), window, cx)
 2331    });
 2332    cx.assert_editor_state(
 2333        &r#"
 2334        one
 2335        two
 2336        three
 2337        four
 2338        five
 2339        six
 2340        ˇseven
 2341        eight
 2342        nineˇ
 2343        ten
 2344        "#
 2345        .unindent(),
 2346    );
 2347
 2348    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2349    cx.assert_editor_state(
 2350        &r#"
 2351        one
 2352        two
 2353        three
 2354        ˇfour
 2355        five
 2356        sixˇ
 2357        seven
 2358        eight
 2359        nine
 2360        ten
 2361        "#
 2362        .unindent(),
 2363    );
 2364
 2365    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2366    cx.assert_editor_state(
 2367        &r#"
 2368        ˇone
 2369        two
 2370        threeˇ
 2371        four
 2372        five
 2373        six
 2374        seven
 2375        eight
 2376        nine
 2377        ten
 2378        "#
 2379        .unindent(),
 2380    );
 2381
 2382    // Test select collapsing
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386        editor.move_page_down(&MovePageDown::default(), window, cx);
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        seven
 2397        eight
 2398        nine
 2399        ˇten
 2400        ˇ"#
 2401        .unindent(),
 2402    );
 2403}
 2404
 2405#[gpui::test]
 2406async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2407    init_test(cx, |_| {});
 2408    let mut cx = EditorTestContext::new(cx).await;
 2409    cx.set_state("one «two threeˇ» four");
 2410    cx.update_editor(|editor, window, cx| {
 2411        editor.delete_to_beginning_of_line(
 2412            &DeleteToBeginningOfLine {
 2413                stop_at_indent: false,
 2414            },
 2415            window,
 2416            cx,
 2417        );
 2418        assert_eq!(editor.text(cx), " four");
 2419    });
 2420}
 2421
 2422#[gpui::test]
 2423fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2424    init_test(cx, |_| {});
 2425
 2426    let editor = cx.add_window(|window, cx| {
 2427        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2428        build_editor(buffer.clone(), window, cx)
 2429    });
 2430
 2431    _ = editor.update(cx, |editor, window, cx| {
 2432        editor.change_selections(None, window, cx, |s| {
 2433            s.select_display_ranges([
 2434                // an empty selection - the preceding word fragment is deleted
 2435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2436                // characters selected - they are deleted
 2437                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2438            ])
 2439        });
 2440        editor.delete_to_previous_word_start(
 2441            &DeleteToPreviousWordStart {
 2442                ignore_newlines: false,
 2443            },
 2444            window,
 2445            cx,
 2446        );
 2447        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2448    });
 2449
 2450    _ = editor.update(cx, |editor, window, cx| {
 2451        editor.change_selections(None, window, cx, |s| {
 2452            s.select_display_ranges([
 2453                // an empty selection - the following word fragment is deleted
 2454                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2455                // characters selected - they are deleted
 2456                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2457            ])
 2458        });
 2459        editor.delete_to_next_word_end(
 2460            &DeleteToNextWordEnd {
 2461                ignore_newlines: false,
 2462            },
 2463            window,
 2464            cx,
 2465        );
 2466        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2467    });
 2468}
 2469
 2470#[gpui::test]
 2471fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2472    init_test(cx, |_| {});
 2473
 2474    let editor = cx.add_window(|window, cx| {
 2475        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2476        build_editor(buffer.clone(), window, cx)
 2477    });
 2478    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2479        ignore_newlines: false,
 2480    };
 2481    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2482        ignore_newlines: true,
 2483    };
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(None, window, cx, |s| {
 2487            s.select_display_ranges([
 2488                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2489            ])
 2490        });
 2491        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2492        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2493        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2494        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2495        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2496        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2497        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2498        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2499        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2500        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2501        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2502        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2503    });
 2504}
 2505
 2506#[gpui::test]
 2507fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2508    init_test(cx, |_| {});
 2509
 2510    let editor = cx.add_window(|window, cx| {
 2511        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2512        build_editor(buffer.clone(), window, cx)
 2513    });
 2514    let del_to_next_word_end = DeleteToNextWordEnd {
 2515        ignore_newlines: false,
 2516    };
 2517    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2518        ignore_newlines: true,
 2519    };
 2520
 2521    _ = editor.update(cx, |editor, window, cx| {
 2522        editor.change_selections(None, window, cx, |s| {
 2523            s.select_display_ranges([
 2524                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2525            ])
 2526        });
 2527        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2528        assert_eq!(
 2529            editor.buffer.read(cx).read(cx).text(),
 2530            "one\n   two\nthree\n   four"
 2531        );
 2532        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2533        assert_eq!(
 2534            editor.buffer.read(cx).read(cx).text(),
 2535            "\n   two\nthree\n   four"
 2536        );
 2537        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2538        assert_eq!(
 2539            editor.buffer.read(cx).read(cx).text(),
 2540            "two\nthree\n   four"
 2541        );
 2542        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2543        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2544        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2545        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2546        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2547        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2548    });
 2549}
 2550
 2551#[gpui::test]
 2552fn test_newline(cx: &mut TestAppContext) {
 2553    init_test(cx, |_| {});
 2554
 2555    let editor = cx.add_window(|window, cx| {
 2556        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2557        build_editor(buffer.clone(), window, cx)
 2558    });
 2559
 2560    _ = editor.update(cx, |editor, window, cx| {
 2561        editor.change_selections(None, window, cx, |s| {
 2562            s.select_display_ranges([
 2563                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2565                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2566            ])
 2567        });
 2568
 2569        editor.newline(&Newline, window, cx);
 2570        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2571    });
 2572}
 2573
 2574#[gpui::test]
 2575fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2576    init_test(cx, |_| {});
 2577
 2578    let editor = cx.add_window(|window, cx| {
 2579        let buffer = MultiBuffer::build_simple(
 2580            "
 2581                a
 2582                b(
 2583                    X
 2584                )
 2585                c(
 2586                    X
 2587                )
 2588            "
 2589            .unindent()
 2590            .as_str(),
 2591            cx,
 2592        );
 2593        let mut editor = build_editor(buffer.clone(), window, cx);
 2594        editor.change_selections(None, window, cx, |s| {
 2595            s.select_ranges([
 2596                Point::new(2, 4)..Point::new(2, 5),
 2597                Point::new(5, 4)..Point::new(5, 5),
 2598            ])
 2599        });
 2600        editor
 2601    });
 2602
 2603    _ = editor.update(cx, |editor, window, cx| {
 2604        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2605        editor.buffer.update(cx, |buffer, cx| {
 2606            buffer.edit(
 2607                [
 2608                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2609                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2610                ],
 2611                None,
 2612                cx,
 2613            );
 2614            assert_eq!(
 2615                buffer.read(cx).text(),
 2616                "
 2617                    a
 2618                    b()
 2619                    c()
 2620                "
 2621                .unindent()
 2622            );
 2623        });
 2624        assert_eq!(
 2625            editor.selections.ranges(cx),
 2626            &[
 2627                Point::new(1, 2)..Point::new(1, 2),
 2628                Point::new(2, 2)..Point::new(2, 2),
 2629            ],
 2630        );
 2631
 2632        editor.newline(&Newline, window, cx);
 2633        assert_eq!(
 2634            editor.text(cx),
 2635            "
 2636                a
 2637                b(
 2638                )
 2639                c(
 2640                )
 2641            "
 2642            .unindent()
 2643        );
 2644
 2645        // The selections are moved after the inserted newlines
 2646        assert_eq!(
 2647            editor.selections.ranges(cx),
 2648            &[
 2649                Point::new(2, 0)..Point::new(2, 0),
 2650                Point::new(4, 0)..Point::new(4, 0),
 2651            ],
 2652        );
 2653    });
 2654}
 2655
 2656#[gpui::test]
 2657async fn test_newline_above(cx: &mut TestAppContext) {
 2658    init_test(cx, |settings| {
 2659        settings.defaults.tab_size = NonZeroU32::new(4)
 2660    });
 2661
 2662    let language = Arc::new(
 2663        Language::new(
 2664            LanguageConfig::default(),
 2665            Some(tree_sitter_rust::LANGUAGE.into()),
 2666        )
 2667        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2668        .unwrap(),
 2669    );
 2670
 2671    let mut cx = EditorTestContext::new(cx).await;
 2672    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2673    cx.set_state(indoc! {"
 2674        const a: ˇA = (
 2675 2676                «const_functionˇ»(ˇ),
 2677                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2678 2679        ˇ);ˇ
 2680    "});
 2681
 2682    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2683    cx.assert_editor_state(indoc! {"
 2684        ˇ
 2685        const a: A = (
 2686            ˇ
 2687            (
 2688                ˇ
 2689                ˇ
 2690                const_function(),
 2691                ˇ
 2692                ˇ
 2693                ˇ
 2694                ˇ
 2695                something_else,
 2696                ˇ
 2697            )
 2698            ˇ
 2699            ˇ
 2700        );
 2701    "});
 2702}
 2703
 2704#[gpui::test]
 2705async fn test_newline_below(cx: &mut TestAppContext) {
 2706    init_test(cx, |settings| {
 2707        settings.defaults.tab_size = NonZeroU32::new(4)
 2708    });
 2709
 2710    let language = Arc::new(
 2711        Language::new(
 2712            LanguageConfig::default(),
 2713            Some(tree_sitter_rust::LANGUAGE.into()),
 2714        )
 2715        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2716        .unwrap(),
 2717    );
 2718
 2719    let mut cx = EditorTestContext::new(cx).await;
 2720    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2721    cx.set_state(indoc! {"
 2722        const a: ˇA = (
 2723 2724                «const_functionˇ»(ˇ),
 2725                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2726 2727        ˇ);ˇ
 2728    "});
 2729
 2730    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2731    cx.assert_editor_state(indoc! {"
 2732        const a: A = (
 2733            ˇ
 2734            (
 2735                ˇ
 2736                const_function(),
 2737                ˇ
 2738                ˇ
 2739                something_else,
 2740                ˇ
 2741                ˇ
 2742                ˇ
 2743                ˇ
 2744            )
 2745            ˇ
 2746        );
 2747        ˇ
 2748        ˇ
 2749    "});
 2750}
 2751
 2752#[gpui::test]
 2753async fn test_newline_comments(cx: &mut TestAppContext) {
 2754    init_test(cx, |settings| {
 2755        settings.defaults.tab_size = NonZeroU32::new(4)
 2756    });
 2757
 2758    let language = Arc::new(Language::new(
 2759        LanguageConfig {
 2760            line_comments: vec!["// ".into()],
 2761            ..LanguageConfig::default()
 2762        },
 2763        None,
 2764    ));
 2765    {
 2766        let mut cx = EditorTestContext::new(cx).await;
 2767        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2768        cx.set_state(indoc! {"
 2769        // Fooˇ
 2770    "});
 2771
 2772        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2773        cx.assert_editor_state(indoc! {"
 2774        // Foo
 2775        // ˇ
 2776    "});
 2777        // Ensure that we add comment prefix when existing line contains space
 2778        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2779        cx.assert_editor_state(
 2780            indoc! {"
 2781        // Foo
 2782        //s
 2783        // ˇ
 2784    "}
 2785            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2786            .as_str(),
 2787        );
 2788        // Ensure that we add comment prefix when existing line does not contain space
 2789        cx.set_state(indoc! {"
 2790        // Foo
 2791        //ˇ
 2792    "});
 2793        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2794        cx.assert_editor_state(indoc! {"
 2795        // Foo
 2796        //
 2797        // ˇ
 2798    "});
 2799        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2800        cx.set_state(indoc! {"
 2801        ˇ// Foo
 2802    "});
 2803        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2804        cx.assert_editor_state(indoc! {"
 2805
 2806        ˇ// Foo
 2807    "});
 2808    }
 2809    // Ensure that comment continuations can be disabled.
 2810    update_test_language_settings(cx, |settings| {
 2811        settings.defaults.extend_comment_on_newline = Some(false);
 2812    });
 2813    let mut cx = EditorTestContext::new(cx).await;
 2814    cx.set_state(indoc! {"
 2815        // Fooˇ
 2816    "});
 2817    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2818    cx.assert_editor_state(indoc! {"
 2819        // Foo
 2820        ˇ
 2821    "});
 2822}
 2823
 2824#[gpui::test]
 2825async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2826    init_test(cx, |settings| {
 2827        settings.defaults.tab_size = NonZeroU32::new(4)
 2828    });
 2829
 2830    let language = Arc::new(Language::new(
 2831        LanguageConfig {
 2832            line_comments: vec!["// ".into(), "/// ".into()],
 2833            ..LanguageConfig::default()
 2834        },
 2835        None,
 2836    ));
 2837    {
 2838        let mut cx = EditorTestContext::new(cx).await;
 2839        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2840        cx.set_state(indoc! {"
 2841        //ˇ
 2842    "});
 2843        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2844        cx.assert_editor_state(indoc! {"
 2845        //
 2846        // ˇ
 2847    "});
 2848
 2849        cx.set_state(indoc! {"
 2850        ///ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        ///
 2855        /// ˇ
 2856    "});
 2857    }
 2858}
 2859
 2860#[gpui::test]
 2861async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2862    init_test(cx, |settings| {
 2863        settings.defaults.tab_size = NonZeroU32::new(4)
 2864    });
 2865
 2866    let language = Arc::new(
 2867        Language::new(
 2868            LanguageConfig {
 2869                documentation: Some(language::DocumentationConfig {
 2870                    start: "/**".into(),
 2871                    end: "*/".into(),
 2872                    prefix: "* ".into(),
 2873                    tab_size: NonZeroU32::new(1).unwrap(),
 2874                }),
 2875
 2876                ..LanguageConfig::default()
 2877            },
 2878            Some(tree_sitter_rust::LANGUAGE.into()),
 2879        )
 2880        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2881        .unwrap(),
 2882    );
 2883
 2884    {
 2885        let mut cx = EditorTestContext::new(cx).await;
 2886        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2887        cx.set_state(indoc! {"
 2888        /**ˇ
 2889    "});
 2890
 2891        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2892        cx.assert_editor_state(indoc! {"
 2893        /**
 2894         * ˇ
 2895    "});
 2896        // Ensure that if cursor is before the comment start,
 2897        // we do not actually insert a comment prefix.
 2898        cx.set_state(indoc! {"
 2899        ˇ/**
 2900    "});
 2901        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2902        cx.assert_editor_state(indoc! {"
 2903
 2904        ˇ/**
 2905    "});
 2906        // Ensure that if cursor is between it doesn't add comment prefix.
 2907        cx.set_state(indoc! {"
 2908        /*ˇ*
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912        /*
 2913        ˇ*
 2914    "});
 2915        // Ensure that if suffix exists on same line after cursor it adds new line.
 2916        cx.set_state(indoc! {"
 2917        /**ˇ*/
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /**
 2922         * ˇ
 2923         */
 2924    "});
 2925        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2926        cx.set_state(indoc! {"
 2927        /**ˇ */
 2928    "});
 2929        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2930        cx.assert_editor_state(indoc! {"
 2931        /**
 2932         * ˇ
 2933         */
 2934    "});
 2935        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2936        cx.set_state(indoc! {"
 2937        /** ˇ*/
 2938    "});
 2939        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2940        cx.assert_editor_state(
 2941            indoc! {"
 2942        /**s
 2943         * ˇ
 2944         */
 2945    "}
 2946            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2947            .as_str(),
 2948        );
 2949        // Ensure that delimiter space is preserved when newline on already
 2950        // spaced delimiter.
 2951        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2952        cx.assert_editor_state(
 2953            indoc! {"
 2954        /**s
 2955         *s
 2956         * ˇ
 2957         */
 2958    "}
 2959            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2960            .as_str(),
 2961        );
 2962        // Ensure that delimiter space is preserved when space is not
 2963        // on existing delimiter.
 2964        cx.set_state(indoc! {"
 2965        /**
 2966 2967         */
 2968    "});
 2969        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2970        cx.assert_editor_state(indoc! {"
 2971        /**
 2972         *
 2973         * ˇ
 2974         */
 2975    "});
 2976        // Ensure that if suffix exists on same line after cursor it
 2977        // doesn't add extra new line if prefix is not on same line.
 2978        cx.set_state(indoc! {"
 2979        /**
 2980        ˇ*/
 2981    "});
 2982        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2983        cx.assert_editor_state(indoc! {"
 2984        /**
 2985
 2986        ˇ*/
 2987    "});
 2988        // Ensure that it detects suffix after existing prefix.
 2989        cx.set_state(indoc! {"
 2990        /**ˇ/
 2991    "});
 2992        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2993        cx.assert_editor_state(indoc! {"
 2994        /**
 2995        ˇ/
 2996    "});
 2997        // Ensure that if suffix exists on same line before
 2998        // cursor it does not add comment prefix.
 2999        cx.set_state(indoc! {"
 3000        /** */ˇ
 3001    "});
 3002        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3003        cx.assert_editor_state(indoc! {"
 3004        /** */
 3005        ˇ
 3006    "});
 3007        // Ensure that if suffix exists on same line before
 3008        // cursor it does not add comment prefix.
 3009        cx.set_state(indoc! {"
 3010        /**
 3011         *
 3012         */ˇ
 3013    "});
 3014        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3015        cx.assert_editor_state(indoc! {"
 3016        /**
 3017         *
 3018         */
 3019         ˇ
 3020    "});
 3021
 3022        // Ensure that inline comment followed by code
 3023        // doesn't add comment prefix on newline
 3024        cx.set_state(indoc! {"
 3025        /** */ textˇ
 3026    "});
 3027        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3028        cx.assert_editor_state(indoc! {"
 3029        /** */ text
 3030        ˇ
 3031    "});
 3032
 3033        // Ensure that text after comment end tag
 3034        // doesn't add comment prefix on newline
 3035        cx.set_state(indoc! {"
 3036        /**
 3037         *
 3038         */ˇtext
 3039    "});
 3040        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3041        cx.assert_editor_state(indoc! {"
 3042        /**
 3043         *
 3044         */
 3045         ˇtext
 3046    "});
 3047
 3048        // Ensure if not comment block it doesn't
 3049        // add comment prefix on newline
 3050        cx.set_state(indoc! {"
 3051        * textˇ
 3052    "});
 3053        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3054        cx.assert_editor_state(indoc! {"
 3055        * text
 3056        ˇ
 3057    "});
 3058    }
 3059    // Ensure that comment continuations can be disabled.
 3060    update_test_language_settings(cx, |settings| {
 3061        settings.defaults.extend_comment_on_newline = Some(false);
 3062    });
 3063    let mut cx = EditorTestContext::new(cx).await;
 3064    cx.set_state(indoc! {"
 3065        /**ˇ
 3066    "});
 3067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3068    cx.assert_editor_state(indoc! {"
 3069        /**
 3070        ˇ
 3071    "});
 3072}
 3073
 3074#[gpui::test]
 3075fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3076    init_test(cx, |_| {});
 3077
 3078    let editor = cx.add_window(|window, cx| {
 3079        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3080        let mut editor = build_editor(buffer.clone(), window, cx);
 3081        editor.change_selections(None, window, cx, |s| {
 3082            s.select_ranges([3..4, 11..12, 19..20])
 3083        });
 3084        editor
 3085    });
 3086
 3087    _ = editor.update(cx, |editor, window, cx| {
 3088        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3089        editor.buffer.update(cx, |buffer, cx| {
 3090            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3091            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3092        });
 3093        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3094
 3095        editor.insert("Z", window, cx);
 3096        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3097
 3098        // The selections are moved after the inserted characters
 3099        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3100    });
 3101}
 3102
 3103#[gpui::test]
 3104async fn test_tab(cx: &mut TestAppContext) {
 3105    init_test(cx, |settings| {
 3106        settings.defaults.tab_size = NonZeroU32::new(3)
 3107    });
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.set_state(indoc! {"
 3111        ˇabˇc
 3112        ˇ🏀ˇ🏀ˇefg
 3113 3114    "});
 3115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117           ˇab ˇc
 3118           ˇ🏀  ˇ🏀  ˇefg
 3119        d  ˇ
 3120    "});
 3121
 3122    cx.set_state(indoc! {"
 3123        a
 3124        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3125    "});
 3126    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        a
 3129           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3130    "});
 3131}
 3132
 3133#[gpui::test]
 3134async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3135    init_test(cx, |_| {});
 3136
 3137    let mut cx = EditorTestContext::new(cx).await;
 3138    let language = Arc::new(
 3139        Language::new(
 3140            LanguageConfig::default(),
 3141            Some(tree_sitter_rust::LANGUAGE.into()),
 3142        )
 3143        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3144        .unwrap(),
 3145    );
 3146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3147
 3148    // test when all cursors are not at suggested indent
 3149    // then simply move to their suggested indent location
 3150    cx.set_state(indoc! {"
 3151        const a: B = (
 3152            c(
 3153        ˇ
 3154        ˇ    )
 3155        );
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        const a: B = (
 3160            c(
 3161                ˇ
 3162            ˇ)
 3163        );
 3164    "});
 3165
 3166    // test cursor already at suggested indent not moving when
 3167    // other cursors are yet to reach their suggested indents
 3168    cx.set_state(indoc! {"
 3169        ˇ
 3170        const a: B = (
 3171            c(
 3172                d(
 3173        ˇ
 3174                )
 3175        ˇ
 3176        ˇ    )
 3177        );
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        ˇ
 3182        const a: B = (
 3183            c(
 3184                d(
 3185                    ˇ
 3186                )
 3187                ˇ
 3188            ˇ)
 3189        );
 3190    "});
 3191    // test when all cursors are at suggested indent then tab is inserted
 3192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194            ˇ
 3195        const a: B = (
 3196            c(
 3197                d(
 3198                        ˇ
 3199                )
 3200                    ˇ
 3201                ˇ)
 3202        );
 3203    "});
 3204
 3205    // test when current indent is less than suggested indent,
 3206    // we adjust line to match suggested indent and move cursor to it
 3207    //
 3208    // when no other cursor is at word boundary, all of them should move
 3209    cx.set_state(indoc! {"
 3210        const a: B = (
 3211            c(
 3212                d(
 3213        ˇ
 3214        ˇ   )
 3215        ˇ   )
 3216        );
 3217    "});
 3218    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3219    cx.assert_editor_state(indoc! {"
 3220        const a: B = (
 3221            c(
 3222                d(
 3223                    ˇ
 3224                ˇ)
 3225            ˇ)
 3226        );
 3227    "});
 3228
 3229    // test when current indent is less than suggested indent,
 3230    // we adjust line to match suggested indent and move cursor to it
 3231    //
 3232    // when some other cursor is at word boundary, it should not move
 3233    cx.set_state(indoc! {"
 3234        const a: B = (
 3235            c(
 3236                d(
 3237        ˇ
 3238        ˇ   )
 3239           ˇ)
 3240        );
 3241    "});
 3242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244        const a: B = (
 3245            c(
 3246                d(
 3247                    ˇ
 3248                ˇ)
 3249            ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is more than suggested indent,
 3254    // we just move cursor to current indent instead of suggested indent
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ                )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                        ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3277    cx.assert_editor_state(indoc! {"
 3278        const a: B = (
 3279            c(
 3280                d(
 3281                        ˇ
 3282                            ˇ)
 3283                ˇ)
 3284        );
 3285    "});
 3286
 3287    // test when current indent is more than suggested indent,
 3288    // we just move cursor to current indent instead of suggested indent
 3289    //
 3290    // when some other cursor is at word boundary, it doesn't move
 3291    cx.set_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295        ˇ
 3296        ˇ                )
 3297            ˇ)
 3298        );
 3299    "});
 3300    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3301    cx.assert_editor_state(indoc! {"
 3302        const a: B = (
 3303            c(
 3304                d(
 3305                    ˇ
 3306                        ˇ)
 3307            ˇ)
 3308        );
 3309    "});
 3310
 3311    // handle auto-indent when there are multiple cursors on the same line
 3312    cx.set_state(indoc! {"
 3313        const a: B = (
 3314            c(
 3315        ˇ    ˇ
 3316        ˇ    )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                ˇ
 3324            ˇ)
 3325        );
 3326    "});
 3327}
 3328
 3329#[gpui::test]
 3330async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3331    init_test(cx, |settings| {
 3332        settings.defaults.tab_size = NonZeroU32::new(3)
 3333    });
 3334
 3335    let mut cx = EditorTestContext::new(cx).await;
 3336    cx.set_state(indoc! {"
 3337         ˇ
 3338        \t ˇ
 3339        \t  ˇ
 3340        \t   ˇ
 3341         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3342    "});
 3343
 3344    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3345    cx.assert_editor_state(indoc! {"
 3346           ˇ
 3347        \t   ˇ
 3348        \t   ˇ
 3349        \t      ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3351    "});
 3352}
 3353
 3354#[gpui::test]
 3355async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3356    init_test(cx, |settings| {
 3357        settings.defaults.tab_size = NonZeroU32::new(4)
 3358    });
 3359
 3360    let language = Arc::new(
 3361        Language::new(
 3362            LanguageConfig::default(),
 3363            Some(tree_sitter_rust::LANGUAGE.into()),
 3364        )
 3365        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3366        .unwrap(),
 3367    );
 3368
 3369    let mut cx = EditorTestContext::new(cx).await;
 3370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3371    cx.set_state(indoc! {"
 3372        fn a() {
 3373            if b {
 3374        \t ˇc
 3375            }
 3376        }
 3377    "});
 3378
 3379    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3380    cx.assert_editor_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383                ˇc
 3384            }
 3385        }
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_indent_outdent(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4);
 3393    });
 3394
 3395    let mut cx = EditorTestContext::new(cx).await;
 3396
 3397    cx.set_state(indoc! {"
 3398          «oneˇ» «twoˇ»
 3399        three
 3400         four
 3401    "});
 3402    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3403    cx.assert_editor_state(indoc! {"
 3404            «oneˇ» «twoˇ»
 3405        three
 3406         four
 3407    "});
 3408
 3409    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3410    cx.assert_editor_state(indoc! {"
 3411        «oneˇ» «twoˇ»
 3412        three
 3413         four
 3414    "});
 3415
 3416    // select across line ending
 3417    cx.set_state(indoc! {"
 3418        one two
 3419        t«hree
 3420        ˇ» four
 3421    "});
 3422    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3423    cx.assert_editor_state(indoc! {"
 3424        one two
 3425            t«hree
 3426        ˇ» four
 3427    "});
 3428
 3429    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3430    cx.assert_editor_state(indoc! {"
 3431        one two
 3432        t«hree
 3433        ˇ» four
 3434    "});
 3435
 3436    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3437    cx.set_state(indoc! {"
 3438        one two
 3439        ˇthree
 3440            four
 3441    "});
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444        one two
 3445            ˇthree
 3446            four
 3447    "});
 3448
 3449    cx.set_state(indoc! {"
 3450        one two
 3451        ˇ    three
 3452            four
 3453    "});
 3454    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3455    cx.assert_editor_state(indoc! {"
 3456        one two
 3457        ˇthree
 3458            four
 3459    "});
 3460}
 3461
 3462#[gpui::test]
 3463async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3464    init_test(cx, |settings| {
 3465        settings.defaults.hard_tabs = Some(true);
 3466    });
 3467
 3468    let mut cx = EditorTestContext::new(cx).await;
 3469
 3470    // select two ranges on one line
 3471    cx.set_state(indoc! {"
 3472        «oneˇ» «twoˇ»
 3473        three
 3474        four
 3475    "});
 3476    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3477    cx.assert_editor_state(indoc! {"
 3478        \t«oneˇ» «twoˇ»
 3479        three
 3480        four
 3481    "});
 3482    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        \t\t«oneˇ» «twoˇ»
 3485        three
 3486        four
 3487    "});
 3488    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3489    cx.assert_editor_state(indoc! {"
 3490        \t«oneˇ» «twoˇ»
 3491        three
 3492        four
 3493    "});
 3494    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3495    cx.assert_editor_state(indoc! {"
 3496        «oneˇ» «twoˇ»
 3497        three
 3498        four
 3499    "});
 3500
 3501    // select across a line ending
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        t«hree
 3505        ˇ»four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        \tt«hree
 3511        ˇ»four
 3512    "});
 3513    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3514    cx.assert_editor_state(indoc! {"
 3515        one two
 3516        \t\tt«hree
 3517        ˇ»four
 3518    "});
 3519    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3520    cx.assert_editor_state(indoc! {"
 3521        one two
 3522        \tt«hree
 3523        ˇ»four
 3524    "});
 3525    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3526    cx.assert_editor_state(indoc! {"
 3527        one two
 3528        t«hree
 3529        ˇ»four
 3530    "});
 3531
 3532    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3533    cx.set_state(indoc! {"
 3534        one two
 3535        ˇthree
 3536        four
 3537    "});
 3538    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3539    cx.assert_editor_state(indoc! {"
 3540        one two
 3541        ˇthree
 3542        four
 3543    "});
 3544    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3545    cx.assert_editor_state(indoc! {"
 3546        one two
 3547        \tˇthree
 3548        four
 3549    "});
 3550    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3551    cx.assert_editor_state(indoc! {"
 3552        one two
 3553        ˇthree
 3554        four
 3555    "});
 3556}
 3557
 3558#[gpui::test]
 3559fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3560    init_test(cx, |settings| {
 3561        settings.languages.extend([
 3562            (
 3563                "TOML".into(),
 3564                LanguageSettingsContent {
 3565                    tab_size: NonZeroU32::new(2),
 3566                    ..Default::default()
 3567                },
 3568            ),
 3569            (
 3570                "Rust".into(),
 3571                LanguageSettingsContent {
 3572                    tab_size: NonZeroU32::new(4),
 3573                    ..Default::default()
 3574                },
 3575            ),
 3576        ]);
 3577    });
 3578
 3579    let toml_language = Arc::new(Language::new(
 3580        LanguageConfig {
 3581            name: "TOML".into(),
 3582            ..Default::default()
 3583        },
 3584        None,
 3585    ));
 3586    let rust_language = Arc::new(Language::new(
 3587        LanguageConfig {
 3588            name: "Rust".into(),
 3589            ..Default::default()
 3590        },
 3591        None,
 3592    ));
 3593
 3594    let toml_buffer =
 3595        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3596    let rust_buffer =
 3597        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3598    let multibuffer = cx.new(|cx| {
 3599        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3600        multibuffer.push_excerpts(
 3601            toml_buffer.clone(),
 3602            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3603            cx,
 3604        );
 3605        multibuffer.push_excerpts(
 3606            rust_buffer.clone(),
 3607            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3608            cx,
 3609        );
 3610        multibuffer
 3611    });
 3612
 3613    cx.add_window(|window, cx| {
 3614        let mut editor = build_editor(multibuffer, window, cx);
 3615
 3616        assert_eq!(
 3617            editor.text(cx),
 3618            indoc! {"
 3619                a = 1
 3620                b = 2
 3621
 3622                const c: usize = 3;
 3623            "}
 3624        );
 3625
 3626        select_ranges(
 3627            &mut editor,
 3628            indoc! {"
 3629                «aˇ» = 1
 3630                b = 2
 3631
 3632                «const c:ˇ» usize = 3;
 3633            "},
 3634            window,
 3635            cx,
 3636        );
 3637
 3638        editor.tab(&Tab, window, cx);
 3639        assert_text_with_selections(
 3640            &mut editor,
 3641            indoc! {"
 3642                  «aˇ» = 1
 3643                b = 2
 3644
 3645                    «const c:ˇ» usize = 3;
 3646            "},
 3647            cx,
 3648        );
 3649        editor.backtab(&Backtab, window, cx);
 3650        assert_text_with_selections(
 3651            &mut editor,
 3652            indoc! {"
 3653                «aˇ» = 1
 3654                b = 2
 3655
 3656                «const c:ˇ» usize = 3;
 3657            "},
 3658            cx,
 3659        );
 3660
 3661        editor
 3662    });
 3663}
 3664
 3665#[gpui::test]
 3666async fn test_backspace(cx: &mut TestAppContext) {
 3667    init_test(cx, |_| {});
 3668
 3669    let mut cx = EditorTestContext::new(cx).await;
 3670
 3671    // Basic backspace
 3672    cx.set_state(indoc! {"
 3673        onˇe two three
 3674        fou«rˇ» five six
 3675        seven «ˇeight nine
 3676        »ten
 3677    "});
 3678    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3679    cx.assert_editor_state(indoc! {"
 3680        oˇe two three
 3681        fouˇ five six
 3682        seven ˇten
 3683    "});
 3684
 3685    // Test backspace inside and around indents
 3686    cx.set_state(indoc! {"
 3687        zero
 3688            ˇone
 3689                ˇtwo
 3690            ˇ ˇ ˇ  three
 3691        ˇ  ˇ  four
 3692    "});
 3693    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3694    cx.assert_editor_state(indoc! {"
 3695        zero
 3696        ˇone
 3697            ˇtwo
 3698        ˇ  threeˇ  four
 3699    "});
 3700}
 3701
 3702#[gpui::test]
 3703async fn test_delete(cx: &mut TestAppContext) {
 3704    init_test(cx, |_| {});
 3705
 3706    let mut cx = EditorTestContext::new(cx).await;
 3707    cx.set_state(indoc! {"
 3708        onˇe two three
 3709        fou«rˇ» five six
 3710        seven «ˇeight nine
 3711        »ten
 3712    "});
 3713    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3714    cx.assert_editor_state(indoc! {"
 3715        onˇ two three
 3716        fouˇ five six
 3717        seven ˇten
 3718    "});
 3719}
 3720
 3721#[gpui::test]
 3722fn test_delete_line(cx: &mut TestAppContext) {
 3723    init_test(cx, |_| {});
 3724
 3725    let editor = cx.add_window(|window, cx| {
 3726        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3727        build_editor(buffer, window, cx)
 3728    });
 3729    _ = editor.update(cx, |editor, window, cx| {
 3730        editor.change_selections(None, window, cx, |s| {
 3731            s.select_display_ranges([
 3732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3733                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3734                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3735            ])
 3736        });
 3737        editor.delete_line(&DeleteLine, window, cx);
 3738        assert_eq!(editor.display_text(cx), "ghi");
 3739        assert_eq!(
 3740            editor.selections.display_ranges(cx),
 3741            vec![
 3742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3744            ]
 3745        );
 3746    });
 3747
 3748    let editor = cx.add_window(|window, cx| {
 3749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3750        build_editor(buffer, window, cx)
 3751    });
 3752    _ = editor.update(cx, |editor, window, cx| {
 3753        editor.change_selections(None, window, cx, |s| {
 3754            s.select_display_ranges([
 3755                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3756            ])
 3757        });
 3758        editor.delete_line(&DeleteLine, window, cx);
 3759        assert_eq!(editor.display_text(cx), "ghi\n");
 3760        assert_eq!(
 3761            editor.selections.display_ranges(cx),
 3762            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3763        );
 3764    });
 3765}
 3766
 3767#[gpui::test]
 3768fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3769    init_test(cx, |_| {});
 3770
 3771    cx.add_window(|window, cx| {
 3772        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3773        let mut editor = build_editor(buffer.clone(), window, cx);
 3774        let buffer = buffer.read(cx).as_singleton().unwrap();
 3775
 3776        assert_eq!(
 3777            editor.selections.ranges::<Point>(cx),
 3778            &[Point::new(0, 0)..Point::new(0, 0)]
 3779        );
 3780
 3781        // When on single line, replace newline at end by space
 3782        editor.join_lines(&JoinLines, window, cx);
 3783        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3784        assert_eq!(
 3785            editor.selections.ranges::<Point>(cx),
 3786            &[Point::new(0, 3)..Point::new(0, 3)]
 3787        );
 3788
 3789        // When multiple lines are selected, remove newlines that are spanned by the selection
 3790        editor.change_selections(None, window, cx, |s| {
 3791            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3792        });
 3793        editor.join_lines(&JoinLines, window, cx);
 3794        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3795        assert_eq!(
 3796            editor.selections.ranges::<Point>(cx),
 3797            &[Point::new(0, 11)..Point::new(0, 11)]
 3798        );
 3799
 3800        // Undo should be transactional
 3801        editor.undo(&Undo, window, cx);
 3802        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3803        assert_eq!(
 3804            editor.selections.ranges::<Point>(cx),
 3805            &[Point::new(0, 5)..Point::new(2, 2)]
 3806        );
 3807
 3808        // When joining an empty line don't insert a space
 3809        editor.change_selections(None, window, cx, |s| {
 3810            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3811        });
 3812        editor.join_lines(&JoinLines, window, cx);
 3813        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3814        assert_eq!(
 3815            editor.selections.ranges::<Point>(cx),
 3816            [Point::new(2, 3)..Point::new(2, 3)]
 3817        );
 3818
 3819        // We can remove trailing newlines
 3820        editor.join_lines(&JoinLines, window, cx);
 3821        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3822        assert_eq!(
 3823            editor.selections.ranges::<Point>(cx),
 3824            [Point::new(2, 3)..Point::new(2, 3)]
 3825        );
 3826
 3827        // We don't blow up on the last line
 3828        editor.join_lines(&JoinLines, window, cx);
 3829        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3830        assert_eq!(
 3831            editor.selections.ranges::<Point>(cx),
 3832            [Point::new(2, 3)..Point::new(2, 3)]
 3833        );
 3834
 3835        // reset to test indentation
 3836        editor.buffer.update(cx, |buffer, cx| {
 3837            buffer.edit(
 3838                [
 3839                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3840                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3841                ],
 3842                None,
 3843                cx,
 3844            )
 3845        });
 3846
 3847        // We remove any leading spaces
 3848        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3849        editor.change_selections(None, window, cx, |s| {
 3850            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3851        });
 3852        editor.join_lines(&JoinLines, window, cx);
 3853        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3854
 3855        // We don't insert a space for a line containing only spaces
 3856        editor.join_lines(&JoinLines, window, cx);
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3858
 3859        // We ignore any leading tabs
 3860        editor.join_lines(&JoinLines, window, cx);
 3861        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3862
 3863        editor
 3864    });
 3865}
 3866
 3867#[gpui::test]
 3868fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3869    init_test(cx, |_| {});
 3870
 3871    cx.add_window(|window, cx| {
 3872        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3873        let mut editor = build_editor(buffer.clone(), window, cx);
 3874        let buffer = buffer.read(cx).as_singleton().unwrap();
 3875
 3876        editor.change_selections(None, window, cx, |s| {
 3877            s.select_ranges([
 3878                Point::new(0, 2)..Point::new(1, 1),
 3879                Point::new(1, 2)..Point::new(1, 2),
 3880                Point::new(3, 1)..Point::new(3, 2),
 3881            ])
 3882        });
 3883
 3884        editor.join_lines(&JoinLines, window, cx);
 3885        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3886
 3887        assert_eq!(
 3888            editor.selections.ranges::<Point>(cx),
 3889            [
 3890                Point::new(0, 7)..Point::new(0, 7),
 3891                Point::new(1, 3)..Point::new(1, 3)
 3892            ]
 3893        );
 3894        editor
 3895    });
 3896}
 3897
 3898#[gpui::test]
 3899async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3900    init_test(cx, |_| {});
 3901
 3902    let mut cx = EditorTestContext::new(cx).await;
 3903
 3904    let diff_base = r#"
 3905        Line 0
 3906        Line 1
 3907        Line 2
 3908        Line 3
 3909        "#
 3910    .unindent();
 3911
 3912    cx.set_state(
 3913        &r#"
 3914        ˇLine 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919        .unindent(),
 3920    );
 3921
 3922    cx.set_head_text(&diff_base);
 3923    executor.run_until_parked();
 3924
 3925    // Join lines
 3926    cx.update_editor(|editor, window, cx| {
 3927        editor.join_lines(&JoinLines, window, cx);
 3928    });
 3929    executor.run_until_parked();
 3930
 3931    cx.assert_editor_state(
 3932        &r#"
 3933        Line 0ˇ Line 1
 3934        Line 2
 3935        Line 3
 3936        "#
 3937        .unindent(),
 3938    );
 3939    // Join again
 3940    cx.update_editor(|editor, window, cx| {
 3941        editor.join_lines(&JoinLines, window, cx);
 3942    });
 3943    executor.run_until_parked();
 3944
 3945    cx.assert_editor_state(
 3946        &r#"
 3947        Line 0 Line 1ˇ Line 2
 3948        Line 3
 3949        "#
 3950        .unindent(),
 3951    );
 3952}
 3953
 3954#[gpui::test]
 3955async fn test_custom_newlines_cause_no_false_positive_diffs(
 3956    executor: BackgroundExecutor,
 3957    cx: &mut TestAppContext,
 3958) {
 3959    init_test(cx, |_| {});
 3960    let mut cx = EditorTestContext::new(cx).await;
 3961    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3962    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3963    executor.run_until_parked();
 3964
 3965    cx.update_editor(|editor, window, cx| {
 3966        let snapshot = editor.snapshot(window, cx);
 3967        assert_eq!(
 3968            snapshot
 3969                .buffer_snapshot
 3970                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3971                .collect::<Vec<_>>(),
 3972            Vec::new(),
 3973            "Should not have any diffs for files with custom newlines"
 3974        );
 3975    });
 3976}
 3977
 3978#[gpui::test]
 3979async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3980    init_test(cx, |_| {});
 3981
 3982    let mut cx = EditorTestContext::new(cx).await;
 3983
 3984    // Test sort_lines_case_insensitive()
 3985    cx.set_state(indoc! {"
 3986        «z
 3987        y
 3988        x
 3989        Z
 3990        Y
 3991        Xˇ»
 3992    "});
 3993    cx.update_editor(|e, window, cx| {
 3994        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3995    });
 3996    cx.assert_editor_state(indoc! {"
 3997        «x
 3998        X
 3999        y
 4000        Y
 4001        z
 4002        Zˇ»
 4003    "});
 4004
 4005    // Test reverse_lines()
 4006    cx.set_state(indoc! {"
 4007        «5
 4008        4
 4009        3
 4010        2
 4011        1ˇ»
 4012    "});
 4013    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4014    cx.assert_editor_state(indoc! {"
 4015        «1
 4016        2
 4017        3
 4018        4
 4019        5ˇ»
 4020    "});
 4021
 4022    // Skip testing shuffle_line()
 4023
 4024    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 4025    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 4026
 4027    // Don't manipulate when cursor is on single line, but expand the selection
 4028    cx.set_state(indoc! {"
 4029        ddˇdd
 4030        ccc
 4031        bb
 4032        a
 4033    "});
 4034    cx.update_editor(|e, window, cx| {
 4035        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4036    });
 4037    cx.assert_editor_state(indoc! {"
 4038        «ddddˇ»
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043
 4044    // Basic manipulate case
 4045    // Start selection moves to column 0
 4046    // End of selection shrinks to fit shorter line
 4047    cx.set_state(indoc! {"
 4048        dd«d
 4049        ccc
 4050        bb
 4051        aaaaaˇ»
 4052    "});
 4053    cx.update_editor(|e, window, cx| {
 4054        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4055    });
 4056    cx.assert_editor_state(indoc! {"
 4057        «aaaaa
 4058        bb
 4059        ccc
 4060        dddˇ»
 4061    "});
 4062
 4063    // Manipulate case with newlines
 4064    cx.set_state(indoc! {"
 4065        dd«d
 4066        ccc
 4067
 4068        bb
 4069        aaaaa
 4070
 4071        ˇ»
 4072    "});
 4073    cx.update_editor(|e, window, cx| {
 4074        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4075    });
 4076    cx.assert_editor_state(indoc! {"
 4077        «
 4078
 4079        aaaaa
 4080        bb
 4081        ccc
 4082        dddˇ»
 4083
 4084    "});
 4085
 4086    // Adding new line
 4087    cx.set_state(indoc! {"
 4088        aa«a
 4089        bbˇ»b
 4090    "});
 4091    cx.update_editor(|e, window, cx| {
 4092        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4093    });
 4094    cx.assert_editor_state(indoc! {"
 4095        «aaa
 4096        bbb
 4097        added_lineˇ»
 4098    "});
 4099
 4100    // Removing line
 4101    cx.set_state(indoc! {"
 4102        aa«a
 4103        bbbˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.manipulate_lines(window, cx, |lines| {
 4107            lines.pop();
 4108        })
 4109    });
 4110    cx.assert_editor_state(indoc! {"
 4111        «aaaˇ»
 4112    "});
 4113
 4114    // Removing all lines
 4115    cx.set_state(indoc! {"
 4116        aa«a
 4117        bbbˇ»
 4118    "});
 4119    cx.update_editor(|e, window, cx| {
 4120        e.manipulate_lines(window, cx, |lines| {
 4121            lines.drain(..);
 4122        })
 4123    });
 4124    cx.assert_editor_state(indoc! {"
 4125        ˇ
 4126    "});
 4127}
 4128
 4129#[gpui::test]
 4130async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4131    init_test(cx, |_| {});
 4132
 4133    let mut cx = EditorTestContext::new(cx).await;
 4134
 4135    // Consider continuous selection as single selection
 4136    cx.set_state(indoc! {"
 4137        Aaa«aa
 4138        cˇ»c«c
 4139        bb
 4140        aaaˇ»aa
 4141    "});
 4142    cx.update_editor(|e, window, cx| {
 4143        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4144    });
 4145    cx.assert_editor_state(indoc! {"
 4146        «Aaaaa
 4147        ccc
 4148        bb
 4149        aaaaaˇ»
 4150    "});
 4151
 4152    cx.set_state(indoc! {"
 4153        Aaa«aa
 4154        cˇ»c«c
 4155        bb
 4156        aaaˇ»aa
 4157    "});
 4158    cx.update_editor(|e, window, cx| {
 4159        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4160    });
 4161    cx.assert_editor_state(indoc! {"
 4162        «Aaaaa
 4163        ccc
 4164        bbˇ»
 4165    "});
 4166
 4167    // Consider non continuous selection as distinct dedup operations
 4168    cx.set_state(indoc! {"
 4169        «aaaaa
 4170        bb
 4171        aaaaa
 4172        aaaaaˇ»
 4173
 4174        aaa«aaˇ»
 4175    "});
 4176    cx.update_editor(|e, window, cx| {
 4177        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4178    });
 4179    cx.assert_editor_state(indoc! {"
 4180        «aaaaa
 4181        bbˇ»
 4182
 4183        «aaaaaˇ»
 4184    "});
 4185}
 4186
 4187#[gpui::test]
 4188async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4189    init_test(cx, |_| {});
 4190
 4191    let mut cx = EditorTestContext::new(cx).await;
 4192
 4193    cx.set_state(indoc! {"
 4194        «Aaa
 4195        aAa
 4196        Aaaˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| {
 4199        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4200    });
 4201    cx.assert_editor_state(indoc! {"
 4202        «Aaa
 4203        aAaˇ»
 4204    "});
 4205
 4206    cx.set_state(indoc! {"
 4207        «Aaa
 4208        aAa
 4209        aaAˇ»
 4210    "});
 4211    cx.update_editor(|e, window, cx| {
 4212        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4213    });
 4214    cx.assert_editor_state(indoc! {"
 4215        «Aaaˇ»
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224
 4225    // Manipulate with multiple selections on a single line
 4226    cx.set_state(indoc! {"
 4227        dd«dd
 4228        cˇ»c«c
 4229        bb
 4230        aaaˇ»aa
 4231    "});
 4232    cx.update_editor(|e, window, cx| {
 4233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4234    });
 4235    cx.assert_editor_state(indoc! {"
 4236        «aaaaa
 4237        bb
 4238        ccc
 4239        ddddˇ»
 4240    "});
 4241
 4242    // Manipulate with multiple disjoin selections
 4243    cx.set_state(indoc! {"
 4244 4245        4
 4246        3
 4247        2
 4248        1ˇ»
 4249
 4250        dd«dd
 4251        ccc
 4252        bb
 4253        aaaˇ»aa
 4254    "});
 4255    cx.update_editor(|e, window, cx| {
 4256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4257    });
 4258    cx.assert_editor_state(indoc! {"
 4259        «1
 4260        2
 4261        3
 4262        4
 4263        5ˇ»
 4264
 4265        «aaaaa
 4266        bb
 4267        ccc
 4268        ddddˇ»
 4269    "});
 4270
 4271    // Adding lines on each selection
 4272    cx.set_state(indoc! {"
 4273 4274        1ˇ»
 4275
 4276        bb«bb
 4277        aaaˇ»aa
 4278    "});
 4279    cx.update_editor(|e, window, cx| {
 4280        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4281    });
 4282    cx.assert_editor_state(indoc! {"
 4283        «2
 4284        1
 4285        added lineˇ»
 4286
 4287        «bbbb
 4288        aaaaa
 4289        added lineˇ»
 4290    "});
 4291
 4292    // Removing lines on each selection
 4293    cx.set_state(indoc! {"
 4294 4295        1ˇ»
 4296
 4297        bb«bb
 4298        aaaˇ»aa
 4299    "});
 4300    cx.update_editor(|e, window, cx| {
 4301        e.manipulate_lines(window, cx, |lines| {
 4302            lines.pop();
 4303        })
 4304    });
 4305    cx.assert_editor_state(indoc! {"
 4306        «2ˇ»
 4307
 4308        «bbbbˇ»
 4309    "});
 4310}
 4311
 4312#[gpui::test]
 4313async fn test_toggle_case(cx: &mut TestAppContext) {
 4314    init_test(cx, |_| {});
 4315
 4316    let mut cx = EditorTestContext::new(cx).await;
 4317
 4318    // If all lower case -> upper case
 4319    cx.set_state(indoc! {"
 4320        «hello worldˇ»
 4321    "});
 4322    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4323    cx.assert_editor_state(indoc! {"
 4324        «HELLO WORLDˇ»
 4325    "});
 4326
 4327    // If all upper case -> lower case
 4328    cx.set_state(indoc! {"
 4329        «HELLO WORLDˇ»
 4330    "});
 4331    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4332    cx.assert_editor_state(indoc! {"
 4333        «hello worldˇ»
 4334    "});
 4335
 4336    // If any upper case characters are identified -> lower case
 4337    // This matches JetBrains IDEs
 4338    cx.set_state(indoc! {"
 4339        «hEllo worldˇ»
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        «hello worldˇ»
 4344    "});
 4345}
 4346
 4347#[gpui::test]
 4348async fn test_manipulate_text(cx: &mut TestAppContext) {
 4349    init_test(cx, |_| {});
 4350
 4351    let mut cx = EditorTestContext::new(cx).await;
 4352
 4353    // Test convert_to_upper_case()
 4354    cx.set_state(indoc! {"
 4355        «hello worldˇ»
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        «HELLO WORLDˇ»
 4360    "});
 4361
 4362    // Test convert_to_lower_case()
 4363    cx.set_state(indoc! {"
 4364        «HELLO WORLDˇ»
 4365    "});
 4366    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4367    cx.assert_editor_state(indoc! {"
 4368        «hello worldˇ»
 4369    "});
 4370
 4371    // Test multiple line, single selection case
 4372    cx.set_state(indoc! {"
 4373        «The quick brown
 4374        fox jumps over
 4375        the lazy dogˇ»
 4376    "});
 4377    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4378    cx.assert_editor_state(indoc! {"
 4379        «The Quick Brown
 4380        Fox Jumps Over
 4381        The Lazy Dogˇ»
 4382    "});
 4383
 4384    // Test multiple line, single selection case
 4385    cx.set_state(indoc! {"
 4386        «The quick brown
 4387        fox jumps over
 4388        the lazy dogˇ»
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «TheQuickBrown
 4395        FoxJumpsOver
 4396        TheLazyDogˇ»
 4397    "});
 4398
 4399    // From here on out, test more complex cases of manipulate_text()
 4400
 4401    // Test no selection case - should affect words cursors are in
 4402    // Cursor at beginning, middle, and end of word
 4403    cx.set_state(indoc! {"
 4404        ˇhello big beauˇtiful worldˇ
 4405    "});
 4406    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4407    cx.assert_editor_state(indoc! {"
 4408        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4409    "});
 4410
 4411    // Test multiple selections on a single line and across multiple lines
 4412    cx.set_state(indoc! {"
 4413        «Theˇ» quick «brown
 4414        foxˇ» jumps «overˇ»
 4415        the «lazyˇ» dog
 4416    "});
 4417    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4418    cx.assert_editor_state(indoc! {"
 4419        «THEˇ» quick «BROWN
 4420        FOXˇ» jumps «OVERˇ»
 4421        the «LAZYˇ» dog
 4422    "});
 4423
 4424    // Test case where text length grows
 4425    cx.set_state(indoc! {"
 4426        «tschüߡ»
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        «TSCHÜSSˇ»
 4431    "});
 4432
 4433    // Test to make sure we don't crash when text shrinks
 4434    cx.set_state(indoc! {"
 4435        aaa_bbbˇ
 4436    "});
 4437    cx.update_editor(|e, window, cx| {
 4438        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «aaaBbbˇ»
 4442    "});
 4443
 4444    // Test to make sure we all aware of the fact that each word can grow and shrink
 4445    // Final selections should be aware of this fact
 4446    cx.set_state(indoc! {"
 4447        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4448    "});
 4449    cx.update_editor(|e, window, cx| {
 4450        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4451    });
 4452    cx.assert_editor_state(indoc! {"
 4453        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4454    "});
 4455
 4456    cx.set_state(indoc! {"
 4457        «hElLo, WoRld!ˇ»
 4458    "});
 4459    cx.update_editor(|e, window, cx| {
 4460        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4461    });
 4462    cx.assert_editor_state(indoc! {"
 4463        «HeLlO, wOrLD!ˇ»
 4464    "});
 4465}
 4466
 4467#[gpui::test]
 4468fn test_duplicate_line(cx: &mut TestAppContext) {
 4469    init_test(cx, |_| {});
 4470
 4471    let editor = cx.add_window(|window, cx| {
 4472        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4473        build_editor(buffer, window, cx)
 4474    });
 4475    _ = editor.update(cx, |editor, window, cx| {
 4476        editor.change_selections(None, window, cx, |s| {
 4477            s.select_display_ranges([
 4478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4479                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4480                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4481                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4482            ])
 4483        });
 4484        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4485        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4486        assert_eq!(
 4487            editor.selections.display_ranges(cx),
 4488            vec![
 4489                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4490                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4491                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4492                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4493            ]
 4494        );
 4495    });
 4496
 4497    let editor = cx.add_window(|window, cx| {
 4498        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4499        build_editor(buffer, window, cx)
 4500    });
 4501    _ = editor.update(cx, |editor, window, cx| {
 4502        editor.change_selections(None, window, cx, |s| {
 4503            s.select_display_ranges([
 4504                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4505                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4506            ])
 4507        });
 4508        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4509        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![
 4513                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4514                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4515            ]
 4516        );
 4517    });
 4518
 4519    // With `move_upwards` the selections stay in place, except for
 4520    // the lines inserted above them
 4521    let editor = cx.add_window(|window, cx| {
 4522        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4523        build_editor(buffer, window, cx)
 4524    });
 4525    _ = editor.update(cx, |editor, window, cx| {
 4526        editor.change_selections(None, window, cx, |s| {
 4527            s.select_display_ranges([
 4528                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4529                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4530                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4531                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4532            ])
 4533        });
 4534        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4535        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4536        assert_eq!(
 4537            editor.selections.display_ranges(cx),
 4538            vec![
 4539                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4540                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4541                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4542                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4543            ]
 4544        );
 4545    });
 4546
 4547    let editor = cx.add_window(|window, cx| {
 4548        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4549        build_editor(buffer, window, cx)
 4550    });
 4551    _ = editor.update(cx, |editor, window, cx| {
 4552        editor.change_selections(None, window, cx, |s| {
 4553            s.select_display_ranges([
 4554                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4556            ])
 4557        });
 4558        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4559        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4560        assert_eq!(
 4561            editor.selections.display_ranges(cx),
 4562            vec![
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4565            ]
 4566        );
 4567    });
 4568
 4569    let editor = cx.add_window(|window, cx| {
 4570        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4571        build_editor(buffer, window, cx)
 4572    });
 4573    _ = editor.update(cx, |editor, window, cx| {
 4574        editor.change_selections(None, window, cx, |s| {
 4575            s.select_display_ranges([
 4576                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4577                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4578            ])
 4579        });
 4580        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4581        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4582        assert_eq!(
 4583            editor.selections.display_ranges(cx),
 4584            vec![
 4585                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4586                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4587            ]
 4588        );
 4589    });
 4590}
 4591
 4592#[gpui::test]
 4593fn test_move_line_up_down(cx: &mut TestAppContext) {
 4594    init_test(cx, |_| {});
 4595
 4596    let editor = cx.add_window(|window, cx| {
 4597        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4598        build_editor(buffer, window, cx)
 4599    });
 4600    _ = editor.update(cx, |editor, window, cx| {
 4601        editor.fold_creases(
 4602            vec![
 4603                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4604                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4605                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4606            ],
 4607            true,
 4608            window,
 4609            cx,
 4610        );
 4611        editor.change_selections(None, window, cx, |s| {
 4612            s.select_display_ranges([
 4613                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4614                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4615                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4616                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4617            ])
 4618        });
 4619        assert_eq!(
 4620            editor.display_text(cx),
 4621            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4622        );
 4623
 4624        editor.move_line_up(&MoveLineUp, window, cx);
 4625        assert_eq!(
 4626            editor.display_text(cx),
 4627            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4628        );
 4629        assert_eq!(
 4630            editor.selections.display_ranges(cx),
 4631            vec![
 4632                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4633                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4634                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4635                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4636            ]
 4637        );
 4638    });
 4639
 4640    _ = editor.update(cx, |editor, window, cx| {
 4641        editor.move_line_down(&MoveLineDown, window, cx);
 4642        assert_eq!(
 4643            editor.display_text(cx),
 4644            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4645        );
 4646        assert_eq!(
 4647            editor.selections.display_ranges(cx),
 4648            vec![
 4649                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4650                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4651                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4652                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4653            ]
 4654        );
 4655    });
 4656
 4657    _ = editor.update(cx, |editor, window, cx| {
 4658        editor.move_line_down(&MoveLineDown, window, cx);
 4659        assert_eq!(
 4660            editor.display_text(cx),
 4661            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4662        );
 4663        assert_eq!(
 4664            editor.selections.display_ranges(cx),
 4665            vec![
 4666                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4667                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4668                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4669                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4670            ]
 4671        );
 4672    });
 4673
 4674    _ = editor.update(cx, |editor, window, cx| {
 4675        editor.move_line_up(&MoveLineUp, window, cx);
 4676        assert_eq!(
 4677            editor.display_text(cx),
 4678            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4679        );
 4680        assert_eq!(
 4681            editor.selections.display_ranges(cx),
 4682            vec![
 4683                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4684                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4685                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4686                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4687            ]
 4688        );
 4689    });
 4690}
 4691
 4692#[gpui::test]
 4693fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let editor = cx.add_window(|window, cx| {
 4697        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4698        build_editor(buffer, window, cx)
 4699    });
 4700    _ = editor.update(cx, |editor, window, cx| {
 4701        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4702        editor.insert_blocks(
 4703            [BlockProperties {
 4704                style: BlockStyle::Fixed,
 4705                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4706                height: Some(1),
 4707                render: Arc::new(|_| div().into_any()),
 4708                priority: 0,
 4709                render_in_minimap: true,
 4710            }],
 4711            Some(Autoscroll::fit()),
 4712            cx,
 4713        );
 4714        editor.change_selections(None, window, cx, |s| {
 4715            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4716        });
 4717        editor.move_line_down(&MoveLineDown, window, cx);
 4718    });
 4719}
 4720
 4721#[gpui::test]
 4722async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4723    init_test(cx, |_| {});
 4724
 4725    let mut cx = EditorTestContext::new(cx).await;
 4726    cx.set_state(
 4727        &"
 4728            ˇzero
 4729            one
 4730            two
 4731            three
 4732            four
 4733            five
 4734        "
 4735        .unindent(),
 4736    );
 4737
 4738    // Create a four-line block that replaces three lines of text.
 4739    cx.update_editor(|editor, window, cx| {
 4740        let snapshot = editor.snapshot(window, cx);
 4741        let snapshot = &snapshot.buffer_snapshot;
 4742        let placement = BlockPlacement::Replace(
 4743            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4744        );
 4745        editor.insert_blocks(
 4746            [BlockProperties {
 4747                placement,
 4748                height: Some(4),
 4749                style: BlockStyle::Sticky,
 4750                render: Arc::new(|_| gpui::div().into_any_element()),
 4751                priority: 0,
 4752                render_in_minimap: true,
 4753            }],
 4754            None,
 4755            cx,
 4756        );
 4757    });
 4758
 4759    // Move down so that the cursor touches the block.
 4760    cx.update_editor(|editor, window, cx| {
 4761        editor.move_down(&Default::default(), window, cx);
 4762    });
 4763    cx.assert_editor_state(
 4764        &"
 4765            zero
 4766            «one
 4767            two
 4768            threeˇ»
 4769            four
 4770            five
 4771        "
 4772        .unindent(),
 4773    );
 4774
 4775    // Move down past the block.
 4776    cx.update_editor(|editor, window, cx| {
 4777        editor.move_down(&Default::default(), window, cx);
 4778    });
 4779    cx.assert_editor_state(
 4780        &"
 4781            zero
 4782            one
 4783            two
 4784            three
 4785            ˇfour
 4786            five
 4787        "
 4788        .unindent(),
 4789    );
 4790}
 4791
 4792#[gpui::test]
 4793fn test_transpose(cx: &mut TestAppContext) {
 4794    init_test(cx, |_| {});
 4795
 4796    _ = cx.add_window(|window, cx| {
 4797        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4798        editor.set_style(EditorStyle::default(), window, cx);
 4799        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4800        editor.transpose(&Default::default(), window, cx);
 4801        assert_eq!(editor.text(cx), "bac");
 4802        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4803
 4804        editor.transpose(&Default::default(), window, cx);
 4805        assert_eq!(editor.text(cx), "bca");
 4806        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4807
 4808        editor.transpose(&Default::default(), window, cx);
 4809        assert_eq!(editor.text(cx), "bac");
 4810        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4811
 4812        editor
 4813    });
 4814
 4815    _ = cx.add_window(|window, cx| {
 4816        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4817        editor.set_style(EditorStyle::default(), window, cx);
 4818        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4819        editor.transpose(&Default::default(), window, cx);
 4820        assert_eq!(editor.text(cx), "acb\nde");
 4821        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4822
 4823        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4824        editor.transpose(&Default::default(), window, cx);
 4825        assert_eq!(editor.text(cx), "acbd\ne");
 4826        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4827
 4828        editor.transpose(&Default::default(), window, cx);
 4829        assert_eq!(editor.text(cx), "acbde\n");
 4830        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4831
 4832        editor.transpose(&Default::default(), window, cx);
 4833        assert_eq!(editor.text(cx), "acbd\ne");
 4834        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4835
 4836        editor
 4837    });
 4838
 4839    _ = cx.add_window(|window, cx| {
 4840        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4841        editor.set_style(EditorStyle::default(), window, cx);
 4842        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4843        editor.transpose(&Default::default(), window, cx);
 4844        assert_eq!(editor.text(cx), "bacd\ne");
 4845        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4846
 4847        editor.transpose(&Default::default(), window, cx);
 4848        assert_eq!(editor.text(cx), "bcade\n");
 4849        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4850
 4851        editor.transpose(&Default::default(), window, cx);
 4852        assert_eq!(editor.text(cx), "bcda\ne");
 4853        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4854
 4855        editor.transpose(&Default::default(), window, cx);
 4856        assert_eq!(editor.text(cx), "bcade\n");
 4857        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4858
 4859        editor.transpose(&Default::default(), window, cx);
 4860        assert_eq!(editor.text(cx), "bcaed\n");
 4861        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4862
 4863        editor
 4864    });
 4865
 4866    _ = cx.add_window(|window, cx| {
 4867        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4868        editor.set_style(EditorStyle::default(), window, cx);
 4869        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4870        editor.transpose(&Default::default(), window, cx);
 4871        assert_eq!(editor.text(cx), "🏀🍐✋");
 4872        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4873
 4874        editor.transpose(&Default::default(), window, cx);
 4875        assert_eq!(editor.text(cx), "🏀✋🍐");
 4876        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4877
 4878        editor.transpose(&Default::default(), window, cx);
 4879        assert_eq!(editor.text(cx), "🏀🍐✋");
 4880        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4881
 4882        editor
 4883    });
 4884}
 4885
 4886#[gpui::test]
 4887async fn test_rewrap(cx: &mut TestAppContext) {
 4888    init_test(cx, |settings| {
 4889        settings.languages.extend([
 4890            (
 4891                "Markdown".into(),
 4892                LanguageSettingsContent {
 4893                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4894                    ..Default::default()
 4895                },
 4896            ),
 4897            (
 4898                "Plain Text".into(),
 4899                LanguageSettingsContent {
 4900                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4901                    ..Default::default()
 4902                },
 4903            ),
 4904        ])
 4905    });
 4906
 4907    let mut cx = EditorTestContext::new(cx).await;
 4908
 4909    let language_with_c_comments = Arc::new(Language::new(
 4910        LanguageConfig {
 4911            line_comments: vec!["// ".into()],
 4912            ..LanguageConfig::default()
 4913        },
 4914        None,
 4915    ));
 4916    let language_with_pound_comments = Arc::new(Language::new(
 4917        LanguageConfig {
 4918            line_comments: vec!["# ".into()],
 4919            ..LanguageConfig::default()
 4920        },
 4921        None,
 4922    ));
 4923    let markdown_language = Arc::new(Language::new(
 4924        LanguageConfig {
 4925            name: "Markdown".into(),
 4926            ..LanguageConfig::default()
 4927        },
 4928        None,
 4929    ));
 4930    let language_with_doc_comments = Arc::new(Language::new(
 4931        LanguageConfig {
 4932            line_comments: vec!["// ".into(), "/// ".into()],
 4933            ..LanguageConfig::default()
 4934        },
 4935        Some(tree_sitter_rust::LANGUAGE.into()),
 4936    ));
 4937
 4938    let plaintext_language = Arc::new(Language::new(
 4939        LanguageConfig {
 4940            name: "Plain Text".into(),
 4941            ..LanguageConfig::default()
 4942        },
 4943        None,
 4944    ));
 4945
 4946    assert_rewrap(
 4947        indoc! {"
 4948            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4949        "},
 4950        indoc! {"
 4951            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4952            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4953            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4954            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4955            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4956            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4957            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4958            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4959            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4960            // porttitor id. Aliquam id accumsan eros.
 4961        "},
 4962        language_with_c_comments.clone(),
 4963        &mut cx,
 4964    );
 4965
 4966    // Test that rewrapping works inside of a selection
 4967    assert_rewrap(
 4968        indoc! {"
 4969            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
 4970        "},
 4971        indoc! {"
 4972            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4973            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4974            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4975            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4976            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4977            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4978            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4979            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4980            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4981            // porttitor id. Aliquam id accumsan eros.ˇ»
 4982        "},
 4983        language_with_c_comments.clone(),
 4984        &mut cx,
 4985    );
 4986
 4987    // Test that cursors that expand to the same region are collapsed.
 4988    assert_rewrap(
 4989        indoc! {"
 4990            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4991            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4992            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4993            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4994        "},
 4995        indoc! {"
 4996            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4997            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4998            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4999            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5000            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5001            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5002            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5003            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5004            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5005            // porttitor id. Aliquam id accumsan eros.
 5006        "},
 5007        language_with_c_comments.clone(),
 5008        &mut cx,
 5009    );
 5010
 5011    // Test that non-contiguous selections are treated separately.
 5012    assert_rewrap(
 5013        indoc! {"
 5014            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5015            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5016            //
 5017            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5018            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5019        "},
 5020        indoc! {"
 5021            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5022            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5023            // auctor, eu lacinia sapien scelerisque.
 5024            //
 5025            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5026            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5027            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5028            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5029            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5030            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5031            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5032        "},
 5033        language_with_c_comments.clone(),
 5034        &mut cx,
 5035    );
 5036
 5037    // Test that different comment prefixes are supported.
 5038    assert_rewrap(
 5039        indoc! {"
 5040            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5041        "},
 5042        indoc! {"
 5043            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5044            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5045            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5046            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5047            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5048            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5049            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5050            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5051            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5052            # accumsan eros.
 5053        "},
 5054        language_with_pound_comments.clone(),
 5055        &mut cx,
 5056    );
 5057
 5058    // Test that rewrapping is ignored outside of comments in most languages.
 5059    assert_rewrap(
 5060        indoc! {"
 5061            /// Adds two numbers.
 5062            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5063            fn add(a: u32, b: u32) -> u32 {
 5064                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5065            }
 5066        "},
 5067        indoc! {"
 5068            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5069            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5070            fn add(a: u32, b: u32) -> u32 {
 5071                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5072            }
 5073        "},
 5074        language_with_doc_comments.clone(),
 5075        &mut cx,
 5076    );
 5077
 5078    // Test that rewrapping works in Markdown and Plain Text languages.
 5079    assert_rewrap(
 5080        indoc! {"
 5081            # Hello
 5082
 5083            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5084        "},
 5085        indoc! {"
 5086            # Hello
 5087
 5088            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5089            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5090            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5091            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5092            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5093            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5094            Integer sit amet scelerisque nisi.
 5095        "},
 5096        markdown_language,
 5097        &mut cx,
 5098    );
 5099
 5100    assert_rewrap(
 5101        indoc! {"
 5102            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5103        "},
 5104        indoc! {"
 5105            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5106            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5107            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5108            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5109            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5110            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5111            Integer sit amet scelerisque nisi.
 5112        "},
 5113        plaintext_language.clone(),
 5114        &mut cx,
 5115    );
 5116
 5117    // Test rewrapping unaligned comments in a selection.
 5118    assert_rewrap(
 5119        indoc! {"
 5120            fn foo() {
 5121                if true {
 5122            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5123            // Praesent semper egestas tellus id dignissim.ˇ»
 5124                    do_something();
 5125                } else {
 5126                    //
 5127                }
 5128            }
 5129        "},
 5130        indoc! {"
 5131            fn foo() {
 5132                if true {
 5133            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5134                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5135                    // egestas tellus id dignissim.ˇ»
 5136                    do_something();
 5137                } else {
 5138                    //
 5139                }
 5140            }
 5141        "},
 5142        language_with_doc_comments.clone(),
 5143        &mut cx,
 5144    );
 5145
 5146    assert_rewrap(
 5147        indoc! {"
 5148            fn foo() {
 5149                if true {
 5150            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5151            // Praesent semper egestas tellus id dignissim.»
 5152                    do_something();
 5153                } else {
 5154                    //
 5155                }
 5156
 5157            }
 5158        "},
 5159        indoc! {"
 5160            fn foo() {
 5161                if true {
 5162            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5163                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5164                    // egestas tellus id dignissim.»
 5165                    do_something();
 5166                } else {
 5167                    //
 5168                }
 5169
 5170            }
 5171        "},
 5172        language_with_doc_comments.clone(),
 5173        &mut cx,
 5174    );
 5175
 5176    assert_rewrap(
 5177        indoc! {"
 5178            «ˇone one one one one one one one one one one one one one one one one one one one one one one one one
 5179
 5180            two»
 5181
 5182            three
 5183
 5184            «ˇ\t
 5185
 5186            four four four four four four four four four four four four four four four four four four four four»
 5187
 5188            «ˇfive five five five five five five five five five five five five five five five five five five five
 5189            \t»
 5190            six six six six six six six six six six six six six six six six six six six six six six six six six
 5191        "},
 5192        indoc! {"
 5193            «ˇone one one one one one one one one one one one one one one one one one one one
 5194            one one one one one
 5195
 5196            two»
 5197
 5198            three
 5199
 5200            «ˇ\t
 5201
 5202            four four four four four four four four four four four four four four four four
 5203            four four four four»
 5204
 5205            «ˇfive five five five five five five five five five five five five five five five
 5206            five five five five
 5207            \t»
 5208            six six six six six six six six six six six six six six six six six six six six six six six six six
 5209        "},
 5210        plaintext_language.clone(),
 5211        &mut cx,
 5212    );
 5213
 5214    assert_rewrap(
 5215        indoc! {"
 5216            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5217            //ˇ
 5218            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5219            //ˇ short short short
 5220            int main(void) {
 5221                return 17;
 5222            }
 5223        "},
 5224        indoc! {"
 5225            //ˇ long long long long long long long long long long long long long long long
 5226            // long long long long long long long long long long long long long
 5227            //ˇ
 5228            //ˇ long long long long long long long long long long long long long long long
 5229            //ˇ long long long long long long long long long long long long long short short
 5230            // short
 5231            int main(void) {
 5232                return 17;
 5233            }
 5234        "},
 5235        language_with_c_comments,
 5236        &mut cx,
 5237    );
 5238
 5239    #[track_caller]
 5240    fn assert_rewrap(
 5241        unwrapped_text: &str,
 5242        wrapped_text: &str,
 5243        language: Arc<Language>,
 5244        cx: &mut EditorTestContext,
 5245    ) {
 5246        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5247        cx.set_state(unwrapped_text);
 5248        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5249        cx.assert_editor_state(wrapped_text);
 5250    }
 5251}
 5252
 5253#[gpui::test]
 5254async fn test_hard_wrap(cx: &mut TestAppContext) {
 5255    init_test(cx, |_| {});
 5256    let mut cx = EditorTestContext::new(cx).await;
 5257
 5258    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5259    cx.update_editor(|editor, _, cx| {
 5260        editor.set_hard_wrap(Some(14), cx);
 5261    });
 5262
 5263    cx.set_state(indoc!(
 5264        "
 5265        one two three ˇ
 5266        "
 5267    ));
 5268    cx.simulate_input("four");
 5269    cx.run_until_parked();
 5270
 5271    cx.assert_editor_state(indoc!(
 5272        "
 5273        one two three
 5274        fourˇ
 5275        "
 5276    ));
 5277
 5278    cx.update_editor(|editor, window, cx| {
 5279        editor.newline(&Default::default(), window, cx);
 5280    });
 5281    cx.run_until_parked();
 5282    cx.assert_editor_state(indoc!(
 5283        "
 5284        one two three
 5285        four
 5286        ˇ
 5287        "
 5288    ));
 5289
 5290    cx.simulate_input("five");
 5291    cx.run_until_parked();
 5292    cx.assert_editor_state(indoc!(
 5293        "
 5294        one two three
 5295        four
 5296        fiveˇ
 5297        "
 5298    ));
 5299
 5300    cx.update_editor(|editor, window, cx| {
 5301        editor.newline(&Default::default(), window, cx);
 5302    });
 5303    cx.run_until_parked();
 5304    cx.simulate_input("# ");
 5305    cx.run_until_parked();
 5306    cx.assert_editor_state(indoc!(
 5307        "
 5308        one two three
 5309        four
 5310        five
 5311        # ˇ
 5312        "
 5313    ));
 5314
 5315    cx.update_editor(|editor, window, cx| {
 5316        editor.newline(&Default::default(), window, cx);
 5317    });
 5318    cx.run_until_parked();
 5319    cx.assert_editor_state(indoc!(
 5320        "
 5321        one two three
 5322        four
 5323        five
 5324        #\x20
 5325 5326        "
 5327    ));
 5328
 5329    cx.simulate_input(" 6");
 5330    cx.run_until_parked();
 5331    cx.assert_editor_state(indoc!(
 5332        "
 5333        one two three
 5334        four
 5335        five
 5336        #
 5337        # 6ˇ
 5338        "
 5339    ));
 5340}
 5341
 5342#[gpui::test]
 5343async fn test_clipboard(cx: &mut TestAppContext) {
 5344    init_test(cx, |_| {});
 5345
 5346    let mut cx = EditorTestContext::new(cx).await;
 5347
 5348    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5349    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5350    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5351
 5352    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5353    cx.set_state("two ˇfour ˇsix ˇ");
 5354    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5355    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5356
 5357    // Paste again but with only two cursors. Since the number of cursors doesn't
 5358    // match the number of slices in the clipboard, the entire clipboard text
 5359    // is pasted at each cursor.
 5360    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5361    cx.update_editor(|e, window, cx| {
 5362        e.handle_input("( ", window, cx);
 5363        e.paste(&Paste, window, cx);
 5364        e.handle_input(") ", window, cx);
 5365    });
 5366    cx.assert_editor_state(
 5367        &([
 5368            "( one✅ ",
 5369            "three ",
 5370            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5371            "three ",
 5372            "five ) ˇ",
 5373        ]
 5374        .join("\n")),
 5375    );
 5376
 5377    // Cut with three selections, one of which is full-line.
 5378    cx.set_state(indoc! {"
 5379        1«2ˇ»3
 5380        4ˇ567
 5381        «8ˇ»9"});
 5382    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5383    cx.assert_editor_state(indoc! {"
 5384        1ˇ3
 5385        ˇ9"});
 5386
 5387    // Paste with three selections, noticing how the copied selection that was full-line
 5388    // gets inserted before the second cursor.
 5389    cx.set_state(indoc! {"
 5390        1ˇ3
 5391 5392        «oˇ»ne"});
 5393    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5394    cx.assert_editor_state(indoc! {"
 5395        12ˇ3
 5396        4567
 5397 5398        8ˇne"});
 5399
 5400    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5401    cx.set_state(indoc! {"
 5402        The quick brown
 5403        fox juˇmps over
 5404        the lazy dog"});
 5405    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5406    assert_eq!(
 5407        cx.read_from_clipboard()
 5408            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5409        Some("fox jumps over\n".to_string())
 5410    );
 5411
 5412    // Paste with three selections, noticing how the copied full-line selection is inserted
 5413    // before the empty selections but replaces the selection that is non-empty.
 5414    cx.set_state(indoc! {"
 5415        Tˇhe quick brown
 5416        «foˇ»x jumps over
 5417        tˇhe lazy dog"});
 5418    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5419    cx.assert_editor_state(indoc! {"
 5420        fox jumps over
 5421        Tˇhe quick brown
 5422        fox jumps over
 5423        ˇx jumps over
 5424        fox jumps over
 5425        tˇhe lazy dog"});
 5426}
 5427
 5428#[gpui::test]
 5429async fn test_copy_trim(cx: &mut TestAppContext) {
 5430    init_test(cx, |_| {});
 5431
 5432    let mut cx = EditorTestContext::new(cx).await;
 5433    cx.set_state(
 5434        r#"            «for selection in selections.iter() {
 5435            let mut start = selection.start;
 5436            let mut end = selection.end;
 5437            let is_entire_line = selection.is_empty();
 5438            if is_entire_line {
 5439                start = Point::new(start.row, 0);ˇ»
 5440                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5441            }
 5442        "#,
 5443    );
 5444    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5445    assert_eq!(
 5446        cx.read_from_clipboard()
 5447            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5448        Some(
 5449            "for selection in selections.iter() {
 5450            let mut start = selection.start;
 5451            let mut end = selection.end;
 5452            let is_entire_line = selection.is_empty();
 5453            if is_entire_line {
 5454                start = Point::new(start.row, 0);"
 5455                .to_string()
 5456        ),
 5457        "Regular copying preserves all indentation selected",
 5458    );
 5459    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5460    assert_eq!(
 5461        cx.read_from_clipboard()
 5462            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5463        Some(
 5464            "for selection in selections.iter() {
 5465let mut start = selection.start;
 5466let mut end = selection.end;
 5467let is_entire_line = selection.is_empty();
 5468if is_entire_line {
 5469    start = Point::new(start.row, 0);"
 5470                .to_string()
 5471        ),
 5472        "Copying with stripping should strip all leading whitespaces"
 5473    );
 5474
 5475    cx.set_state(
 5476        r#"       «     for selection in selections.iter() {
 5477            let mut start = selection.start;
 5478            let mut end = selection.end;
 5479            let is_entire_line = selection.is_empty();
 5480            if is_entire_line {
 5481                start = Point::new(start.row, 0);ˇ»
 5482                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5483            }
 5484        "#,
 5485    );
 5486    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5487    assert_eq!(
 5488        cx.read_from_clipboard()
 5489            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5490        Some(
 5491            "     for selection in selections.iter() {
 5492            let mut start = selection.start;
 5493            let mut end = selection.end;
 5494            let is_entire_line = selection.is_empty();
 5495            if is_entire_line {
 5496                start = Point::new(start.row, 0);"
 5497                .to_string()
 5498        ),
 5499        "Regular copying preserves all indentation selected",
 5500    );
 5501    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5502    assert_eq!(
 5503        cx.read_from_clipboard()
 5504            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5505        Some(
 5506            "for selection in selections.iter() {
 5507let mut start = selection.start;
 5508let mut end = selection.end;
 5509let is_entire_line = selection.is_empty();
 5510if is_entire_line {
 5511    start = Point::new(start.row, 0);"
 5512                .to_string()
 5513        ),
 5514        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5515    );
 5516
 5517    cx.set_state(
 5518        r#"       «ˇ     for selection in selections.iter() {
 5519            let mut start = selection.start;
 5520            let mut end = selection.end;
 5521            let is_entire_line = selection.is_empty();
 5522            if is_entire_line {
 5523                start = Point::new(start.row, 0);»
 5524                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5525            }
 5526        "#,
 5527    );
 5528    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5529    assert_eq!(
 5530        cx.read_from_clipboard()
 5531            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5532        Some(
 5533            "     for selection in selections.iter() {
 5534            let mut start = selection.start;
 5535            let mut end = selection.end;
 5536            let is_entire_line = selection.is_empty();
 5537            if is_entire_line {
 5538                start = Point::new(start.row, 0);"
 5539                .to_string()
 5540        ),
 5541        "Regular copying for reverse selection works the same",
 5542    );
 5543    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5544    assert_eq!(
 5545        cx.read_from_clipboard()
 5546            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5547        Some(
 5548            "for selection in selections.iter() {
 5549let mut start = selection.start;
 5550let mut end = selection.end;
 5551let is_entire_line = selection.is_empty();
 5552if is_entire_line {
 5553    start = Point::new(start.row, 0);"
 5554                .to_string()
 5555        ),
 5556        "Copying with stripping for reverse selection works the same"
 5557    );
 5558
 5559    cx.set_state(
 5560        r#"            for selection «in selections.iter() {
 5561            let mut start = selection.start;
 5562            let mut end = selection.end;
 5563            let is_entire_line = selection.is_empty();
 5564            if is_entire_line {
 5565                start = Point::new(start.row, 0);ˇ»
 5566                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5567            }
 5568        "#,
 5569    );
 5570    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5571    assert_eq!(
 5572        cx.read_from_clipboard()
 5573            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5574        Some(
 5575            "in selections.iter() {
 5576            let mut start = selection.start;
 5577            let mut end = selection.end;
 5578            let is_entire_line = selection.is_empty();
 5579            if is_entire_line {
 5580                start = Point::new(start.row, 0);"
 5581                .to_string()
 5582        ),
 5583        "When selecting past the indent, the copying works as usual",
 5584    );
 5585    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5586    assert_eq!(
 5587        cx.read_from_clipboard()
 5588            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5589        Some(
 5590            "in selections.iter() {
 5591            let mut start = selection.start;
 5592            let mut end = selection.end;
 5593            let is_entire_line = selection.is_empty();
 5594            if is_entire_line {
 5595                start = Point::new(start.row, 0);"
 5596                .to_string()
 5597        ),
 5598        "When selecting past the indent, nothing is trimmed"
 5599    );
 5600
 5601    cx.set_state(
 5602        r#"            «for selection in selections.iter() {
 5603            let mut start = selection.start;
 5604
 5605            let mut end = selection.end;
 5606            let is_entire_line = selection.is_empty();
 5607            if is_entire_line {
 5608                start = Point::new(start.row, 0);
 5609ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5610            }
 5611        "#,
 5612    );
 5613    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5614    assert_eq!(
 5615        cx.read_from_clipboard()
 5616            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5617        Some(
 5618            "for selection in selections.iter() {
 5619let mut start = selection.start;
 5620
 5621let mut end = selection.end;
 5622let is_entire_line = selection.is_empty();
 5623if is_entire_line {
 5624    start = Point::new(start.row, 0);
 5625"
 5626            .to_string()
 5627        ),
 5628        "Copying with stripping should ignore empty lines"
 5629    );
 5630}
 5631
 5632#[gpui::test]
 5633async fn test_paste_multiline(cx: &mut TestAppContext) {
 5634    init_test(cx, |_| {});
 5635
 5636    let mut cx = EditorTestContext::new(cx).await;
 5637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5638
 5639    // Cut an indented block, without the leading whitespace.
 5640    cx.set_state(indoc! {"
 5641        const a: B = (
 5642            c(),
 5643            «d(
 5644                e,
 5645                f
 5646            )ˇ»
 5647        );
 5648    "});
 5649    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5650    cx.assert_editor_state(indoc! {"
 5651        const a: B = (
 5652            c(),
 5653            ˇ
 5654        );
 5655    "});
 5656
 5657    // Paste it at the same position.
 5658    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5659    cx.assert_editor_state(indoc! {"
 5660        const a: B = (
 5661            c(),
 5662            d(
 5663                e,
 5664                f
 5665 5666        );
 5667    "});
 5668
 5669    // Paste it at a line with a lower indent level.
 5670    cx.set_state(indoc! {"
 5671        ˇ
 5672        const a: B = (
 5673            c(),
 5674        );
 5675    "});
 5676    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5677    cx.assert_editor_state(indoc! {"
 5678        d(
 5679            e,
 5680            f
 5681 5682        const a: B = (
 5683            c(),
 5684        );
 5685    "});
 5686
 5687    // Cut an indented block, with the leading whitespace.
 5688    cx.set_state(indoc! {"
 5689        const a: B = (
 5690            c(),
 5691        «    d(
 5692                e,
 5693                f
 5694            )
 5695        ˇ»);
 5696    "});
 5697    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5698    cx.assert_editor_state(indoc! {"
 5699        const a: B = (
 5700            c(),
 5701        ˇ);
 5702    "});
 5703
 5704    // Paste it at the same position.
 5705    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5706    cx.assert_editor_state(indoc! {"
 5707        const a: B = (
 5708            c(),
 5709            d(
 5710                e,
 5711                f
 5712            )
 5713        ˇ);
 5714    "});
 5715
 5716    // Paste it at a line with a higher indent level.
 5717    cx.set_state(indoc! {"
 5718        const a: B = (
 5719            c(),
 5720            d(
 5721                e,
 5722 5723            )
 5724        );
 5725    "});
 5726    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5727    cx.assert_editor_state(indoc! {"
 5728        const a: B = (
 5729            c(),
 5730            d(
 5731                e,
 5732                f    d(
 5733                    e,
 5734                    f
 5735                )
 5736        ˇ
 5737            )
 5738        );
 5739    "});
 5740
 5741    // Copy an indented block, starting mid-line
 5742    cx.set_state(indoc! {"
 5743        const a: B = (
 5744            c(),
 5745            somethin«g(
 5746                e,
 5747                f
 5748            )ˇ»
 5749        );
 5750    "});
 5751    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5752
 5753    // Paste it on a line with a lower indent level
 5754    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5755    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5756    cx.assert_editor_state(indoc! {"
 5757        const a: B = (
 5758            c(),
 5759            something(
 5760                e,
 5761                f
 5762            )
 5763        );
 5764        g(
 5765            e,
 5766            f
 5767"});
 5768}
 5769
 5770#[gpui::test]
 5771async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5772    init_test(cx, |_| {});
 5773
 5774    cx.write_to_clipboard(ClipboardItem::new_string(
 5775        "    d(\n        e\n    );\n".into(),
 5776    ));
 5777
 5778    let mut cx = EditorTestContext::new(cx).await;
 5779    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5780
 5781    cx.set_state(indoc! {"
 5782        fn a() {
 5783            b();
 5784            if c() {
 5785                ˇ
 5786            }
 5787        }
 5788    "});
 5789
 5790    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5791    cx.assert_editor_state(indoc! {"
 5792        fn a() {
 5793            b();
 5794            if c() {
 5795                d(
 5796                    e
 5797                );
 5798        ˇ
 5799            }
 5800        }
 5801    "});
 5802
 5803    cx.set_state(indoc! {"
 5804        fn a() {
 5805            b();
 5806            ˇ
 5807        }
 5808    "});
 5809
 5810    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5811    cx.assert_editor_state(indoc! {"
 5812        fn a() {
 5813            b();
 5814            d(
 5815                e
 5816            );
 5817        ˇ
 5818        }
 5819    "});
 5820}
 5821
 5822#[gpui::test]
 5823fn test_select_all(cx: &mut TestAppContext) {
 5824    init_test(cx, |_| {});
 5825
 5826    let editor = cx.add_window(|window, cx| {
 5827        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5828        build_editor(buffer, window, cx)
 5829    });
 5830    _ = editor.update(cx, |editor, window, cx| {
 5831        editor.select_all(&SelectAll, window, cx);
 5832        assert_eq!(
 5833            editor.selections.display_ranges(cx),
 5834            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5835        );
 5836    });
 5837}
 5838
 5839#[gpui::test]
 5840fn test_select_line(cx: &mut TestAppContext) {
 5841    init_test(cx, |_| {});
 5842
 5843    let editor = cx.add_window(|window, cx| {
 5844        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5845        build_editor(buffer, window, cx)
 5846    });
 5847    _ = editor.update(cx, |editor, window, cx| {
 5848        editor.change_selections(None, window, cx, |s| {
 5849            s.select_display_ranges([
 5850                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5851                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5852                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5853                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5854            ])
 5855        });
 5856        editor.select_line(&SelectLine, window, cx);
 5857        assert_eq!(
 5858            editor.selections.display_ranges(cx),
 5859            vec![
 5860                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5861                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5862            ]
 5863        );
 5864    });
 5865
 5866    _ = editor.update(cx, |editor, window, cx| {
 5867        editor.select_line(&SelectLine, window, cx);
 5868        assert_eq!(
 5869            editor.selections.display_ranges(cx),
 5870            vec![
 5871                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5872                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5873            ]
 5874        );
 5875    });
 5876
 5877    _ = editor.update(cx, |editor, window, cx| {
 5878        editor.select_line(&SelectLine, window, cx);
 5879        assert_eq!(
 5880            editor.selections.display_ranges(cx),
 5881            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5882        );
 5883    });
 5884}
 5885
 5886#[gpui::test]
 5887async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5888    init_test(cx, |_| {});
 5889    let mut cx = EditorTestContext::new(cx).await;
 5890
 5891    #[track_caller]
 5892    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5893        cx.set_state(initial_state);
 5894        cx.update_editor(|e, window, cx| {
 5895            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5896        });
 5897        cx.assert_editor_state(expected_state);
 5898    }
 5899
 5900    // Selection starts and ends at the middle of lines, left-to-right
 5901    test(
 5902        &mut cx,
 5903        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5904        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5905    );
 5906    // Same thing, right-to-left
 5907    test(
 5908        &mut cx,
 5909        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5910        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5911    );
 5912
 5913    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5914    test(
 5915        &mut cx,
 5916        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5917        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5918    );
 5919    // Same thing, right-to-left
 5920    test(
 5921        &mut cx,
 5922        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5923        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5924    );
 5925
 5926    // Whole buffer, left-to-right, last line ends with newline
 5927    test(
 5928        &mut cx,
 5929        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5930        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5931    );
 5932    // Same thing, right-to-left
 5933    test(
 5934        &mut cx,
 5935        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5936        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5937    );
 5938
 5939    // Starts at the end of a line, ends at the start of another
 5940    test(
 5941        &mut cx,
 5942        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5943        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5944    );
 5945}
 5946
 5947#[gpui::test]
 5948async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5949    init_test(cx, |_| {});
 5950
 5951    let editor = cx.add_window(|window, cx| {
 5952        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5953        build_editor(buffer, window, cx)
 5954    });
 5955
 5956    // setup
 5957    _ = editor.update(cx, |editor, window, cx| {
 5958        editor.fold_creases(
 5959            vec![
 5960                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5961                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5962                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5963            ],
 5964            true,
 5965            window,
 5966            cx,
 5967        );
 5968        assert_eq!(
 5969            editor.display_text(cx),
 5970            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5971        );
 5972    });
 5973
 5974    _ = editor.update(cx, |editor, window, cx| {
 5975        editor.change_selections(None, window, cx, |s| {
 5976            s.select_display_ranges([
 5977                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5978                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5979                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5980                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5981            ])
 5982        });
 5983        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5984        assert_eq!(
 5985            editor.display_text(cx),
 5986            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5987        );
 5988    });
 5989    EditorTestContext::for_editor(editor, cx)
 5990        .await
 5991        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5992
 5993    _ = editor.update(cx, |editor, window, cx| {
 5994        editor.change_selections(None, window, cx, |s| {
 5995            s.select_display_ranges([
 5996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5997            ])
 5998        });
 5999        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6000        assert_eq!(
 6001            editor.display_text(cx),
 6002            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6003        );
 6004        assert_eq!(
 6005            editor.selections.display_ranges(cx),
 6006            [
 6007                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6008                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6009                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6010                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6011                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6012                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6013                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6014            ]
 6015        );
 6016    });
 6017    EditorTestContext::for_editor(editor, cx)
 6018        .await
 6019        .assert_editor_state(
 6020            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6021        );
 6022}
 6023
 6024#[gpui::test]
 6025async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6026    init_test(cx, |_| {});
 6027
 6028    let mut cx = EditorTestContext::new(cx).await;
 6029
 6030    cx.set_state(indoc!(
 6031        r#"abc
 6032           defˇghi
 6033
 6034           jk
 6035           nlmo
 6036           "#
 6037    ));
 6038
 6039    cx.update_editor(|editor, window, cx| {
 6040        editor.add_selection_above(&Default::default(), window, cx);
 6041    });
 6042
 6043    cx.assert_editor_state(indoc!(
 6044        r#"abcˇ
 6045           defˇghi
 6046
 6047           jk
 6048           nlmo
 6049           "#
 6050    ));
 6051
 6052    cx.update_editor(|editor, window, cx| {
 6053        editor.add_selection_above(&Default::default(), window, cx);
 6054    });
 6055
 6056    cx.assert_editor_state(indoc!(
 6057        r#"abcˇ
 6058            defˇghi
 6059
 6060            jk
 6061            nlmo
 6062            "#
 6063    ));
 6064
 6065    cx.update_editor(|editor, window, cx| {
 6066        editor.add_selection_below(&Default::default(), window, cx);
 6067    });
 6068
 6069    cx.assert_editor_state(indoc!(
 6070        r#"abc
 6071           defˇghi
 6072
 6073           jk
 6074           nlmo
 6075           "#
 6076    ));
 6077
 6078    cx.update_editor(|editor, window, cx| {
 6079        editor.undo_selection(&Default::default(), window, cx);
 6080    });
 6081
 6082    cx.assert_editor_state(indoc!(
 6083        r#"abcˇ
 6084           defˇghi
 6085
 6086           jk
 6087           nlmo
 6088           "#
 6089    ));
 6090
 6091    cx.update_editor(|editor, window, cx| {
 6092        editor.redo_selection(&Default::default(), window, cx);
 6093    });
 6094
 6095    cx.assert_editor_state(indoc!(
 6096        r#"abc
 6097           defˇghi
 6098
 6099           jk
 6100           nlmo
 6101           "#
 6102    ));
 6103
 6104    cx.update_editor(|editor, window, cx| {
 6105        editor.add_selection_below(&Default::default(), window, cx);
 6106    });
 6107
 6108    cx.assert_editor_state(indoc!(
 6109        r#"abc
 6110           defˇghi
 6111           ˇ
 6112           jk
 6113           nlmo
 6114           "#
 6115    ));
 6116
 6117    cx.update_editor(|editor, window, cx| {
 6118        editor.add_selection_below(&Default::default(), window, cx);
 6119    });
 6120
 6121    cx.assert_editor_state(indoc!(
 6122        r#"abc
 6123           defˇghi
 6124           ˇ
 6125           jkˇ
 6126           nlmo
 6127           "#
 6128    ));
 6129
 6130    cx.update_editor(|editor, window, cx| {
 6131        editor.add_selection_below(&Default::default(), window, cx);
 6132    });
 6133
 6134    cx.assert_editor_state(indoc!(
 6135        r#"abc
 6136           defˇghi
 6137           ˇ
 6138           jkˇ
 6139           nlmˇo
 6140           "#
 6141    ));
 6142
 6143    cx.update_editor(|editor, window, cx| {
 6144        editor.add_selection_below(&Default::default(), window, cx);
 6145    });
 6146
 6147    cx.assert_editor_state(indoc!(
 6148        r#"abc
 6149           defˇghi
 6150           ˇ
 6151           jkˇ
 6152           nlmˇo
 6153           ˇ"#
 6154    ));
 6155
 6156    // change selections
 6157    cx.set_state(indoc!(
 6158        r#"abc
 6159           def«ˇg»hi
 6160
 6161           jk
 6162           nlmo
 6163           "#
 6164    ));
 6165
 6166    cx.update_editor(|editor, window, cx| {
 6167        editor.add_selection_below(&Default::default(), window, cx);
 6168    });
 6169
 6170    cx.assert_editor_state(indoc!(
 6171        r#"abc
 6172           def«ˇg»hi
 6173
 6174           jk
 6175           nlm«ˇo»
 6176           "#
 6177    ));
 6178
 6179    cx.update_editor(|editor, window, cx| {
 6180        editor.add_selection_below(&Default::default(), window, cx);
 6181    });
 6182
 6183    cx.assert_editor_state(indoc!(
 6184        r#"abc
 6185           def«ˇg»hi
 6186
 6187           jk
 6188           nlm«ˇo»
 6189           "#
 6190    ));
 6191
 6192    cx.update_editor(|editor, window, cx| {
 6193        editor.add_selection_above(&Default::default(), window, cx);
 6194    });
 6195
 6196    cx.assert_editor_state(indoc!(
 6197        r#"abc
 6198           def«ˇg»hi
 6199
 6200           jk
 6201           nlmo
 6202           "#
 6203    ));
 6204
 6205    cx.update_editor(|editor, window, cx| {
 6206        editor.add_selection_above(&Default::default(), window, cx);
 6207    });
 6208
 6209    cx.assert_editor_state(indoc!(
 6210        r#"abc
 6211           def«ˇg»hi
 6212
 6213           jk
 6214           nlmo
 6215           "#
 6216    ));
 6217
 6218    // Change selections again
 6219    cx.set_state(indoc!(
 6220        r#"a«bc
 6221           defgˇ»hi
 6222
 6223           jk
 6224           nlmo
 6225           "#
 6226    ));
 6227
 6228    cx.update_editor(|editor, window, cx| {
 6229        editor.add_selection_below(&Default::default(), window, cx);
 6230    });
 6231
 6232    cx.assert_editor_state(indoc!(
 6233        r#"a«bcˇ»
 6234           d«efgˇ»hi
 6235
 6236           j«kˇ»
 6237           nlmo
 6238           "#
 6239    ));
 6240
 6241    cx.update_editor(|editor, window, cx| {
 6242        editor.add_selection_below(&Default::default(), window, cx);
 6243    });
 6244    cx.assert_editor_state(indoc!(
 6245        r#"a«bcˇ»
 6246           d«efgˇ»hi
 6247
 6248           j«kˇ»
 6249           n«lmoˇ»
 6250           "#
 6251    ));
 6252    cx.update_editor(|editor, window, cx| {
 6253        editor.add_selection_above(&Default::default(), window, cx);
 6254    });
 6255
 6256    cx.assert_editor_state(indoc!(
 6257        r#"a«bcˇ»
 6258           d«efgˇ»hi
 6259
 6260           j«kˇ»
 6261           nlmo
 6262           "#
 6263    ));
 6264
 6265    // Change selections again
 6266    cx.set_state(indoc!(
 6267        r#"abc
 6268           d«ˇefghi
 6269
 6270           jk
 6271           nlm»o
 6272           "#
 6273    ));
 6274
 6275    cx.update_editor(|editor, window, cx| {
 6276        editor.add_selection_above(&Default::default(), window, cx);
 6277    });
 6278
 6279    cx.assert_editor_state(indoc!(
 6280        r#"a«ˇbc»
 6281           d«ˇef»ghi
 6282
 6283           j«ˇk»
 6284           n«ˇlm»o
 6285           "#
 6286    ));
 6287
 6288    cx.update_editor(|editor, window, cx| {
 6289        editor.add_selection_below(&Default::default(), window, cx);
 6290    });
 6291
 6292    cx.assert_editor_state(indoc!(
 6293        r#"abc
 6294           d«ˇef»ghi
 6295
 6296           j«ˇk»
 6297           n«ˇlm»o
 6298           "#
 6299    ));
 6300}
 6301
 6302#[gpui::test]
 6303async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6304    init_test(cx, |_| {});
 6305    let mut cx = EditorTestContext::new(cx).await;
 6306
 6307    cx.set_state(indoc!(
 6308        r#"line onˇe
 6309           liˇne two
 6310           line three
 6311           line four"#
 6312    ));
 6313
 6314    cx.update_editor(|editor, window, cx| {
 6315        editor.add_selection_below(&Default::default(), window, cx);
 6316    });
 6317
 6318    // test multiple cursors expand in the same direction
 6319    cx.assert_editor_state(indoc!(
 6320        r#"line onˇe
 6321           liˇne twˇo
 6322           liˇne three
 6323           line four"#
 6324    ));
 6325
 6326    cx.update_editor(|editor, window, cx| {
 6327        editor.add_selection_below(&Default::default(), window, cx);
 6328    });
 6329
 6330    cx.update_editor(|editor, window, cx| {
 6331        editor.add_selection_below(&Default::default(), window, cx);
 6332    });
 6333
 6334    // test multiple cursors expand below overflow
 6335    cx.assert_editor_state(indoc!(
 6336        r#"line onˇe
 6337           liˇne twˇo
 6338           liˇne thˇree
 6339           liˇne foˇur"#
 6340    ));
 6341
 6342    cx.update_editor(|editor, window, cx| {
 6343        editor.add_selection_above(&Default::default(), window, cx);
 6344    });
 6345
 6346    // test multiple cursors retrieves back correctly
 6347    cx.assert_editor_state(indoc!(
 6348        r#"line onˇe
 6349           liˇne twˇo
 6350           liˇne thˇree
 6351           line four"#
 6352    ));
 6353
 6354    cx.update_editor(|editor, window, cx| {
 6355        editor.add_selection_above(&Default::default(), window, cx);
 6356    });
 6357
 6358    cx.update_editor(|editor, window, cx| {
 6359        editor.add_selection_above(&Default::default(), window, cx);
 6360    });
 6361
 6362    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6363    cx.assert_editor_state(indoc!(
 6364        r#"liˇne onˇe
 6365           liˇne two
 6366           line three
 6367           line four"#
 6368    ));
 6369
 6370    cx.update_editor(|editor, window, cx| {
 6371        editor.undo_selection(&Default::default(), window, cx);
 6372    });
 6373
 6374    // test undo
 6375    cx.assert_editor_state(indoc!(
 6376        r#"line onˇe
 6377           liˇne twˇo
 6378           line three
 6379           line four"#
 6380    ));
 6381
 6382    cx.update_editor(|editor, window, cx| {
 6383        editor.redo_selection(&Default::default(), window, cx);
 6384    });
 6385
 6386    // test redo
 6387    cx.assert_editor_state(indoc!(
 6388        r#"liˇne onˇe
 6389           liˇne two
 6390           line three
 6391           line four"#
 6392    ));
 6393
 6394    cx.set_state(indoc!(
 6395        r#"abcd
 6396           ef«ghˇ»
 6397           ijkl
 6398           «mˇ»nop"#
 6399    ));
 6400
 6401    cx.update_editor(|editor, window, cx| {
 6402        editor.add_selection_above(&Default::default(), window, cx);
 6403    });
 6404
 6405    // test multiple selections expand in the same direction
 6406    cx.assert_editor_state(indoc!(
 6407        r#"ab«cdˇ»
 6408           ef«ghˇ»
 6409           «iˇ»jkl
 6410           «mˇ»nop"#
 6411    ));
 6412
 6413    cx.update_editor(|editor, window, cx| {
 6414        editor.add_selection_above(&Default::default(), window, cx);
 6415    });
 6416
 6417    // test multiple selection upward overflow
 6418    cx.assert_editor_state(indoc!(
 6419        r#"ab«cdˇ»
 6420           «eˇ»f«ghˇ»
 6421           «iˇ»jkl
 6422           «mˇ»nop"#
 6423    ));
 6424
 6425    cx.update_editor(|editor, window, cx| {
 6426        editor.add_selection_below(&Default::default(), window, cx);
 6427    });
 6428
 6429    // test multiple selection retrieves back correctly
 6430    cx.assert_editor_state(indoc!(
 6431        r#"abcd
 6432           ef«ghˇ»
 6433           «iˇ»jkl
 6434           «mˇ»nop"#
 6435    ));
 6436
 6437    cx.update_editor(|editor, window, cx| {
 6438        editor.add_selection_below(&Default::default(), window, cx);
 6439    });
 6440
 6441    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6442    cx.assert_editor_state(indoc!(
 6443        r#"abcd
 6444           ef«ghˇ»
 6445           ij«klˇ»
 6446           «mˇ»nop"#
 6447    ));
 6448
 6449    cx.update_editor(|editor, window, cx| {
 6450        editor.undo_selection(&Default::default(), window, cx);
 6451    });
 6452
 6453    // test undo
 6454    cx.assert_editor_state(indoc!(
 6455        r#"abcd
 6456           ef«ghˇ»
 6457           «iˇ»jkl
 6458           «mˇ»nop"#
 6459    ));
 6460
 6461    cx.update_editor(|editor, window, cx| {
 6462        editor.redo_selection(&Default::default(), window, cx);
 6463    });
 6464
 6465    // test redo
 6466    cx.assert_editor_state(indoc!(
 6467        r#"abcd
 6468           ef«ghˇ»
 6469           ij«klˇ»
 6470           «mˇ»nop"#
 6471    ));
 6472}
 6473
 6474#[gpui::test]
 6475async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6476    init_test(cx, |_| {});
 6477    let mut cx = EditorTestContext::new(cx).await;
 6478
 6479    cx.set_state(indoc!(
 6480        r#"line onˇe
 6481           liˇne two
 6482           line three
 6483           line four"#
 6484    ));
 6485
 6486    cx.update_editor(|editor, window, cx| {
 6487        editor.add_selection_below(&Default::default(), window, cx);
 6488        editor.add_selection_below(&Default::default(), window, cx);
 6489        editor.add_selection_below(&Default::default(), window, cx);
 6490    });
 6491
 6492    // initial state with two multi cursor groups
 6493    cx.assert_editor_state(indoc!(
 6494        r#"line onˇe
 6495           liˇne twˇo
 6496           liˇne thˇree
 6497           liˇne foˇur"#
 6498    ));
 6499
 6500    // add single cursor in middle - simulate opt click
 6501    cx.update_editor(|editor, window, cx| {
 6502        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6503        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6504        editor.end_selection(window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"line onˇe
 6509           liˇne twˇo
 6510           liˇneˇ thˇree
 6511           liˇne foˇur"#
 6512    ));
 6513
 6514    cx.update_editor(|editor, window, cx| {
 6515        editor.add_selection_above(&Default::default(), window, cx);
 6516    });
 6517
 6518    // test new added selection expands above and existing selection shrinks
 6519    cx.assert_editor_state(indoc!(
 6520        r#"line onˇe
 6521           liˇneˇ twˇo
 6522           liˇneˇ thˇree
 6523           line four"#
 6524    ));
 6525
 6526    cx.update_editor(|editor, window, cx| {
 6527        editor.add_selection_above(&Default::default(), window, cx);
 6528    });
 6529
 6530    // test new added selection expands above and existing selection shrinks
 6531    cx.assert_editor_state(indoc!(
 6532        r#"lineˇ onˇe
 6533           liˇneˇ twˇo
 6534           lineˇ three
 6535           line four"#
 6536    ));
 6537
 6538    // intial state with two selection groups
 6539    cx.set_state(indoc!(
 6540        r#"abcd
 6541           ef«ghˇ»
 6542           ijkl
 6543           «mˇ»nop"#
 6544    ));
 6545
 6546    cx.update_editor(|editor, window, cx| {
 6547        editor.add_selection_above(&Default::default(), window, cx);
 6548        editor.add_selection_above(&Default::default(), window, cx);
 6549    });
 6550
 6551    cx.assert_editor_state(indoc!(
 6552        r#"ab«cdˇ»
 6553           «eˇ»f«ghˇ»
 6554           «iˇ»jkl
 6555           «mˇ»nop"#
 6556    ));
 6557
 6558    // add single selection in middle - simulate opt drag
 6559    cx.update_editor(|editor, window, cx| {
 6560        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6561        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6562        editor.update_selection(
 6563            DisplayPoint::new(DisplayRow(2), 4),
 6564            0,
 6565            gpui::Point::<f32>::default(),
 6566            window,
 6567            cx,
 6568        );
 6569        editor.end_selection(window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"ab«cdˇ»
 6574           «eˇ»f«ghˇ»
 6575           «iˇ»jk«lˇ»
 6576           «mˇ»nop"#
 6577    ));
 6578
 6579    cx.update_editor(|editor, window, cx| {
 6580        editor.add_selection_below(&Default::default(), window, cx);
 6581    });
 6582
 6583    // test new added selection expands below, others shrinks from above
 6584    cx.assert_editor_state(indoc!(
 6585        r#"abcd
 6586           ef«ghˇ»
 6587           «iˇ»jk«lˇ»
 6588           «mˇ»no«pˇ»"#
 6589    ));
 6590}
 6591
 6592#[gpui::test]
 6593async fn test_select_next(cx: &mut TestAppContext) {
 6594    init_test(cx, |_| {});
 6595
 6596    let mut cx = EditorTestContext::new(cx).await;
 6597    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6598
 6599    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6600        .unwrap();
 6601    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6602
 6603    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6604        .unwrap();
 6605    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6606
 6607    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6608    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6609
 6610    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6611    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6612
 6613    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6614        .unwrap();
 6615    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6616
 6617    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6618        .unwrap();
 6619    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6620
 6621    // Test selection direction should be preserved
 6622    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6623
 6624    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6625        .unwrap();
 6626    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6627}
 6628
 6629#[gpui::test]
 6630async fn test_select_all_matches(cx: &mut TestAppContext) {
 6631    init_test(cx, |_| {});
 6632
 6633    let mut cx = EditorTestContext::new(cx).await;
 6634
 6635    // Test caret-only selections
 6636    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6637    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6638        .unwrap();
 6639    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6640
 6641    // Test left-to-right selections
 6642    cx.set_state("abc\n«abcˇ»\nabc");
 6643    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6644        .unwrap();
 6645    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6646
 6647    // Test right-to-left selections
 6648    cx.set_state("abc\n«ˇabc»\nabc");
 6649    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6650        .unwrap();
 6651    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6652
 6653    // Test selecting whitespace with caret selection
 6654    cx.set_state("abc\nˇ   abc\nabc");
 6655    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6656        .unwrap();
 6657    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6658
 6659    // Test selecting whitespace with left-to-right selection
 6660    cx.set_state("abc\n«ˇ  »abc\nabc");
 6661    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6662        .unwrap();
 6663    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6664
 6665    // Test no matches with right-to-left selection
 6666    cx.set_state("abc\n«  ˇ»abc\nabc");
 6667    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6668        .unwrap();
 6669    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6670}
 6671
 6672#[gpui::test]
 6673async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6674    init_test(cx, |_| {});
 6675
 6676    let mut cx = EditorTestContext::new(cx).await;
 6677
 6678    let large_body_1 = "\nd".repeat(200);
 6679    let large_body_2 = "\ne".repeat(200);
 6680
 6681    cx.set_state(&format!(
 6682        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6683    ));
 6684    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6685        let scroll_position = editor.scroll_position(cx);
 6686        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6687        scroll_position
 6688    });
 6689
 6690    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6691        .unwrap();
 6692    cx.assert_editor_state(&format!(
 6693        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6694    ));
 6695    let scroll_position_after_selection =
 6696        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6697    assert_eq!(
 6698        initial_scroll_position, scroll_position_after_selection,
 6699        "Scroll position should not change after selecting all matches"
 6700    );
 6701}
 6702
 6703#[gpui::test]
 6704async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6705    init_test(cx, |_| {});
 6706
 6707    let mut cx = EditorLspTestContext::new_rust(
 6708        lsp::ServerCapabilities {
 6709            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6710            ..Default::default()
 6711        },
 6712        cx,
 6713    )
 6714    .await;
 6715
 6716    cx.set_state(indoc! {"
 6717        line 1
 6718        line 2
 6719        linˇe 3
 6720        line 4
 6721        line 5
 6722    "});
 6723
 6724    // Make an edit
 6725    cx.update_editor(|editor, window, cx| {
 6726        editor.handle_input("X", window, cx);
 6727    });
 6728
 6729    // Move cursor to a different position
 6730    cx.update_editor(|editor, window, cx| {
 6731        editor.change_selections(None, window, cx, |s| {
 6732            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6733        });
 6734    });
 6735
 6736    cx.assert_editor_state(indoc! {"
 6737        line 1
 6738        line 2
 6739        linXe 3
 6740        line 4
 6741        liˇne 5
 6742    "});
 6743
 6744    cx.lsp
 6745        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6746            Ok(Some(vec![lsp::TextEdit::new(
 6747                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6748                "PREFIX ".to_string(),
 6749            )]))
 6750        });
 6751
 6752    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6753        .unwrap()
 6754        .await
 6755        .unwrap();
 6756
 6757    cx.assert_editor_state(indoc! {"
 6758        PREFIX line 1
 6759        line 2
 6760        linXe 3
 6761        line 4
 6762        liˇne 5
 6763    "});
 6764
 6765    // Undo formatting
 6766    cx.update_editor(|editor, window, cx| {
 6767        editor.undo(&Default::default(), window, cx);
 6768    });
 6769
 6770    // Verify cursor moved back to position after edit
 6771    cx.assert_editor_state(indoc! {"
 6772        line 1
 6773        line 2
 6774        linXˇe 3
 6775        line 4
 6776        line 5
 6777    "});
 6778}
 6779
 6780#[gpui::test]
 6781async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6782    init_test(cx, |_| {});
 6783
 6784    let mut cx = EditorTestContext::new(cx).await;
 6785
 6786    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6787    cx.update_editor(|editor, window, cx| {
 6788        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6789    });
 6790
 6791    cx.set_state(indoc! {"
 6792        line 1
 6793        line 2
 6794        linˇe 3
 6795        line 4
 6796        line 5
 6797        line 6
 6798        line 7
 6799        line 8
 6800        line 9
 6801        line 10
 6802    "});
 6803
 6804    let snapshot = cx.buffer_snapshot();
 6805    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6806
 6807    cx.update(|_, cx| {
 6808        provider.update(cx, |provider, _| {
 6809            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6810                id: None,
 6811                edits: vec![(edit_position..edit_position, "X".into())],
 6812                edit_preview: None,
 6813            }))
 6814        })
 6815    });
 6816
 6817    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6820    });
 6821
 6822    cx.assert_editor_state(indoc! {"
 6823        line 1
 6824        line 2
 6825        lineXˇ 3
 6826        line 4
 6827        line 5
 6828        line 6
 6829        line 7
 6830        line 8
 6831        line 9
 6832        line 10
 6833    "});
 6834
 6835    cx.update_editor(|editor, window, cx| {
 6836        editor.change_selections(None, window, cx, |s| {
 6837            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6838        });
 6839    });
 6840
 6841    cx.assert_editor_state(indoc! {"
 6842        line 1
 6843        line 2
 6844        lineX 3
 6845        line 4
 6846        line 5
 6847        line 6
 6848        line 7
 6849        line 8
 6850        line 9
 6851        liˇne 10
 6852    "});
 6853
 6854    cx.update_editor(|editor, window, cx| {
 6855        editor.undo(&Default::default(), window, cx);
 6856    });
 6857
 6858    cx.assert_editor_state(indoc! {"
 6859        line 1
 6860        line 2
 6861        lineˇ 3
 6862        line 4
 6863        line 5
 6864        line 6
 6865        line 7
 6866        line 8
 6867        line 9
 6868        line 10
 6869    "});
 6870}
 6871
 6872#[gpui::test]
 6873async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6874    init_test(cx, |_| {});
 6875
 6876    let mut cx = EditorTestContext::new(cx).await;
 6877    cx.set_state(
 6878        r#"let foo = 2;
 6879lˇet foo = 2;
 6880let fooˇ = 2;
 6881let foo = 2;
 6882let foo = ˇ2;"#,
 6883    );
 6884
 6885    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6886        .unwrap();
 6887    cx.assert_editor_state(
 6888        r#"let foo = 2;
 6889«letˇ» foo = 2;
 6890let «fooˇ» = 2;
 6891let foo = 2;
 6892let foo = «2ˇ»;"#,
 6893    );
 6894
 6895    // noop for multiple selections with different contents
 6896    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6897        .unwrap();
 6898    cx.assert_editor_state(
 6899        r#"let foo = 2;
 6900«letˇ» foo = 2;
 6901let «fooˇ» = 2;
 6902let foo = 2;
 6903let foo = «2ˇ»;"#,
 6904    );
 6905
 6906    // Test last selection direction should be preserved
 6907    cx.set_state(
 6908        r#"let foo = 2;
 6909let foo = 2;
 6910let «fooˇ» = 2;
 6911let «ˇfoo» = 2;
 6912let foo = 2;"#,
 6913    );
 6914
 6915    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6916        .unwrap();
 6917    cx.assert_editor_state(
 6918        r#"let foo = 2;
 6919let foo = 2;
 6920let «fooˇ» = 2;
 6921let «ˇfoo» = 2;
 6922let «ˇfoo» = 2;"#,
 6923    );
 6924}
 6925
 6926#[gpui::test]
 6927async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6928    init_test(cx, |_| {});
 6929
 6930    let mut cx =
 6931        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6932
 6933    cx.assert_editor_state(indoc! {"
 6934        ˇbbb
 6935        ccc
 6936
 6937        bbb
 6938        ccc
 6939        "});
 6940    cx.dispatch_action(SelectPrevious::default());
 6941    cx.assert_editor_state(indoc! {"
 6942                «bbbˇ»
 6943                ccc
 6944
 6945                bbb
 6946                ccc
 6947                "});
 6948    cx.dispatch_action(SelectPrevious::default());
 6949    cx.assert_editor_state(indoc! {"
 6950                «bbbˇ»
 6951                ccc
 6952
 6953                «bbbˇ»
 6954                ccc
 6955                "});
 6956}
 6957
 6958#[gpui::test]
 6959async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6960    init_test(cx, |_| {});
 6961
 6962    let mut cx = EditorTestContext::new(cx).await;
 6963    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6964
 6965    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6966        .unwrap();
 6967    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6968
 6969    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6970        .unwrap();
 6971    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6972
 6973    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6974    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6975
 6976    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6977    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6978
 6979    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6980        .unwrap();
 6981    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6982
 6983    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6984        .unwrap();
 6985    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6986}
 6987
 6988#[gpui::test]
 6989async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6990    init_test(cx, |_| {});
 6991
 6992    let mut cx = EditorTestContext::new(cx).await;
 6993    cx.set_state("");
 6994
 6995    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6996        .unwrap();
 6997    cx.assert_editor_state("«aˇ»");
 6998    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6999        .unwrap();
 7000    cx.assert_editor_state("«aˇ»");
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorTestContext::new(cx).await;
 7008    cx.set_state(
 7009        r#"let foo = 2;
 7010lˇet foo = 2;
 7011let fooˇ = 2;
 7012let foo = 2;
 7013let foo = ˇ2;"#,
 7014    );
 7015
 7016    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7017        .unwrap();
 7018    cx.assert_editor_state(
 7019        r#"let foo = 2;
 7020«letˇ» foo = 2;
 7021let «fooˇ» = 2;
 7022let foo = 2;
 7023let foo = «2ˇ»;"#,
 7024    );
 7025
 7026    // noop for multiple selections with different contents
 7027    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7028        .unwrap();
 7029    cx.assert_editor_state(
 7030        r#"let foo = 2;
 7031«letˇ» foo = 2;
 7032let «fooˇ» = 2;
 7033let foo = 2;
 7034let foo = «2ˇ»;"#,
 7035    );
 7036}
 7037
 7038#[gpui::test]
 7039async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7040    init_test(cx, |_| {});
 7041
 7042    let mut cx = EditorTestContext::new(cx).await;
 7043    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7044
 7045    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7046        .unwrap();
 7047    // selection direction is preserved
 7048    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7049
 7050    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7051        .unwrap();
 7052    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7053
 7054    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7055    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7056
 7057    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7058    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7059
 7060    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7061        .unwrap();
 7062    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7063
 7064    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7065        .unwrap();
 7066    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7067}
 7068
 7069#[gpui::test]
 7070async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7071    init_test(cx, |_| {});
 7072
 7073    let language = Arc::new(Language::new(
 7074        LanguageConfig::default(),
 7075        Some(tree_sitter_rust::LANGUAGE.into()),
 7076    ));
 7077
 7078    let text = r#"
 7079        use mod1::mod2::{mod3, mod4};
 7080
 7081        fn fn_1(param1: bool, param2: &str) {
 7082            let var1 = "text";
 7083        }
 7084    "#
 7085    .unindent();
 7086
 7087    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7088    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7089    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7090
 7091    editor
 7092        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7093        .await;
 7094
 7095    editor.update_in(cx, |editor, window, cx| {
 7096        editor.change_selections(None, window, cx, |s| {
 7097            s.select_display_ranges([
 7098                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7099                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7100                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7101            ]);
 7102        });
 7103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7104    });
 7105    editor.update(cx, |editor, cx| {
 7106        assert_text_with_selections(
 7107            editor,
 7108            indoc! {r#"
 7109                use mod1::mod2::{mod3, «mod4ˇ»};
 7110
 7111                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7112                    let var1 = "«ˇtext»";
 7113                }
 7114            "#},
 7115            cx,
 7116        );
 7117    });
 7118
 7119    editor.update_in(cx, |editor, window, cx| {
 7120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7121    });
 7122    editor.update(cx, |editor, cx| {
 7123        assert_text_with_selections(
 7124            editor,
 7125            indoc! {r#"
 7126                use mod1::mod2::«{mod3, mod4}ˇ»;
 7127
 7128                «ˇfn fn_1(param1: bool, param2: &str) {
 7129                    let var1 = "text";
 7130 7131            "#},
 7132            cx,
 7133        );
 7134    });
 7135
 7136    editor.update_in(cx, |editor, window, cx| {
 7137        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7138    });
 7139    assert_eq!(
 7140        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7141        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7142    );
 7143
 7144    // Trying to expand the selected syntax node one more time has no effect.
 7145    editor.update_in(cx, |editor, window, cx| {
 7146        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7147    });
 7148    assert_eq!(
 7149        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7150        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7151    );
 7152
 7153    editor.update_in(cx, |editor, window, cx| {
 7154        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7155    });
 7156    editor.update(cx, |editor, cx| {
 7157        assert_text_with_selections(
 7158            editor,
 7159            indoc! {r#"
 7160                use mod1::mod2::«{mod3, mod4}ˇ»;
 7161
 7162                «ˇfn fn_1(param1: bool, param2: &str) {
 7163                    let var1 = "text";
 7164 7165            "#},
 7166            cx,
 7167        );
 7168    });
 7169
 7170    editor.update_in(cx, |editor, window, cx| {
 7171        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7172    });
 7173    editor.update(cx, |editor, cx| {
 7174        assert_text_with_selections(
 7175            editor,
 7176            indoc! {r#"
 7177                use mod1::mod2::{mod3, «mod4ˇ»};
 7178
 7179                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7180                    let var1 = "«ˇtext»";
 7181                }
 7182            "#},
 7183            cx,
 7184        );
 7185    });
 7186
 7187    editor.update_in(cx, |editor, window, cx| {
 7188        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7189    });
 7190    editor.update(cx, |editor, cx| {
 7191        assert_text_with_selections(
 7192            editor,
 7193            indoc! {r#"
 7194                use mod1::mod2::{mod3, mo«ˇ»d4};
 7195
 7196                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7197                    let var1 = "te«ˇ»xt";
 7198                }
 7199            "#},
 7200            cx,
 7201        );
 7202    });
 7203
 7204    // Trying to shrink the selected syntax node one more time has no effect.
 7205    editor.update_in(cx, |editor, window, cx| {
 7206        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7207    });
 7208    editor.update_in(cx, |editor, _, cx| {
 7209        assert_text_with_selections(
 7210            editor,
 7211            indoc! {r#"
 7212                use mod1::mod2::{mod3, mo«ˇ»d4};
 7213
 7214                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7215                    let var1 = "te«ˇ»xt";
 7216                }
 7217            "#},
 7218            cx,
 7219        );
 7220    });
 7221
 7222    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7223    // a fold.
 7224    editor.update_in(cx, |editor, window, cx| {
 7225        editor.fold_creases(
 7226            vec![
 7227                Crease::simple(
 7228                    Point::new(0, 21)..Point::new(0, 24),
 7229                    FoldPlaceholder::test(),
 7230                ),
 7231                Crease::simple(
 7232                    Point::new(3, 20)..Point::new(3, 22),
 7233                    FoldPlaceholder::test(),
 7234                ),
 7235            ],
 7236            true,
 7237            window,
 7238            cx,
 7239        );
 7240        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7241    });
 7242    editor.update(cx, |editor, cx| {
 7243        assert_text_with_selections(
 7244            editor,
 7245            indoc! {r#"
 7246                use mod1::mod2::«{mod3, mod4}ˇ»;
 7247
 7248                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7249                    let var1 = "«ˇtext»";
 7250                }
 7251            "#},
 7252            cx,
 7253        );
 7254    });
 7255}
 7256
 7257#[gpui::test]
 7258async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7259    init_test(cx, |_| {});
 7260
 7261    let language = Arc::new(Language::new(
 7262        LanguageConfig::default(),
 7263        Some(tree_sitter_rust::LANGUAGE.into()),
 7264    ));
 7265
 7266    let text = "let a = 2;";
 7267
 7268    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7269    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7270    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7271
 7272    editor
 7273        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7274        .await;
 7275
 7276    // Test case 1: Cursor at end of word
 7277    editor.update_in(cx, |editor, window, cx| {
 7278        editor.change_selections(None, window, cx, |s| {
 7279            s.select_display_ranges([
 7280                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7281            ]);
 7282        });
 7283    });
 7284    editor.update(cx, |editor, cx| {
 7285        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7286    });
 7287    editor.update_in(cx, |editor, window, cx| {
 7288        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7289    });
 7290    editor.update(cx, |editor, cx| {
 7291        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7292    });
 7293    editor.update_in(cx, |editor, window, cx| {
 7294        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7295    });
 7296    editor.update(cx, |editor, cx| {
 7297        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7298    });
 7299
 7300    // Test case 2: Cursor at end of statement
 7301    editor.update_in(cx, |editor, window, cx| {
 7302        editor.change_selections(None, window, cx, |s| {
 7303            s.select_display_ranges([
 7304                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7305            ]);
 7306        });
 7307    });
 7308    editor.update(cx, |editor, cx| {
 7309        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7310    });
 7311    editor.update_in(cx, |editor, window, cx| {
 7312        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7313    });
 7314    editor.update(cx, |editor, cx| {
 7315        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7316    });
 7317}
 7318
 7319#[gpui::test]
 7320async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7321    init_test(cx, |_| {});
 7322
 7323    let language = Arc::new(Language::new(
 7324        LanguageConfig::default(),
 7325        Some(tree_sitter_rust::LANGUAGE.into()),
 7326    ));
 7327
 7328    let text = r#"
 7329        use mod1::mod2::{mod3, mod4};
 7330
 7331        fn fn_1(param1: bool, param2: &str) {
 7332            let var1 = "hello world";
 7333        }
 7334    "#
 7335    .unindent();
 7336
 7337    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7338    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7339    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7340
 7341    editor
 7342        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7343        .await;
 7344
 7345    // Test 1: Cursor on a letter of a string word
 7346    editor.update_in(cx, |editor, window, cx| {
 7347        editor.change_selections(None, window, cx, |s| {
 7348            s.select_display_ranges([
 7349                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7350            ]);
 7351        });
 7352    });
 7353    editor.update_in(cx, |editor, window, cx| {
 7354        assert_text_with_selections(
 7355            editor,
 7356            indoc! {r#"
 7357                use mod1::mod2::{mod3, mod4};
 7358
 7359                fn fn_1(param1: bool, param2: &str) {
 7360                    let var1 = "hˇello world";
 7361                }
 7362            "#},
 7363            cx,
 7364        );
 7365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7366        assert_text_with_selections(
 7367            editor,
 7368            indoc! {r#"
 7369                use mod1::mod2::{mod3, mod4};
 7370
 7371                fn fn_1(param1: bool, param2: &str) {
 7372                    let var1 = "«ˇhello» world";
 7373                }
 7374            "#},
 7375            cx,
 7376        );
 7377    });
 7378
 7379    // Test 2: Partial selection within a word
 7380    editor.update_in(cx, |editor, window, cx| {
 7381        editor.change_selections(None, window, cx, |s| {
 7382            s.select_display_ranges([
 7383                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7384            ]);
 7385        });
 7386    });
 7387    editor.update_in(cx, |editor, window, cx| {
 7388        assert_text_with_selections(
 7389            editor,
 7390            indoc! {r#"
 7391                use mod1::mod2::{mod3, mod4};
 7392
 7393                fn fn_1(param1: bool, param2: &str) {
 7394                    let var1 = "h«elˇ»lo world";
 7395                }
 7396            "#},
 7397            cx,
 7398        );
 7399        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7400        assert_text_with_selections(
 7401            editor,
 7402            indoc! {r#"
 7403                use mod1::mod2::{mod3, mod4};
 7404
 7405                fn fn_1(param1: bool, param2: &str) {
 7406                    let var1 = "«ˇhello» world";
 7407                }
 7408            "#},
 7409            cx,
 7410        );
 7411    });
 7412
 7413    // Test 3: Complete word already selected
 7414    editor.update_in(cx, |editor, window, cx| {
 7415        editor.change_selections(None, window, cx, |s| {
 7416            s.select_display_ranges([
 7417                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7418            ]);
 7419        });
 7420    });
 7421    editor.update_in(cx, |editor, window, cx| {
 7422        assert_text_with_selections(
 7423            editor,
 7424            indoc! {r#"
 7425                use mod1::mod2::{mod3, mod4};
 7426
 7427                fn fn_1(param1: bool, param2: &str) {
 7428                    let var1 = "«helloˇ» world";
 7429                }
 7430            "#},
 7431            cx,
 7432        );
 7433        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7434        assert_text_with_selections(
 7435            editor,
 7436            indoc! {r#"
 7437                use mod1::mod2::{mod3, mod4};
 7438
 7439                fn fn_1(param1: bool, param2: &str) {
 7440                    let var1 = "«hello worldˇ»";
 7441                }
 7442            "#},
 7443            cx,
 7444        );
 7445    });
 7446
 7447    // Test 4: Selection spanning across words
 7448    editor.update_in(cx, |editor, window, cx| {
 7449        editor.change_selections(None, window, cx, |s| {
 7450            s.select_display_ranges([
 7451                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7452            ]);
 7453        });
 7454    });
 7455    editor.update_in(cx, |editor, window, cx| {
 7456        assert_text_with_selections(
 7457            editor,
 7458            indoc! {r#"
 7459                use mod1::mod2::{mod3, mod4};
 7460
 7461                fn fn_1(param1: bool, param2: &str) {
 7462                    let var1 = "hel«lo woˇ»rld";
 7463                }
 7464            "#},
 7465            cx,
 7466        );
 7467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7468        assert_text_with_selections(
 7469            editor,
 7470            indoc! {r#"
 7471                use mod1::mod2::{mod3, mod4};
 7472
 7473                fn fn_1(param1: bool, param2: &str) {
 7474                    let var1 = "«ˇhello world»";
 7475                }
 7476            "#},
 7477            cx,
 7478        );
 7479    });
 7480
 7481    // Test 5: Expansion beyond string
 7482    editor.update_in(cx, |editor, window, cx| {
 7483        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7485        assert_text_with_selections(
 7486            editor,
 7487            indoc! {r#"
 7488                use mod1::mod2::{mod3, mod4};
 7489
 7490                fn fn_1(param1: bool, param2: &str) {
 7491                    «ˇlet var1 = "hello world";»
 7492                }
 7493            "#},
 7494            cx,
 7495        );
 7496    });
 7497}
 7498
 7499#[gpui::test]
 7500async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7501    init_test(cx, |_| {});
 7502
 7503    let base_text = r#"
 7504        impl A {
 7505            // this is an uncommitted comment
 7506
 7507            fn b() {
 7508                c();
 7509            }
 7510
 7511            // this is another uncommitted comment
 7512
 7513            fn d() {
 7514                // e
 7515                // f
 7516            }
 7517        }
 7518
 7519        fn g() {
 7520            // h
 7521        }
 7522    "#
 7523    .unindent();
 7524
 7525    let text = r#"
 7526        ˇimpl A {
 7527
 7528            fn b() {
 7529                c();
 7530            }
 7531
 7532            fn d() {
 7533                // e
 7534                // f
 7535            }
 7536        }
 7537
 7538        fn g() {
 7539            // h
 7540        }
 7541    "#
 7542    .unindent();
 7543
 7544    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7545    cx.set_state(&text);
 7546    cx.set_head_text(&base_text);
 7547    cx.update_editor(|editor, window, cx| {
 7548        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7549    });
 7550
 7551    cx.assert_state_with_diff(
 7552        "
 7553        ˇimpl A {
 7554      -     // this is an uncommitted comment
 7555
 7556            fn b() {
 7557                c();
 7558            }
 7559
 7560      -     // this is another uncommitted comment
 7561      -
 7562            fn d() {
 7563                // e
 7564                // f
 7565            }
 7566        }
 7567
 7568        fn g() {
 7569            // h
 7570        }
 7571    "
 7572        .unindent(),
 7573    );
 7574
 7575    let expected_display_text = "
 7576        impl A {
 7577            // this is an uncommitted comment
 7578
 7579            fn b() {
 7580 7581            }
 7582
 7583            // this is another uncommitted comment
 7584
 7585            fn d() {
 7586 7587            }
 7588        }
 7589
 7590        fn g() {
 7591 7592        }
 7593        "
 7594    .unindent();
 7595
 7596    cx.update_editor(|editor, window, cx| {
 7597        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7598        assert_eq!(editor.display_text(cx), expected_display_text);
 7599    });
 7600}
 7601
 7602#[gpui::test]
 7603async fn test_autoindent(cx: &mut TestAppContext) {
 7604    init_test(cx, |_| {});
 7605
 7606    let language = Arc::new(
 7607        Language::new(
 7608            LanguageConfig {
 7609                brackets: BracketPairConfig {
 7610                    pairs: vec![
 7611                        BracketPair {
 7612                            start: "{".to_string(),
 7613                            end: "}".to_string(),
 7614                            close: false,
 7615                            surround: false,
 7616                            newline: true,
 7617                        },
 7618                        BracketPair {
 7619                            start: "(".to_string(),
 7620                            end: ")".to_string(),
 7621                            close: false,
 7622                            surround: false,
 7623                            newline: true,
 7624                        },
 7625                    ],
 7626                    ..Default::default()
 7627                },
 7628                ..Default::default()
 7629            },
 7630            Some(tree_sitter_rust::LANGUAGE.into()),
 7631        )
 7632        .with_indents_query(
 7633            r#"
 7634                (_ "(" ")" @end) @indent
 7635                (_ "{" "}" @end) @indent
 7636            "#,
 7637        )
 7638        .unwrap(),
 7639    );
 7640
 7641    let text = "fn a() {}";
 7642
 7643    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7644    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7645    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7646    editor
 7647        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7648        .await;
 7649
 7650    editor.update_in(cx, |editor, window, cx| {
 7651        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7652        editor.newline(&Newline, window, cx);
 7653        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7654        assert_eq!(
 7655            editor.selections.ranges(cx),
 7656            &[
 7657                Point::new(1, 4)..Point::new(1, 4),
 7658                Point::new(3, 4)..Point::new(3, 4),
 7659                Point::new(5, 0)..Point::new(5, 0)
 7660            ]
 7661        );
 7662    });
 7663}
 7664
 7665#[gpui::test]
 7666async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7667    init_test(cx, |_| {});
 7668
 7669    {
 7670        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7671        cx.set_state(indoc! {"
 7672            impl A {
 7673
 7674                fn b() {}
 7675
 7676            «fn c() {
 7677
 7678            }ˇ»
 7679            }
 7680        "});
 7681
 7682        cx.update_editor(|editor, window, cx| {
 7683            editor.autoindent(&Default::default(), window, cx);
 7684        });
 7685
 7686        cx.assert_editor_state(indoc! {"
 7687            impl A {
 7688
 7689                fn b() {}
 7690
 7691                «fn c() {
 7692
 7693                }ˇ»
 7694            }
 7695        "});
 7696    }
 7697
 7698    {
 7699        let mut cx = EditorTestContext::new_multibuffer(
 7700            cx,
 7701            [indoc! { "
 7702                impl A {
 7703                «
 7704                // a
 7705                fn b(){}
 7706                »
 7707                «
 7708                    }
 7709                    fn c(){}
 7710                »
 7711            "}],
 7712        );
 7713
 7714        let buffer = cx.update_editor(|editor, _, cx| {
 7715            let buffer = editor.buffer().update(cx, |buffer, _| {
 7716                buffer.all_buffers().iter().next().unwrap().clone()
 7717            });
 7718            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7719            buffer
 7720        });
 7721
 7722        cx.run_until_parked();
 7723        cx.update_editor(|editor, window, cx| {
 7724            editor.select_all(&Default::default(), window, cx);
 7725            editor.autoindent(&Default::default(), window, cx)
 7726        });
 7727        cx.run_until_parked();
 7728
 7729        cx.update(|_, cx| {
 7730            assert_eq!(
 7731                buffer.read(cx).text(),
 7732                indoc! { "
 7733                    impl A {
 7734
 7735                        // a
 7736                        fn b(){}
 7737
 7738
 7739                    }
 7740                    fn c(){}
 7741
 7742                " }
 7743            )
 7744        });
 7745    }
 7746}
 7747
 7748#[gpui::test]
 7749async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7750    init_test(cx, |_| {});
 7751
 7752    let mut cx = EditorTestContext::new(cx).await;
 7753
 7754    let language = Arc::new(Language::new(
 7755        LanguageConfig {
 7756            brackets: BracketPairConfig {
 7757                pairs: vec![
 7758                    BracketPair {
 7759                        start: "{".to_string(),
 7760                        end: "}".to_string(),
 7761                        close: true,
 7762                        surround: true,
 7763                        newline: true,
 7764                    },
 7765                    BracketPair {
 7766                        start: "(".to_string(),
 7767                        end: ")".to_string(),
 7768                        close: true,
 7769                        surround: true,
 7770                        newline: true,
 7771                    },
 7772                    BracketPair {
 7773                        start: "/*".to_string(),
 7774                        end: " */".to_string(),
 7775                        close: true,
 7776                        surround: true,
 7777                        newline: true,
 7778                    },
 7779                    BracketPair {
 7780                        start: "[".to_string(),
 7781                        end: "]".to_string(),
 7782                        close: false,
 7783                        surround: false,
 7784                        newline: true,
 7785                    },
 7786                    BracketPair {
 7787                        start: "\"".to_string(),
 7788                        end: "\"".to_string(),
 7789                        close: true,
 7790                        surround: true,
 7791                        newline: false,
 7792                    },
 7793                    BracketPair {
 7794                        start: "<".to_string(),
 7795                        end: ">".to_string(),
 7796                        close: false,
 7797                        surround: true,
 7798                        newline: true,
 7799                    },
 7800                ],
 7801                ..Default::default()
 7802            },
 7803            autoclose_before: "})]".to_string(),
 7804            ..Default::default()
 7805        },
 7806        Some(tree_sitter_rust::LANGUAGE.into()),
 7807    ));
 7808
 7809    cx.language_registry().add(language.clone());
 7810    cx.update_buffer(|buffer, cx| {
 7811        buffer.set_language(Some(language), cx);
 7812    });
 7813
 7814    cx.set_state(
 7815        &r#"
 7816            🏀ˇ
 7817            εˇ
 7818            ❤️ˇ
 7819        "#
 7820        .unindent(),
 7821    );
 7822
 7823    // autoclose multiple nested brackets at multiple cursors
 7824    cx.update_editor(|editor, window, cx| {
 7825        editor.handle_input("{", window, cx);
 7826        editor.handle_input("{", window, cx);
 7827        editor.handle_input("{", window, cx);
 7828    });
 7829    cx.assert_editor_state(
 7830        &"
 7831            🏀{{{ˇ}}}
 7832            ε{{{ˇ}}}
 7833            ❤️{{{ˇ}}}
 7834        "
 7835        .unindent(),
 7836    );
 7837
 7838    // insert a different closing bracket
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.handle_input(")", window, cx);
 7841    });
 7842    cx.assert_editor_state(
 7843        &"
 7844            🏀{{{)ˇ}}}
 7845            ε{{{)ˇ}}}
 7846            ❤️{{{)ˇ}}}
 7847        "
 7848        .unindent(),
 7849    );
 7850
 7851    // skip over the auto-closed brackets when typing a closing bracket
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.move_right(&MoveRight, window, cx);
 7854        editor.handle_input("}", window, cx);
 7855        editor.handle_input("}", window, cx);
 7856        editor.handle_input("}", window, cx);
 7857    });
 7858    cx.assert_editor_state(
 7859        &"
 7860            🏀{{{)}}}}ˇ
 7861            ε{{{)}}}}ˇ
 7862            ❤️{{{)}}}}ˇ
 7863        "
 7864        .unindent(),
 7865    );
 7866
 7867    // autoclose multi-character pairs
 7868    cx.set_state(
 7869        &"
 7870            ˇ
 7871            ˇ
 7872        "
 7873        .unindent(),
 7874    );
 7875    cx.update_editor(|editor, window, cx| {
 7876        editor.handle_input("/", window, cx);
 7877        editor.handle_input("*", window, cx);
 7878    });
 7879    cx.assert_editor_state(
 7880        &"
 7881            /*ˇ */
 7882            /*ˇ */
 7883        "
 7884        .unindent(),
 7885    );
 7886
 7887    // one cursor autocloses a multi-character pair, one cursor
 7888    // does not autoclose.
 7889    cx.set_state(
 7890        &"
 7891 7892            ˇ
 7893        "
 7894        .unindent(),
 7895    );
 7896    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7897    cx.assert_editor_state(
 7898        &"
 7899            /*ˇ */
 7900 7901        "
 7902        .unindent(),
 7903    );
 7904
 7905    // Don't autoclose if the next character isn't whitespace and isn't
 7906    // listed in the language's "autoclose_before" section.
 7907    cx.set_state("ˇa b");
 7908    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7909    cx.assert_editor_state("{ˇa b");
 7910
 7911    // Don't autoclose if `close` is false for the bracket pair
 7912    cx.set_state("ˇ");
 7913    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7914    cx.assert_editor_state("");
 7915
 7916    // Surround with brackets if text is selected
 7917    cx.set_state("«aˇ» b");
 7918    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7919    cx.assert_editor_state("{«aˇ»} b");
 7920
 7921    // Autoclose when not immediately after a word character
 7922    cx.set_state("a ˇ");
 7923    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7924    cx.assert_editor_state("a \"ˇ\"");
 7925
 7926    // Autoclose pair where the start and end characters are the same
 7927    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7928    cx.assert_editor_state("a \"\"ˇ");
 7929
 7930    // Don't autoclose when immediately after a word character
 7931    cx.set_state("");
 7932    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7933    cx.assert_editor_state("a\"ˇ");
 7934
 7935    // Do autoclose when after a non-word character
 7936    cx.set_state("");
 7937    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7938    cx.assert_editor_state("{\"ˇ\"");
 7939
 7940    // Non identical pairs autoclose regardless of preceding character
 7941    cx.set_state("");
 7942    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7943    cx.assert_editor_state("a{ˇ}");
 7944
 7945    // Don't autoclose pair if autoclose is disabled
 7946    cx.set_state("ˇ");
 7947    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7948    cx.assert_editor_state("");
 7949
 7950    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7951    cx.set_state("«aˇ» b");
 7952    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7953    cx.assert_editor_state("<«aˇ»> b");
 7954}
 7955
 7956#[gpui::test]
 7957async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7958    init_test(cx, |settings| {
 7959        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7960    });
 7961
 7962    let mut cx = EditorTestContext::new(cx).await;
 7963
 7964    let language = Arc::new(Language::new(
 7965        LanguageConfig {
 7966            brackets: BracketPairConfig {
 7967                pairs: vec![
 7968                    BracketPair {
 7969                        start: "{".to_string(),
 7970                        end: "}".to_string(),
 7971                        close: true,
 7972                        surround: true,
 7973                        newline: true,
 7974                    },
 7975                    BracketPair {
 7976                        start: "(".to_string(),
 7977                        end: ")".to_string(),
 7978                        close: true,
 7979                        surround: true,
 7980                        newline: true,
 7981                    },
 7982                    BracketPair {
 7983                        start: "[".to_string(),
 7984                        end: "]".to_string(),
 7985                        close: false,
 7986                        surround: false,
 7987                        newline: true,
 7988                    },
 7989                ],
 7990                ..Default::default()
 7991            },
 7992            autoclose_before: "})]".to_string(),
 7993            ..Default::default()
 7994        },
 7995        Some(tree_sitter_rust::LANGUAGE.into()),
 7996    ));
 7997
 7998    cx.language_registry().add(language.clone());
 7999    cx.update_buffer(|buffer, cx| {
 8000        buffer.set_language(Some(language), cx);
 8001    });
 8002
 8003    cx.set_state(
 8004        &"
 8005            ˇ
 8006            ˇ
 8007            ˇ
 8008        "
 8009        .unindent(),
 8010    );
 8011
 8012    // ensure only matching closing brackets are skipped over
 8013    cx.update_editor(|editor, window, cx| {
 8014        editor.handle_input("}", window, cx);
 8015        editor.move_left(&MoveLeft, window, cx);
 8016        editor.handle_input(")", window, cx);
 8017        editor.move_left(&MoveLeft, window, cx);
 8018    });
 8019    cx.assert_editor_state(
 8020        &"
 8021            ˇ)}
 8022            ˇ)}
 8023            ˇ)}
 8024        "
 8025        .unindent(),
 8026    );
 8027
 8028    // skip-over closing brackets at multiple cursors
 8029    cx.update_editor(|editor, window, cx| {
 8030        editor.handle_input(")", window, cx);
 8031        editor.handle_input("}", window, cx);
 8032    });
 8033    cx.assert_editor_state(
 8034        &"
 8035            )}ˇ
 8036            )}ˇ
 8037            )}ˇ
 8038        "
 8039        .unindent(),
 8040    );
 8041
 8042    // ignore non-close brackets
 8043    cx.update_editor(|editor, window, cx| {
 8044        editor.handle_input("]", window, cx);
 8045        editor.move_left(&MoveLeft, window, cx);
 8046        editor.handle_input("]", window, cx);
 8047    });
 8048    cx.assert_editor_state(
 8049        &"
 8050            )}]ˇ]
 8051            )}]ˇ]
 8052            )}]ˇ]
 8053        "
 8054        .unindent(),
 8055    );
 8056}
 8057
 8058#[gpui::test]
 8059async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8060    init_test(cx, |_| {});
 8061
 8062    let mut cx = EditorTestContext::new(cx).await;
 8063
 8064    let html_language = Arc::new(
 8065        Language::new(
 8066            LanguageConfig {
 8067                name: "HTML".into(),
 8068                brackets: BracketPairConfig {
 8069                    pairs: vec![
 8070                        BracketPair {
 8071                            start: "<".into(),
 8072                            end: ">".into(),
 8073                            close: true,
 8074                            ..Default::default()
 8075                        },
 8076                        BracketPair {
 8077                            start: "{".into(),
 8078                            end: "}".into(),
 8079                            close: true,
 8080                            ..Default::default()
 8081                        },
 8082                        BracketPair {
 8083                            start: "(".into(),
 8084                            end: ")".into(),
 8085                            close: true,
 8086                            ..Default::default()
 8087                        },
 8088                    ],
 8089                    ..Default::default()
 8090                },
 8091                autoclose_before: "})]>".into(),
 8092                ..Default::default()
 8093            },
 8094            Some(tree_sitter_html::LANGUAGE.into()),
 8095        )
 8096        .with_injection_query(
 8097            r#"
 8098            (script_element
 8099                (raw_text) @injection.content
 8100                (#set! injection.language "javascript"))
 8101            "#,
 8102        )
 8103        .unwrap(),
 8104    );
 8105
 8106    let javascript_language = Arc::new(Language::new(
 8107        LanguageConfig {
 8108            name: "JavaScript".into(),
 8109            brackets: BracketPairConfig {
 8110                pairs: vec![
 8111                    BracketPair {
 8112                        start: "/*".into(),
 8113                        end: " */".into(),
 8114                        close: true,
 8115                        ..Default::default()
 8116                    },
 8117                    BracketPair {
 8118                        start: "{".into(),
 8119                        end: "}".into(),
 8120                        close: true,
 8121                        ..Default::default()
 8122                    },
 8123                    BracketPair {
 8124                        start: "(".into(),
 8125                        end: ")".into(),
 8126                        close: true,
 8127                        ..Default::default()
 8128                    },
 8129                ],
 8130                ..Default::default()
 8131            },
 8132            autoclose_before: "})]>".into(),
 8133            ..Default::default()
 8134        },
 8135        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8136    ));
 8137
 8138    cx.language_registry().add(html_language.clone());
 8139    cx.language_registry().add(javascript_language.clone());
 8140
 8141    cx.update_buffer(|buffer, cx| {
 8142        buffer.set_language(Some(html_language), cx);
 8143    });
 8144
 8145    cx.set_state(
 8146        &r#"
 8147            <body>ˇ
 8148                <script>
 8149                    var x = 1;ˇ
 8150                </script>
 8151            </body>ˇ
 8152        "#
 8153        .unindent(),
 8154    );
 8155
 8156    // Precondition: different languages are active at different locations.
 8157    cx.update_editor(|editor, window, cx| {
 8158        let snapshot = editor.snapshot(window, cx);
 8159        let cursors = editor.selections.ranges::<usize>(cx);
 8160        let languages = cursors
 8161            .iter()
 8162            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8163            .collect::<Vec<_>>();
 8164        assert_eq!(
 8165            languages,
 8166            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8167        );
 8168    });
 8169
 8170    // Angle brackets autoclose in HTML, but not JavaScript.
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.handle_input("<", window, cx);
 8173        editor.handle_input("a", window, cx);
 8174    });
 8175    cx.assert_editor_state(
 8176        &r#"
 8177            <body><aˇ>
 8178                <script>
 8179                    var x = 1;<aˇ
 8180                </script>
 8181            </body><aˇ>
 8182        "#
 8183        .unindent(),
 8184    );
 8185
 8186    // Curly braces and parens autoclose in both HTML and JavaScript.
 8187    cx.update_editor(|editor, window, cx| {
 8188        editor.handle_input(" b=", window, cx);
 8189        editor.handle_input("{", window, cx);
 8190        editor.handle_input("c", window, cx);
 8191        editor.handle_input("(", window, cx);
 8192    });
 8193    cx.assert_editor_state(
 8194        &r#"
 8195            <body><a b={c(ˇ)}>
 8196                <script>
 8197                    var x = 1;<a b={c(ˇ)}
 8198                </script>
 8199            </body><a b={c(ˇ)}>
 8200        "#
 8201        .unindent(),
 8202    );
 8203
 8204    // Brackets that were already autoclosed are skipped.
 8205    cx.update_editor(|editor, window, cx| {
 8206        editor.handle_input(")", window, cx);
 8207        editor.handle_input("d", window, cx);
 8208        editor.handle_input("}", window, cx);
 8209    });
 8210    cx.assert_editor_state(
 8211        &r#"
 8212            <body><a b={c()d}ˇ>
 8213                <script>
 8214                    var x = 1;<a b={c()d}ˇ
 8215                </script>
 8216            </body><a b={c()d}ˇ>
 8217        "#
 8218        .unindent(),
 8219    );
 8220    cx.update_editor(|editor, window, cx| {
 8221        editor.handle_input(">", window, cx);
 8222    });
 8223    cx.assert_editor_state(
 8224        &r#"
 8225            <body><a b={c()d}>ˇ
 8226                <script>
 8227                    var x = 1;<a b={c()d}>ˇ
 8228                </script>
 8229            </body><a b={c()d}>ˇ
 8230        "#
 8231        .unindent(),
 8232    );
 8233
 8234    // Reset
 8235    cx.set_state(
 8236        &r#"
 8237            <body>ˇ
 8238                <script>
 8239                    var x = 1;ˇ
 8240                </script>
 8241            </body>ˇ
 8242        "#
 8243        .unindent(),
 8244    );
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.handle_input("<", window, cx);
 8248    });
 8249    cx.assert_editor_state(
 8250        &r#"
 8251            <body><ˇ>
 8252                <script>
 8253                    var x = 1;<ˇ
 8254                </script>
 8255            </body><ˇ>
 8256        "#
 8257        .unindent(),
 8258    );
 8259
 8260    // When backspacing, the closing angle brackets are removed.
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.backspace(&Backspace, window, cx);
 8263    });
 8264    cx.assert_editor_state(
 8265        &r#"
 8266            <body>ˇ
 8267                <script>
 8268                    var x = 1;ˇ
 8269                </script>
 8270            </body>ˇ
 8271        "#
 8272        .unindent(),
 8273    );
 8274
 8275    // Block comments autoclose in JavaScript, but not HTML.
 8276    cx.update_editor(|editor, window, cx| {
 8277        editor.handle_input("/", window, cx);
 8278        editor.handle_input("*", window, cx);
 8279    });
 8280    cx.assert_editor_state(
 8281        &r#"
 8282            <body>/*ˇ
 8283                <script>
 8284                    var x = 1;/*ˇ */
 8285                </script>
 8286            </body>/*ˇ
 8287        "#
 8288        .unindent(),
 8289    );
 8290}
 8291
 8292#[gpui::test]
 8293async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8294    init_test(cx, |_| {});
 8295
 8296    let mut cx = EditorTestContext::new(cx).await;
 8297
 8298    let rust_language = Arc::new(
 8299        Language::new(
 8300            LanguageConfig {
 8301                name: "Rust".into(),
 8302                brackets: serde_json::from_value(json!([
 8303                    { "start": "{", "end": "}", "close": true, "newline": true },
 8304                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8305                ]))
 8306                .unwrap(),
 8307                autoclose_before: "})]>".into(),
 8308                ..Default::default()
 8309            },
 8310            Some(tree_sitter_rust::LANGUAGE.into()),
 8311        )
 8312        .with_override_query("(string_literal) @string")
 8313        .unwrap(),
 8314    );
 8315
 8316    cx.language_registry().add(rust_language.clone());
 8317    cx.update_buffer(|buffer, cx| {
 8318        buffer.set_language(Some(rust_language), cx);
 8319    });
 8320
 8321    cx.set_state(
 8322        &r#"
 8323            let x = ˇ
 8324        "#
 8325        .unindent(),
 8326    );
 8327
 8328    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8329    cx.update_editor(|editor, window, cx| {
 8330        editor.handle_input("\"", window, cx);
 8331    });
 8332    cx.assert_editor_state(
 8333        &r#"
 8334            let x = "ˇ"
 8335        "#
 8336        .unindent(),
 8337    );
 8338
 8339    // Inserting another quotation mark. The cursor moves across the existing
 8340    // automatically-inserted quotation mark.
 8341    cx.update_editor(|editor, window, cx| {
 8342        editor.handle_input("\"", window, cx);
 8343    });
 8344    cx.assert_editor_state(
 8345        &r#"
 8346            let x = ""ˇ
 8347        "#
 8348        .unindent(),
 8349    );
 8350
 8351    // Reset
 8352    cx.set_state(
 8353        &r#"
 8354            let x = ˇ
 8355        "#
 8356        .unindent(),
 8357    );
 8358
 8359    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8360    cx.update_editor(|editor, window, cx| {
 8361        editor.handle_input("\"", window, cx);
 8362        editor.handle_input(" ", window, cx);
 8363        editor.move_left(&Default::default(), window, cx);
 8364        editor.handle_input("\\", window, cx);
 8365        editor.handle_input("\"", window, cx);
 8366    });
 8367    cx.assert_editor_state(
 8368        &r#"
 8369            let x = "\"ˇ "
 8370        "#
 8371        .unindent(),
 8372    );
 8373
 8374    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8375    // mark. Nothing is inserted.
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.move_right(&Default::default(), window, cx);
 8378        editor.handle_input("\"", window, cx);
 8379    });
 8380    cx.assert_editor_state(
 8381        &r#"
 8382            let x = "\" "ˇ
 8383        "#
 8384        .unindent(),
 8385    );
 8386}
 8387
 8388#[gpui::test]
 8389async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8390    init_test(cx, |_| {});
 8391
 8392    let language = Arc::new(Language::new(
 8393        LanguageConfig {
 8394            brackets: BracketPairConfig {
 8395                pairs: vec![
 8396                    BracketPair {
 8397                        start: "{".to_string(),
 8398                        end: "}".to_string(),
 8399                        close: true,
 8400                        surround: true,
 8401                        newline: true,
 8402                    },
 8403                    BracketPair {
 8404                        start: "/* ".to_string(),
 8405                        end: "*/".to_string(),
 8406                        close: true,
 8407                        surround: true,
 8408                        ..Default::default()
 8409                    },
 8410                ],
 8411                ..Default::default()
 8412            },
 8413            ..Default::default()
 8414        },
 8415        Some(tree_sitter_rust::LANGUAGE.into()),
 8416    ));
 8417
 8418    let text = r#"
 8419        a
 8420        b
 8421        c
 8422    "#
 8423    .unindent();
 8424
 8425    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8426    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8428    editor
 8429        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8430        .await;
 8431
 8432    editor.update_in(cx, |editor, window, cx| {
 8433        editor.change_selections(None, window, cx, |s| {
 8434            s.select_display_ranges([
 8435                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8436                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8437                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8438            ])
 8439        });
 8440
 8441        editor.handle_input("{", window, cx);
 8442        editor.handle_input("{", window, cx);
 8443        editor.handle_input("{", window, cx);
 8444        assert_eq!(
 8445            editor.text(cx),
 8446            "
 8447                {{{a}}}
 8448                {{{b}}}
 8449                {{{c}}}
 8450            "
 8451            .unindent()
 8452        );
 8453        assert_eq!(
 8454            editor.selections.display_ranges(cx),
 8455            [
 8456                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8457                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8458                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8459            ]
 8460        );
 8461
 8462        editor.undo(&Undo, window, cx);
 8463        editor.undo(&Undo, window, cx);
 8464        editor.undo(&Undo, window, cx);
 8465        assert_eq!(
 8466            editor.text(cx),
 8467            "
 8468                a
 8469                b
 8470                c
 8471            "
 8472            .unindent()
 8473        );
 8474        assert_eq!(
 8475            editor.selections.display_ranges(cx),
 8476            [
 8477                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8478                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8479                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8480            ]
 8481        );
 8482
 8483        // Ensure inserting the first character of a multi-byte bracket pair
 8484        // doesn't surround the selections with the bracket.
 8485        editor.handle_input("/", window, cx);
 8486        assert_eq!(
 8487            editor.text(cx),
 8488            "
 8489                /
 8490                /
 8491                /
 8492            "
 8493            .unindent()
 8494        );
 8495        assert_eq!(
 8496            editor.selections.display_ranges(cx),
 8497            [
 8498                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8499                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8500                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8501            ]
 8502        );
 8503
 8504        editor.undo(&Undo, window, cx);
 8505        assert_eq!(
 8506            editor.text(cx),
 8507            "
 8508                a
 8509                b
 8510                c
 8511            "
 8512            .unindent()
 8513        );
 8514        assert_eq!(
 8515            editor.selections.display_ranges(cx),
 8516            [
 8517                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8518                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8519                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8520            ]
 8521        );
 8522
 8523        // Ensure inserting the last character of a multi-byte bracket pair
 8524        // doesn't surround the selections with the bracket.
 8525        editor.handle_input("*", window, cx);
 8526        assert_eq!(
 8527            editor.text(cx),
 8528            "
 8529                *
 8530                *
 8531                *
 8532            "
 8533            .unindent()
 8534        );
 8535        assert_eq!(
 8536            editor.selections.display_ranges(cx),
 8537            [
 8538                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8539                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8540                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8541            ]
 8542        );
 8543    });
 8544}
 8545
 8546#[gpui::test]
 8547async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8548    init_test(cx, |_| {});
 8549
 8550    let language = Arc::new(Language::new(
 8551        LanguageConfig {
 8552            brackets: BracketPairConfig {
 8553                pairs: vec![BracketPair {
 8554                    start: "{".to_string(),
 8555                    end: "}".to_string(),
 8556                    close: true,
 8557                    surround: true,
 8558                    newline: true,
 8559                }],
 8560                ..Default::default()
 8561            },
 8562            autoclose_before: "}".to_string(),
 8563            ..Default::default()
 8564        },
 8565        Some(tree_sitter_rust::LANGUAGE.into()),
 8566    ));
 8567
 8568    let text = r#"
 8569        a
 8570        b
 8571        c
 8572    "#
 8573    .unindent();
 8574
 8575    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8577    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8578    editor
 8579        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8580        .await;
 8581
 8582    editor.update_in(cx, |editor, window, cx| {
 8583        editor.change_selections(None, window, cx, |s| {
 8584            s.select_ranges([
 8585                Point::new(0, 1)..Point::new(0, 1),
 8586                Point::new(1, 1)..Point::new(1, 1),
 8587                Point::new(2, 1)..Point::new(2, 1),
 8588            ])
 8589        });
 8590
 8591        editor.handle_input("{", window, cx);
 8592        editor.handle_input("{", window, cx);
 8593        editor.handle_input("_", window, cx);
 8594        assert_eq!(
 8595            editor.text(cx),
 8596            "
 8597                a{{_}}
 8598                b{{_}}
 8599                c{{_}}
 8600            "
 8601            .unindent()
 8602        );
 8603        assert_eq!(
 8604            editor.selections.ranges::<Point>(cx),
 8605            [
 8606                Point::new(0, 4)..Point::new(0, 4),
 8607                Point::new(1, 4)..Point::new(1, 4),
 8608                Point::new(2, 4)..Point::new(2, 4)
 8609            ]
 8610        );
 8611
 8612        editor.backspace(&Default::default(), window, cx);
 8613        editor.backspace(&Default::default(), window, cx);
 8614        assert_eq!(
 8615            editor.text(cx),
 8616            "
 8617                a{}
 8618                b{}
 8619                c{}
 8620            "
 8621            .unindent()
 8622        );
 8623        assert_eq!(
 8624            editor.selections.ranges::<Point>(cx),
 8625            [
 8626                Point::new(0, 2)..Point::new(0, 2),
 8627                Point::new(1, 2)..Point::new(1, 2),
 8628                Point::new(2, 2)..Point::new(2, 2)
 8629            ]
 8630        );
 8631
 8632        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8633        assert_eq!(
 8634            editor.text(cx),
 8635            "
 8636                a
 8637                b
 8638                c
 8639            "
 8640            .unindent()
 8641        );
 8642        assert_eq!(
 8643            editor.selections.ranges::<Point>(cx),
 8644            [
 8645                Point::new(0, 1)..Point::new(0, 1),
 8646                Point::new(1, 1)..Point::new(1, 1),
 8647                Point::new(2, 1)..Point::new(2, 1)
 8648            ]
 8649        );
 8650    });
 8651}
 8652
 8653#[gpui::test]
 8654async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8655    init_test(cx, |settings| {
 8656        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8657    });
 8658
 8659    let mut cx = EditorTestContext::new(cx).await;
 8660
 8661    let language = Arc::new(Language::new(
 8662        LanguageConfig {
 8663            brackets: BracketPairConfig {
 8664                pairs: vec![
 8665                    BracketPair {
 8666                        start: "{".to_string(),
 8667                        end: "}".to_string(),
 8668                        close: true,
 8669                        surround: true,
 8670                        newline: true,
 8671                    },
 8672                    BracketPair {
 8673                        start: "(".to_string(),
 8674                        end: ")".to_string(),
 8675                        close: true,
 8676                        surround: true,
 8677                        newline: true,
 8678                    },
 8679                    BracketPair {
 8680                        start: "[".to_string(),
 8681                        end: "]".to_string(),
 8682                        close: false,
 8683                        surround: true,
 8684                        newline: true,
 8685                    },
 8686                ],
 8687                ..Default::default()
 8688            },
 8689            autoclose_before: "})]".to_string(),
 8690            ..Default::default()
 8691        },
 8692        Some(tree_sitter_rust::LANGUAGE.into()),
 8693    ));
 8694
 8695    cx.language_registry().add(language.clone());
 8696    cx.update_buffer(|buffer, cx| {
 8697        buffer.set_language(Some(language), cx);
 8698    });
 8699
 8700    cx.set_state(
 8701        &"
 8702            {(ˇ)}
 8703            [[ˇ]]
 8704            {(ˇ)}
 8705        "
 8706        .unindent(),
 8707    );
 8708
 8709    cx.update_editor(|editor, window, cx| {
 8710        editor.backspace(&Default::default(), window, cx);
 8711        editor.backspace(&Default::default(), window, cx);
 8712    });
 8713
 8714    cx.assert_editor_state(
 8715        &"
 8716            ˇ
 8717            ˇ]]
 8718            ˇ
 8719        "
 8720        .unindent(),
 8721    );
 8722
 8723    cx.update_editor(|editor, window, cx| {
 8724        editor.handle_input("{", window, cx);
 8725        editor.handle_input("{", window, cx);
 8726        editor.move_right(&MoveRight, window, cx);
 8727        editor.move_right(&MoveRight, window, cx);
 8728        editor.move_left(&MoveLeft, window, cx);
 8729        editor.move_left(&MoveLeft, window, cx);
 8730        editor.backspace(&Default::default(), window, cx);
 8731    });
 8732
 8733    cx.assert_editor_state(
 8734        &"
 8735            {ˇ}
 8736            {ˇ}]]
 8737            {ˇ}
 8738        "
 8739        .unindent(),
 8740    );
 8741
 8742    cx.update_editor(|editor, window, cx| {
 8743        editor.backspace(&Default::default(), window, cx);
 8744    });
 8745
 8746    cx.assert_editor_state(
 8747        &"
 8748            ˇ
 8749            ˇ]]
 8750            ˇ
 8751        "
 8752        .unindent(),
 8753    );
 8754}
 8755
 8756#[gpui::test]
 8757async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8758    init_test(cx, |_| {});
 8759
 8760    let language = Arc::new(Language::new(
 8761        LanguageConfig::default(),
 8762        Some(tree_sitter_rust::LANGUAGE.into()),
 8763    ));
 8764
 8765    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8766    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8767    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8768    editor
 8769        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8770        .await;
 8771
 8772    editor.update_in(cx, |editor, window, cx| {
 8773        editor.set_auto_replace_emoji_shortcode(true);
 8774
 8775        editor.handle_input("Hello ", window, cx);
 8776        editor.handle_input(":wave", window, cx);
 8777        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8778
 8779        editor.handle_input(":", window, cx);
 8780        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8781
 8782        editor.handle_input(" :smile", window, cx);
 8783        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8784
 8785        editor.handle_input(":", window, cx);
 8786        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8787
 8788        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8789        editor.handle_input(":wave", window, cx);
 8790        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8791
 8792        editor.handle_input(":", window, cx);
 8793        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8794
 8795        editor.handle_input(":1", window, cx);
 8796        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8797
 8798        editor.handle_input(":", window, cx);
 8799        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8800
 8801        // Ensure shortcode does not get replaced when it is part of a word
 8802        editor.handle_input(" Test:wave", window, cx);
 8803        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8804
 8805        editor.handle_input(":", window, cx);
 8806        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8807
 8808        editor.set_auto_replace_emoji_shortcode(false);
 8809
 8810        // Ensure shortcode does not get replaced when auto replace is off
 8811        editor.handle_input(" :wave", window, cx);
 8812        assert_eq!(
 8813            editor.text(cx),
 8814            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8815        );
 8816
 8817        editor.handle_input(":", window, cx);
 8818        assert_eq!(
 8819            editor.text(cx),
 8820            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8821        );
 8822    });
 8823}
 8824
 8825#[gpui::test]
 8826async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8827    init_test(cx, |_| {});
 8828
 8829    let (text, insertion_ranges) = marked_text_ranges(
 8830        indoc! {"
 8831            ˇ
 8832        "},
 8833        false,
 8834    );
 8835
 8836    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8837    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8838
 8839    _ = editor.update_in(cx, |editor, window, cx| {
 8840        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8841
 8842        editor
 8843            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8844            .unwrap();
 8845
 8846        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8847            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8848            assert_eq!(editor.text(cx), expected_text);
 8849            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8850        }
 8851
 8852        assert(
 8853            editor,
 8854            cx,
 8855            indoc! {"
 8856            type «» =•
 8857            "},
 8858        );
 8859
 8860        assert!(editor.context_menu_visible(), "There should be a matches");
 8861    });
 8862}
 8863
 8864#[gpui::test]
 8865async fn test_snippets(cx: &mut TestAppContext) {
 8866    init_test(cx, |_| {});
 8867
 8868    let mut cx = EditorTestContext::new(cx).await;
 8869
 8870    cx.set_state(indoc! {"
 8871        a.ˇ b
 8872        a.ˇ b
 8873        a.ˇ b
 8874    "});
 8875
 8876    cx.update_editor(|editor, window, cx| {
 8877        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8878        let insertion_ranges = editor
 8879            .selections
 8880            .all(cx)
 8881            .iter()
 8882            .map(|s| s.range().clone())
 8883            .collect::<Vec<_>>();
 8884        editor
 8885            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8886            .unwrap();
 8887    });
 8888
 8889    cx.assert_editor_state(indoc! {"
 8890        a.f(«oneˇ», two, «threeˇ») b
 8891        a.f(«oneˇ», two, «threeˇ») b
 8892        a.f(«oneˇ», two, «threeˇ») b
 8893    "});
 8894
 8895    // Can't move earlier than the first tab stop
 8896    cx.update_editor(|editor, window, cx| {
 8897        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8898    });
 8899    cx.assert_editor_state(indoc! {"
 8900        a.f(«oneˇ», two, «threeˇ») b
 8901        a.f(«oneˇ», two, «threeˇ») b
 8902        a.f(«oneˇ», two, «threeˇ») b
 8903    "});
 8904
 8905    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8906    cx.assert_editor_state(indoc! {"
 8907        a.f(one, «twoˇ», three) b
 8908        a.f(one, «twoˇ», three) b
 8909        a.f(one, «twoˇ», three) b
 8910    "});
 8911
 8912    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 8913    cx.assert_editor_state(indoc! {"
 8914        a.f(«oneˇ», two, «threeˇ») b
 8915        a.f(«oneˇ», two, «threeˇ») b
 8916        a.f(«oneˇ», two, «threeˇ») b
 8917    "});
 8918
 8919    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8920    cx.assert_editor_state(indoc! {"
 8921        a.f(one, «twoˇ», three) b
 8922        a.f(one, «twoˇ», three) b
 8923        a.f(one, «twoˇ», three) b
 8924    "});
 8925    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8926    cx.assert_editor_state(indoc! {"
 8927        a.f(one, two, three)ˇ b
 8928        a.f(one, two, three)ˇ b
 8929        a.f(one, two, three)ˇ b
 8930    "});
 8931
 8932    // As soon as the last tab stop is reached, snippet state is gone
 8933    cx.update_editor(|editor, window, cx| {
 8934        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8935    });
 8936    cx.assert_editor_state(indoc! {"
 8937        a.f(one, two, three)ˇ b
 8938        a.f(one, two, three)ˇ b
 8939        a.f(one, two, three)ˇ b
 8940    "});
 8941}
 8942
 8943#[gpui::test]
 8944async fn test_snippet_indentation(cx: &mut TestAppContext) {
 8945    init_test(cx, |_| {});
 8946
 8947    let mut cx = EditorTestContext::new(cx).await;
 8948
 8949    cx.update_editor(|editor, window, cx| {
 8950        let snippet = Snippet::parse(indoc! {"
 8951            /*
 8952             * Multiline comment with leading indentation
 8953             *
 8954             * $1
 8955             */
 8956            $0"})
 8957        .unwrap();
 8958        let insertion_ranges = editor
 8959            .selections
 8960            .all(cx)
 8961            .iter()
 8962            .map(|s| s.range().clone())
 8963            .collect::<Vec<_>>();
 8964        editor
 8965            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8966            .unwrap();
 8967    });
 8968
 8969    cx.assert_editor_state(indoc! {"
 8970        /*
 8971         * Multiline comment with leading indentation
 8972         *
 8973         * ˇ
 8974         */
 8975    "});
 8976
 8977    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8978    cx.assert_editor_state(indoc! {"
 8979        /*
 8980         * Multiline comment with leading indentation
 8981         *
 8982         *•
 8983         */
 8984        ˇ"});
 8985}
 8986
 8987#[gpui::test]
 8988async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8989    init_test(cx, |_| {});
 8990
 8991    let fs = FakeFs::new(cx.executor());
 8992    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8993
 8994    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8995
 8996    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8997    language_registry.add(rust_lang());
 8998    let mut fake_servers = language_registry.register_fake_lsp(
 8999        "Rust",
 9000        FakeLspAdapter {
 9001            capabilities: lsp::ServerCapabilities {
 9002                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9003                ..Default::default()
 9004            },
 9005            ..Default::default()
 9006        },
 9007    );
 9008
 9009    let buffer = project
 9010        .update(cx, |project, cx| {
 9011            project.open_local_buffer(path!("/file.rs"), cx)
 9012        })
 9013        .await
 9014        .unwrap();
 9015
 9016    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9017    let (editor, cx) = cx.add_window_view(|window, cx| {
 9018        build_editor_with_project(project.clone(), buffer, window, cx)
 9019    });
 9020    editor.update_in(cx, |editor, window, cx| {
 9021        editor.set_text("one\ntwo\nthree\n", window, cx)
 9022    });
 9023    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9024
 9025    cx.executor().start_waiting();
 9026    let fake_server = fake_servers.next().await.unwrap();
 9027
 9028    {
 9029        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9030            move |params, _| async move {
 9031                assert_eq!(
 9032                    params.text_document.uri,
 9033                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9034                );
 9035                assert_eq!(params.options.tab_size, 4);
 9036                Ok(Some(vec![lsp::TextEdit::new(
 9037                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9038                    ", ".to_string(),
 9039                )]))
 9040            },
 9041        );
 9042        let save = editor
 9043            .update_in(cx, |editor, window, cx| {
 9044                editor.save(true, project.clone(), window, cx)
 9045            })
 9046            .unwrap();
 9047        cx.executor().start_waiting();
 9048        save.await;
 9049
 9050        assert_eq!(
 9051            editor.update(cx, |editor, cx| editor.text(cx)),
 9052            "one, two\nthree\n"
 9053        );
 9054        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9055    }
 9056
 9057    {
 9058        editor.update_in(cx, |editor, window, cx| {
 9059            editor.set_text("one\ntwo\nthree\n", window, cx)
 9060        });
 9061        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9062
 9063        // Ensure we can still save even if formatting hangs.
 9064        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9065            move |params, _| async move {
 9066                assert_eq!(
 9067                    params.text_document.uri,
 9068                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9069                );
 9070                futures::future::pending::<()>().await;
 9071                unreachable!()
 9072            },
 9073        );
 9074        let save = editor
 9075            .update_in(cx, |editor, window, cx| {
 9076                editor.save(true, project.clone(), window, cx)
 9077            })
 9078            .unwrap();
 9079        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9080        cx.executor().start_waiting();
 9081        save.await;
 9082        assert_eq!(
 9083            editor.update(cx, |editor, cx| editor.text(cx)),
 9084            "one\ntwo\nthree\n"
 9085        );
 9086    }
 9087
 9088    // For non-dirty buffer and the corresponding settings, no formatting request should be sent
 9089    {
 9090        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9091        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9092            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9093                settings.save_non_dirty_buffers = Some(false);
 9094            });
 9095        });
 9096
 9097        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 9098            panic!("Should not be invoked on non-dirty buffer when configured so");
 9099        });
 9100        let save = editor
 9101            .update_in(cx, |editor, window, cx| {
 9102                editor.save(true, project.clone(), window, cx)
 9103            })
 9104            .unwrap();
 9105        cx.executor().start_waiting();
 9106        save.await;
 9107
 9108        assert_eq!(
 9109            editor.update(cx, |editor, cx| editor.text(cx)),
 9110            "one\ntwo\nthree\n"
 9111        );
 9112        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9113    }
 9114
 9115    cx.update_global::<SettingsStore, _>(|settings, cx| {
 9116        settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9117            settings.save_non_dirty_buffers = Some(false);
 9118        });
 9119    });
 9120    // Set rust language override and assert overridden tabsize is sent to language server
 9121    update_test_language_settings(cx, |settings| {
 9122        settings.languages.insert(
 9123            "Rust".into(),
 9124            LanguageSettingsContent {
 9125                tab_size: NonZeroU32::new(8),
 9126                ..Default::default()
 9127            },
 9128        );
 9129    });
 9130
 9131    {
 9132        editor.update_in(cx, |editor, window, cx| {
 9133            editor.set_text("somehting_new\n", window, cx)
 9134        });
 9135        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9136        let _formatting_request_signal = fake_server
 9137            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9138                assert_eq!(
 9139                    params.text_document.uri,
 9140                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9141                );
 9142                assert_eq!(params.options.tab_size, 8);
 9143                Ok(Some(vec![]))
 9144            });
 9145        let save = editor
 9146            .update_in(cx, |editor, window, cx| {
 9147                editor.save(true, project.clone(), window, cx)
 9148            })
 9149            .unwrap();
 9150        cx.executor().start_waiting();
 9151        save.await;
 9152    }
 9153}
 9154
 9155#[gpui::test]
 9156async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9157    init_test(cx, |_| {});
 9158
 9159    let cols = 4;
 9160    let rows = 10;
 9161    let sample_text_1 = sample_text(rows, cols, 'a');
 9162    assert_eq!(
 9163        sample_text_1,
 9164        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9165    );
 9166    let sample_text_2 = sample_text(rows, cols, 'l');
 9167    assert_eq!(
 9168        sample_text_2,
 9169        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9170    );
 9171    let sample_text_3 = sample_text(rows, cols, 'v');
 9172    assert_eq!(
 9173        sample_text_3,
 9174        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9175    );
 9176
 9177    let fs = FakeFs::new(cx.executor());
 9178    fs.insert_tree(
 9179        path!("/a"),
 9180        json!({
 9181            "main.rs": sample_text_1,
 9182            "other.rs": sample_text_2,
 9183            "lib.rs": sample_text_3,
 9184        }),
 9185    )
 9186    .await;
 9187
 9188    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9189    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9190    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9191
 9192    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9193    language_registry.add(rust_lang());
 9194    let mut fake_servers = language_registry.register_fake_lsp(
 9195        "Rust",
 9196        FakeLspAdapter {
 9197            capabilities: lsp::ServerCapabilities {
 9198                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9199                ..Default::default()
 9200            },
 9201            ..Default::default()
 9202        },
 9203    );
 9204
 9205    let worktree = project.update(cx, |project, cx| {
 9206        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9207        assert_eq!(worktrees.len(), 1);
 9208        worktrees.pop().unwrap()
 9209    });
 9210    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9211
 9212    let buffer_1 = project
 9213        .update(cx, |project, cx| {
 9214            project.open_buffer((worktree_id, "main.rs"), cx)
 9215        })
 9216        .await
 9217        .unwrap();
 9218    let buffer_2 = project
 9219        .update(cx, |project, cx| {
 9220            project.open_buffer((worktree_id, "other.rs"), cx)
 9221        })
 9222        .await
 9223        .unwrap();
 9224    let buffer_3 = project
 9225        .update(cx, |project, cx| {
 9226            project.open_buffer((worktree_id, "lib.rs"), cx)
 9227        })
 9228        .await
 9229        .unwrap();
 9230
 9231    let multi_buffer = cx.new(|cx| {
 9232        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9233        multi_buffer.push_excerpts(
 9234            buffer_1.clone(),
 9235            [
 9236                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9237                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9238                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9239            ],
 9240            cx,
 9241        );
 9242        multi_buffer.push_excerpts(
 9243            buffer_2.clone(),
 9244            [
 9245                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9246                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9247                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9248            ],
 9249            cx,
 9250        );
 9251        multi_buffer.push_excerpts(
 9252            buffer_3.clone(),
 9253            [
 9254                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9255                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9256                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9257            ],
 9258            cx,
 9259        );
 9260        multi_buffer
 9261    });
 9262    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9263        Editor::new(
 9264            EditorMode::full(),
 9265            multi_buffer,
 9266            Some(project.clone()),
 9267            window,
 9268            cx,
 9269        )
 9270    });
 9271
 9272    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9273        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9274            s.select_ranges(Some(1..2))
 9275        });
 9276        editor.insert("|one|two|three|", window, cx);
 9277    });
 9278    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9279    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9280        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9281            s.select_ranges(Some(60..70))
 9282        });
 9283        editor.insert("|four|five|six|", window, cx);
 9284    });
 9285    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9286
 9287    // First two buffers should be edited, but not the third one.
 9288    assert_eq!(
 9289        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9290        "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}",
 9291    );
 9292    buffer_1.update(cx, |buffer, _| {
 9293        assert!(buffer.is_dirty());
 9294        assert_eq!(
 9295            buffer.text(),
 9296            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9297        )
 9298    });
 9299    buffer_2.update(cx, |buffer, _| {
 9300        assert!(buffer.is_dirty());
 9301        assert_eq!(
 9302            buffer.text(),
 9303            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9304        )
 9305    });
 9306    buffer_3.update(cx, |buffer, _| {
 9307        assert!(!buffer.is_dirty());
 9308        assert_eq!(buffer.text(), sample_text_3,)
 9309    });
 9310    cx.executor().run_until_parked();
 9311
 9312    cx.executor().start_waiting();
 9313    let save = multi_buffer_editor
 9314        .update_in(cx, |editor, window, cx| {
 9315            editor.save(true, project.clone(), window, cx)
 9316        })
 9317        .unwrap();
 9318
 9319    let fake_server = fake_servers.next().await.unwrap();
 9320    fake_server
 9321        .server
 9322        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9323            Ok(Some(vec![lsp::TextEdit::new(
 9324                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9325                format!("[{} formatted]", params.text_document.uri),
 9326            )]))
 9327        })
 9328        .detach();
 9329    save.await;
 9330
 9331    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9332    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9333    assert_eq!(
 9334        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9335        uri!(
 9336            "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}"
 9337        ),
 9338    );
 9339    buffer_1.update(cx, |buffer, _| {
 9340        assert!(!buffer.is_dirty());
 9341        assert_eq!(
 9342            buffer.text(),
 9343            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9344        )
 9345    });
 9346    buffer_2.update(cx, |buffer, _| {
 9347        assert!(!buffer.is_dirty());
 9348        assert_eq!(
 9349            buffer.text(),
 9350            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9351        )
 9352    });
 9353    buffer_3.update(cx, |buffer, _| {
 9354        assert!(!buffer.is_dirty());
 9355        assert_eq!(buffer.text(), sample_text_3,)
 9356    });
 9357}
 9358
 9359#[gpui::test]
 9360async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9361    init_test(cx, |_| {});
 9362
 9363    let fs = FakeFs::new(cx.executor());
 9364    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9365
 9366    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9367
 9368    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9369    language_registry.add(rust_lang());
 9370    let mut fake_servers = language_registry.register_fake_lsp(
 9371        "Rust",
 9372        FakeLspAdapter {
 9373            capabilities: lsp::ServerCapabilities {
 9374                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9375                ..Default::default()
 9376            },
 9377            ..Default::default()
 9378        },
 9379    );
 9380
 9381    let buffer = project
 9382        .update(cx, |project, cx| {
 9383            project.open_local_buffer(path!("/file.rs"), cx)
 9384        })
 9385        .await
 9386        .unwrap();
 9387
 9388    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9389    let (editor, cx) = cx.add_window_view(|window, cx| {
 9390        build_editor_with_project(project.clone(), buffer, window, cx)
 9391    });
 9392    editor.update_in(cx, |editor, window, cx| {
 9393        editor.set_text("one\ntwo\nthree\n", window, cx)
 9394    });
 9395    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9396
 9397    cx.executor().start_waiting();
 9398    let fake_server = fake_servers.next().await.unwrap();
 9399
 9400    let save = editor
 9401        .update_in(cx, |editor, window, cx| {
 9402            editor.save(true, project.clone(), window, cx)
 9403        })
 9404        .unwrap();
 9405    fake_server
 9406        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9407            assert_eq!(
 9408                params.text_document.uri,
 9409                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9410            );
 9411            assert_eq!(params.options.tab_size, 4);
 9412            Ok(Some(vec![lsp::TextEdit::new(
 9413                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9414                ", ".to_string(),
 9415            )]))
 9416        })
 9417        .next()
 9418        .await;
 9419    cx.executor().start_waiting();
 9420    save.await;
 9421    assert_eq!(
 9422        editor.update(cx, |editor, cx| editor.text(cx)),
 9423        "one, two\nthree\n"
 9424    );
 9425    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9426
 9427    editor.update_in(cx, |editor, window, cx| {
 9428        editor.set_text("one\ntwo\nthree\n", window, cx)
 9429    });
 9430    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9431
 9432    // Ensure we can still save even if formatting hangs.
 9433    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9434        move |params, _| async move {
 9435            assert_eq!(
 9436                params.text_document.uri,
 9437                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9438            );
 9439            futures::future::pending::<()>().await;
 9440            unreachable!()
 9441        },
 9442    );
 9443    let save = editor
 9444        .update_in(cx, |editor, window, cx| {
 9445            editor.save(true, project.clone(), window, cx)
 9446        })
 9447        .unwrap();
 9448    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9449    cx.executor().start_waiting();
 9450    save.await;
 9451    assert_eq!(
 9452        editor.update(cx, |editor, cx| editor.text(cx)),
 9453        "one\ntwo\nthree\n"
 9454    );
 9455    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9456
 9457    // For non-dirty buffer, a formatting request should be sent anyway with the default settings
 9458    // where non-dirty singleton buffers are saved and formatted anyway.
 9459    let save = editor
 9460        .update_in(cx, |editor, window, cx| {
 9461            editor.save(true, project.clone(), window, cx)
 9462        })
 9463        .unwrap();
 9464    fake_server
 9465        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9466            assert_eq!(
 9467                params.text_document.uri,
 9468                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9469            );
 9470            assert_eq!(params.options.tab_size, 4);
 9471            Ok(Some(vec![lsp::TextEdit::new(
 9472                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9473                ", ".to_string(),
 9474            )]))
 9475        })
 9476        .next()
 9477        .await;
 9478    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9479    cx.executor().start_waiting();
 9480    save.await;
 9481    assert_eq!(
 9482        editor.update(cx, |editor, cx| editor.text(cx)),
 9483        "one, two\nthree\n"
 9484    );
 9485    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9486
 9487    // Set Rust language override and assert overridden tabsize is sent to language server
 9488    update_test_language_settings(cx, |settings| {
 9489        settings.languages.insert(
 9490            "Rust".into(),
 9491            LanguageSettingsContent {
 9492                tab_size: NonZeroU32::new(8),
 9493                ..Default::default()
 9494            },
 9495        );
 9496    });
 9497
 9498    editor.update_in(cx, |editor, window, cx| {
 9499        editor.set_text("somehting_new\n", window, cx)
 9500    });
 9501    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9502    let save = editor
 9503        .update_in(cx, |editor, window, cx| {
 9504            editor.save(true, project.clone(), window, cx)
 9505        })
 9506        .unwrap();
 9507    fake_server
 9508        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9509            assert_eq!(
 9510                params.text_document.uri,
 9511                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9512            );
 9513            assert_eq!(params.options.tab_size, 8);
 9514            Ok(Some(Vec::new()))
 9515        })
 9516        .next()
 9517        .await;
 9518    save.await;
 9519}
 9520
 9521#[gpui::test]
 9522async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9523    init_test(cx, |settings| {
 9524        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9525            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9526        ))
 9527    });
 9528
 9529    let fs = FakeFs::new(cx.executor());
 9530    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9531
 9532    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9533
 9534    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9535    language_registry.add(Arc::new(Language::new(
 9536        LanguageConfig {
 9537            name: "Rust".into(),
 9538            matcher: LanguageMatcher {
 9539                path_suffixes: vec!["rs".to_string()],
 9540                ..Default::default()
 9541            },
 9542            ..LanguageConfig::default()
 9543        },
 9544        Some(tree_sitter_rust::LANGUAGE.into()),
 9545    )));
 9546    update_test_language_settings(cx, |settings| {
 9547        // Enable Prettier formatting for the same buffer, and ensure
 9548        // LSP is called instead of Prettier.
 9549        settings.defaults.prettier = Some(PrettierSettings {
 9550            allowed: true,
 9551            ..PrettierSettings::default()
 9552        });
 9553    });
 9554    let mut fake_servers = language_registry.register_fake_lsp(
 9555        "Rust",
 9556        FakeLspAdapter {
 9557            capabilities: lsp::ServerCapabilities {
 9558                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9559                ..Default::default()
 9560            },
 9561            ..Default::default()
 9562        },
 9563    );
 9564
 9565    let buffer = project
 9566        .update(cx, |project, cx| {
 9567            project.open_local_buffer(path!("/file.rs"), cx)
 9568        })
 9569        .await
 9570        .unwrap();
 9571
 9572    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9573    let (editor, cx) = cx.add_window_view(|window, cx| {
 9574        build_editor_with_project(project.clone(), buffer, window, cx)
 9575    });
 9576    editor.update_in(cx, |editor, window, cx| {
 9577        editor.set_text("one\ntwo\nthree\n", window, cx)
 9578    });
 9579
 9580    cx.executor().start_waiting();
 9581    let fake_server = fake_servers.next().await.unwrap();
 9582
 9583    let format = editor
 9584        .update_in(cx, |editor, window, cx| {
 9585            editor.perform_format(
 9586                project.clone(),
 9587                FormatTrigger::Manual,
 9588                FormatTarget::Buffers,
 9589                window,
 9590                cx,
 9591            )
 9592        })
 9593        .unwrap();
 9594    fake_server
 9595        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9596            assert_eq!(
 9597                params.text_document.uri,
 9598                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9599            );
 9600            assert_eq!(params.options.tab_size, 4);
 9601            Ok(Some(vec![lsp::TextEdit::new(
 9602                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9603                ", ".to_string(),
 9604            )]))
 9605        })
 9606        .next()
 9607        .await;
 9608    cx.executor().start_waiting();
 9609    format.await;
 9610    assert_eq!(
 9611        editor.update(cx, |editor, cx| editor.text(cx)),
 9612        "one, two\nthree\n"
 9613    );
 9614
 9615    editor.update_in(cx, |editor, window, cx| {
 9616        editor.set_text("one\ntwo\nthree\n", window, cx)
 9617    });
 9618    // Ensure we don't lock if formatting hangs.
 9619    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9620        move |params, _| async move {
 9621            assert_eq!(
 9622                params.text_document.uri,
 9623                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9624            );
 9625            futures::future::pending::<()>().await;
 9626            unreachable!()
 9627        },
 9628    );
 9629    let format = editor
 9630        .update_in(cx, |editor, window, cx| {
 9631            editor.perform_format(
 9632                project,
 9633                FormatTrigger::Manual,
 9634                FormatTarget::Buffers,
 9635                window,
 9636                cx,
 9637            )
 9638        })
 9639        .unwrap();
 9640    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9641    cx.executor().start_waiting();
 9642    format.await;
 9643    assert_eq!(
 9644        editor.update(cx, |editor, cx| editor.text(cx)),
 9645        "one\ntwo\nthree\n"
 9646    );
 9647}
 9648
 9649#[gpui::test]
 9650async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9651    init_test(cx, |settings| {
 9652        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9653        settings.defaults.formatter =
 9654            Some(language_settings::SelectedFormatter::List(FormatterList(
 9655                vec![
 9656                    Formatter::LanguageServer { name: None },
 9657                    Formatter::CodeActions(
 9658                        [
 9659                            ("code-action-1".into(), true),
 9660                            ("code-action-2".into(), true),
 9661                        ]
 9662                        .into_iter()
 9663                        .collect(),
 9664                    ),
 9665                ]
 9666                .into(),
 9667            )))
 9668    });
 9669
 9670    let fs = FakeFs::new(cx.executor());
 9671    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9672        .await;
 9673
 9674    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9675    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9676    language_registry.add(rust_lang());
 9677
 9678    let mut fake_servers = language_registry.register_fake_lsp(
 9679        "Rust",
 9680        FakeLspAdapter {
 9681            capabilities: lsp::ServerCapabilities {
 9682                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9683                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9684                    commands: vec!["the-command-for-code-action-1".into()],
 9685                    ..Default::default()
 9686                }),
 9687                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9688                ..Default::default()
 9689            },
 9690            ..Default::default()
 9691        },
 9692    );
 9693
 9694    let buffer = project
 9695        .update(cx, |project, cx| {
 9696            project.open_local_buffer(path!("/file.rs"), cx)
 9697        })
 9698        .await
 9699        .unwrap();
 9700
 9701    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9702    let (editor, cx) = cx.add_window_view(|window, cx| {
 9703        build_editor_with_project(project.clone(), buffer, window, cx)
 9704    });
 9705
 9706    cx.executor().start_waiting();
 9707
 9708    let fake_server = fake_servers.next().await.unwrap();
 9709    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9710        move |_params, _| async move {
 9711            Ok(Some(vec![lsp::TextEdit::new(
 9712                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9713                "applied-formatting\n".to_string(),
 9714            )]))
 9715        },
 9716    );
 9717    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9718        move |params, _| async move {
 9719            assert_eq!(
 9720                params.context.only,
 9721                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9722            );
 9723            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9724            Ok(Some(vec![
 9725                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9726                    kind: Some("code-action-1".into()),
 9727                    edit: Some(lsp::WorkspaceEdit::new(
 9728                        [(
 9729                            uri.clone(),
 9730                            vec![lsp::TextEdit::new(
 9731                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9732                                "applied-code-action-1-edit\n".to_string(),
 9733                            )],
 9734                        )]
 9735                        .into_iter()
 9736                        .collect(),
 9737                    )),
 9738                    command: Some(lsp::Command {
 9739                        command: "the-command-for-code-action-1".into(),
 9740                        ..Default::default()
 9741                    }),
 9742                    ..Default::default()
 9743                }),
 9744                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9745                    kind: Some("code-action-2".into()),
 9746                    edit: Some(lsp::WorkspaceEdit::new(
 9747                        [(
 9748                            uri.clone(),
 9749                            vec![lsp::TextEdit::new(
 9750                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9751                                "applied-code-action-2-edit\n".to_string(),
 9752                            )],
 9753                        )]
 9754                        .into_iter()
 9755                        .collect(),
 9756                    )),
 9757                    ..Default::default()
 9758                }),
 9759            ]))
 9760        },
 9761    );
 9762
 9763    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9764        move |params, _| async move { Ok(params) }
 9765    });
 9766
 9767    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9768    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9769        let fake = fake_server.clone();
 9770        let lock = command_lock.clone();
 9771        move |params, _| {
 9772            assert_eq!(params.command, "the-command-for-code-action-1");
 9773            let fake = fake.clone();
 9774            let lock = lock.clone();
 9775            async move {
 9776                lock.lock().await;
 9777                fake.server
 9778                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9779                        label: None,
 9780                        edit: lsp::WorkspaceEdit {
 9781                            changes: Some(
 9782                                [(
 9783                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9784                                    vec![lsp::TextEdit {
 9785                                        range: lsp::Range::new(
 9786                                            lsp::Position::new(0, 0),
 9787                                            lsp::Position::new(0, 0),
 9788                                        ),
 9789                                        new_text: "applied-code-action-1-command\n".into(),
 9790                                    }],
 9791                                )]
 9792                                .into_iter()
 9793                                .collect(),
 9794                            ),
 9795                            ..Default::default()
 9796                        },
 9797                    })
 9798                    .await
 9799                    .into_response()
 9800                    .unwrap();
 9801                Ok(Some(json!(null)))
 9802            }
 9803        }
 9804    });
 9805
 9806    cx.executor().start_waiting();
 9807    editor
 9808        .update_in(cx, |editor, window, cx| {
 9809            editor.perform_format(
 9810                project.clone(),
 9811                FormatTrigger::Manual,
 9812                FormatTarget::Buffers,
 9813                window,
 9814                cx,
 9815            )
 9816        })
 9817        .unwrap()
 9818        .await;
 9819    editor.update(cx, |editor, cx| {
 9820        assert_eq!(
 9821            editor.text(cx),
 9822            r#"
 9823                applied-code-action-2-edit
 9824                applied-code-action-1-command
 9825                applied-code-action-1-edit
 9826                applied-formatting
 9827                one
 9828                two
 9829                three
 9830            "#
 9831            .unindent()
 9832        );
 9833    });
 9834
 9835    editor.update_in(cx, |editor, window, cx| {
 9836        editor.undo(&Default::default(), window, cx);
 9837        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9838    });
 9839
 9840    // Perform a manual edit while waiting for an LSP command
 9841    // that's being run as part of a formatting code action.
 9842    let lock_guard = command_lock.lock().await;
 9843    let format = editor
 9844        .update_in(cx, |editor, window, cx| {
 9845            editor.perform_format(
 9846                project.clone(),
 9847                FormatTrigger::Manual,
 9848                FormatTarget::Buffers,
 9849                window,
 9850                cx,
 9851            )
 9852        })
 9853        .unwrap();
 9854    cx.run_until_parked();
 9855    editor.update(cx, |editor, cx| {
 9856        assert_eq!(
 9857            editor.text(cx),
 9858            r#"
 9859                applied-code-action-1-edit
 9860                applied-formatting
 9861                one
 9862                two
 9863                three
 9864            "#
 9865            .unindent()
 9866        );
 9867
 9868        editor.buffer.update(cx, |buffer, cx| {
 9869            let ix = buffer.len(cx);
 9870            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9871        });
 9872    });
 9873
 9874    // Allow the LSP command to proceed. Because the buffer was edited,
 9875    // the second code action will not be run.
 9876    drop(lock_guard);
 9877    format.await;
 9878    editor.update_in(cx, |editor, window, cx| {
 9879        assert_eq!(
 9880            editor.text(cx),
 9881            r#"
 9882                applied-code-action-1-command
 9883                applied-code-action-1-edit
 9884                applied-formatting
 9885                one
 9886                two
 9887                three
 9888                edited
 9889            "#
 9890            .unindent()
 9891        );
 9892
 9893        // The manual edit is undone first, because it is the last thing the user did
 9894        // (even though the command completed afterwards).
 9895        editor.undo(&Default::default(), window, cx);
 9896        assert_eq!(
 9897            editor.text(cx),
 9898            r#"
 9899                applied-code-action-1-command
 9900                applied-code-action-1-edit
 9901                applied-formatting
 9902                one
 9903                two
 9904                three
 9905            "#
 9906            .unindent()
 9907        );
 9908
 9909        // All the formatting (including the command, which completed after the manual edit)
 9910        // is undone together.
 9911        editor.undo(&Default::default(), window, cx);
 9912        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9913    });
 9914}
 9915
 9916#[gpui::test]
 9917async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9918    init_test(cx, |settings| {
 9919        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9920            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9921        ))
 9922    });
 9923
 9924    let fs = FakeFs::new(cx.executor());
 9925    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9926
 9927    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9928
 9929    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9930    language_registry.add(Arc::new(Language::new(
 9931        LanguageConfig {
 9932            name: "TypeScript".into(),
 9933            matcher: LanguageMatcher {
 9934                path_suffixes: vec!["ts".to_string()],
 9935                ..Default::default()
 9936            },
 9937            ..LanguageConfig::default()
 9938        },
 9939        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9940    )));
 9941    update_test_language_settings(cx, |settings| {
 9942        settings.defaults.prettier = Some(PrettierSettings {
 9943            allowed: true,
 9944            ..PrettierSettings::default()
 9945        });
 9946    });
 9947    let mut fake_servers = language_registry.register_fake_lsp(
 9948        "TypeScript",
 9949        FakeLspAdapter {
 9950            capabilities: lsp::ServerCapabilities {
 9951                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9952                ..Default::default()
 9953            },
 9954            ..Default::default()
 9955        },
 9956    );
 9957
 9958    let buffer = project
 9959        .update(cx, |project, cx| {
 9960            project.open_local_buffer(path!("/file.ts"), cx)
 9961        })
 9962        .await
 9963        .unwrap();
 9964
 9965    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9966    let (editor, cx) = cx.add_window_view(|window, cx| {
 9967        build_editor_with_project(project.clone(), buffer, window, cx)
 9968    });
 9969    editor.update_in(cx, |editor, window, cx| {
 9970        editor.set_text(
 9971            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9972            window,
 9973            cx,
 9974        )
 9975    });
 9976
 9977    cx.executor().start_waiting();
 9978    let fake_server = fake_servers.next().await.unwrap();
 9979
 9980    let format = editor
 9981        .update_in(cx, |editor, window, cx| {
 9982            editor.perform_code_action_kind(
 9983                project.clone(),
 9984                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9985                window,
 9986                cx,
 9987            )
 9988        })
 9989        .unwrap();
 9990    fake_server
 9991        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9992            assert_eq!(
 9993                params.text_document.uri,
 9994                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9995            );
 9996            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9997                lsp::CodeAction {
 9998                    title: "Organize Imports".to_string(),
 9999                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10000                    edit: Some(lsp::WorkspaceEdit {
10001                        changes: Some(
10002                            [(
10003                                params.text_document.uri.clone(),
10004                                vec![lsp::TextEdit::new(
10005                                    lsp::Range::new(
10006                                        lsp::Position::new(1, 0),
10007                                        lsp::Position::new(2, 0),
10008                                    ),
10009                                    "".to_string(),
10010                                )],
10011                            )]
10012                            .into_iter()
10013                            .collect(),
10014                        ),
10015                        ..Default::default()
10016                    }),
10017                    ..Default::default()
10018                },
10019            )]))
10020        })
10021        .next()
10022        .await;
10023    cx.executor().start_waiting();
10024    format.await;
10025    assert_eq!(
10026        editor.update(cx, |editor, cx| editor.text(cx)),
10027        "import { a } from 'module';\n\nconst x = a;\n"
10028    );
10029
10030    editor.update_in(cx, |editor, window, cx| {
10031        editor.set_text(
10032            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10033            window,
10034            cx,
10035        )
10036    });
10037    // Ensure we don't lock if code action hangs.
10038    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10039        move |params, _| async move {
10040            assert_eq!(
10041                params.text_document.uri,
10042                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10043            );
10044            futures::future::pending::<()>().await;
10045            unreachable!()
10046        },
10047    );
10048    let format = editor
10049        .update_in(cx, |editor, window, cx| {
10050            editor.perform_code_action_kind(
10051                project,
10052                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10053                window,
10054                cx,
10055            )
10056        })
10057        .unwrap();
10058    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10059    cx.executor().start_waiting();
10060    format.await;
10061    assert_eq!(
10062        editor.update(cx, |editor, cx| editor.text(cx)),
10063        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10064    );
10065}
10066
10067#[gpui::test]
10068async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10069    init_test(cx, |_| {});
10070
10071    let mut cx = EditorLspTestContext::new_rust(
10072        lsp::ServerCapabilities {
10073            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10074            ..Default::default()
10075        },
10076        cx,
10077    )
10078    .await;
10079
10080    cx.set_state(indoc! {"
10081        one.twoˇ
10082    "});
10083
10084    // The format request takes a long time. When it completes, it inserts
10085    // a newline and an indent before the `.`
10086    cx.lsp
10087        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10088            let executor = cx.background_executor().clone();
10089            async move {
10090                executor.timer(Duration::from_millis(100)).await;
10091                Ok(Some(vec![lsp::TextEdit {
10092                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10093                    new_text: "\n    ".into(),
10094                }]))
10095            }
10096        });
10097
10098    // Submit a format request.
10099    let format_1 = cx
10100        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10101        .unwrap();
10102    cx.executor().run_until_parked();
10103
10104    // Submit a second format request.
10105    let format_2 = cx
10106        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10107        .unwrap();
10108    cx.executor().run_until_parked();
10109
10110    // Wait for both format requests to complete
10111    cx.executor().advance_clock(Duration::from_millis(200));
10112    cx.executor().start_waiting();
10113    format_1.await.unwrap();
10114    cx.executor().start_waiting();
10115    format_2.await.unwrap();
10116
10117    // The formatting edits only happens once.
10118    cx.assert_editor_state(indoc! {"
10119        one
10120            .twoˇ
10121    "});
10122}
10123
10124#[gpui::test]
10125async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10126    init_test(cx, |settings| {
10127        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10128    });
10129
10130    let mut cx = EditorLspTestContext::new_rust(
10131        lsp::ServerCapabilities {
10132            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10133            ..Default::default()
10134        },
10135        cx,
10136    )
10137    .await;
10138
10139    // Set up a buffer white some trailing whitespace and no trailing newline.
10140    cx.set_state(
10141        &[
10142            "one ",   //
10143            "twoˇ",   //
10144            "three ", //
10145            "four",   //
10146        ]
10147        .join("\n"),
10148    );
10149
10150    // Submit a format request.
10151    let format = cx
10152        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10153        .unwrap();
10154
10155    // Record which buffer changes have been sent to the language server
10156    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10157    cx.lsp
10158        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10159            let buffer_changes = buffer_changes.clone();
10160            move |params, _| {
10161                buffer_changes.lock().extend(
10162                    params
10163                        .content_changes
10164                        .into_iter()
10165                        .map(|e| (e.range.unwrap(), e.text)),
10166                );
10167            }
10168        });
10169
10170    // Handle formatting requests to the language server.
10171    cx.lsp
10172        .set_request_handler::<lsp::request::Formatting, _, _>({
10173            let buffer_changes = buffer_changes.clone();
10174            move |_, _| {
10175                // When formatting is requested, trailing whitespace has already been stripped,
10176                // and the trailing newline has already been added.
10177                assert_eq!(
10178                    &buffer_changes.lock()[1..],
10179                    &[
10180                        (
10181                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10182                            "".into()
10183                        ),
10184                        (
10185                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10186                            "".into()
10187                        ),
10188                        (
10189                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10190                            "\n".into()
10191                        ),
10192                    ]
10193                );
10194
10195                // Insert blank lines between each line of the buffer.
10196                async move {
10197                    Ok(Some(vec![
10198                        lsp::TextEdit {
10199                            range: lsp::Range::new(
10200                                lsp::Position::new(1, 0),
10201                                lsp::Position::new(1, 0),
10202                            ),
10203                            new_text: "\n".into(),
10204                        },
10205                        lsp::TextEdit {
10206                            range: lsp::Range::new(
10207                                lsp::Position::new(2, 0),
10208                                lsp::Position::new(2, 0),
10209                            ),
10210                            new_text: "\n".into(),
10211                        },
10212                    ]))
10213                }
10214            }
10215        });
10216
10217    // After formatting the buffer, the trailing whitespace is stripped,
10218    // a newline is appended, and the edits provided by the language server
10219    // have been applied.
10220    format.await.unwrap();
10221    cx.assert_editor_state(
10222        &[
10223            "one",   //
10224            "",      //
10225            "twoˇ",  //
10226            "",      //
10227            "three", //
10228            "four",  //
10229            "",      //
10230        ]
10231        .join("\n"),
10232    );
10233
10234    // Undoing the formatting undoes the trailing whitespace removal, the
10235    // trailing newline, and the LSP edits.
10236    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10237    cx.assert_editor_state(
10238        &[
10239            "one ",   //
10240            "twoˇ",   //
10241            "three ", //
10242            "four",   //
10243        ]
10244        .join("\n"),
10245    );
10246}
10247
10248#[gpui::test]
10249async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10250    cx: &mut TestAppContext,
10251) {
10252    init_test(cx, |_| {});
10253
10254    cx.update(|cx| {
10255        cx.update_global::<SettingsStore, _>(|settings, cx| {
10256            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10257                settings.auto_signature_help = Some(true);
10258            });
10259        });
10260    });
10261
10262    let mut cx = EditorLspTestContext::new_rust(
10263        lsp::ServerCapabilities {
10264            signature_help_provider: Some(lsp::SignatureHelpOptions {
10265                ..Default::default()
10266            }),
10267            ..Default::default()
10268        },
10269        cx,
10270    )
10271    .await;
10272
10273    let language = Language::new(
10274        LanguageConfig {
10275            name: "Rust".into(),
10276            brackets: BracketPairConfig {
10277                pairs: vec![
10278                    BracketPair {
10279                        start: "{".to_string(),
10280                        end: "}".to_string(),
10281                        close: true,
10282                        surround: true,
10283                        newline: true,
10284                    },
10285                    BracketPair {
10286                        start: "(".to_string(),
10287                        end: ")".to_string(),
10288                        close: true,
10289                        surround: true,
10290                        newline: true,
10291                    },
10292                    BracketPair {
10293                        start: "/*".to_string(),
10294                        end: " */".to_string(),
10295                        close: true,
10296                        surround: true,
10297                        newline: true,
10298                    },
10299                    BracketPair {
10300                        start: "[".to_string(),
10301                        end: "]".to_string(),
10302                        close: false,
10303                        surround: false,
10304                        newline: true,
10305                    },
10306                    BracketPair {
10307                        start: "\"".to_string(),
10308                        end: "\"".to_string(),
10309                        close: true,
10310                        surround: true,
10311                        newline: false,
10312                    },
10313                    BracketPair {
10314                        start: "<".to_string(),
10315                        end: ">".to_string(),
10316                        close: false,
10317                        surround: true,
10318                        newline: true,
10319                    },
10320                ],
10321                ..Default::default()
10322            },
10323            autoclose_before: "})]".to_string(),
10324            ..Default::default()
10325        },
10326        Some(tree_sitter_rust::LANGUAGE.into()),
10327    );
10328    let language = Arc::new(language);
10329
10330    cx.language_registry().add(language.clone());
10331    cx.update_buffer(|buffer, cx| {
10332        buffer.set_language(Some(language), cx);
10333    });
10334
10335    cx.set_state(
10336        &r#"
10337            fn main() {
10338                sampleˇ
10339            }
10340        "#
10341        .unindent(),
10342    );
10343
10344    cx.update_editor(|editor, window, cx| {
10345        editor.handle_input("(", window, cx);
10346    });
10347    cx.assert_editor_state(
10348        &"
10349            fn main() {
10350                sample(ˇ)
10351            }
10352        "
10353        .unindent(),
10354    );
10355
10356    let mocked_response = lsp::SignatureHelp {
10357        signatures: vec![lsp::SignatureInformation {
10358            label: "fn sample(param1: u8, param2: u8)".to_string(),
10359            documentation: None,
10360            parameters: Some(vec![
10361                lsp::ParameterInformation {
10362                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10363                    documentation: None,
10364                },
10365                lsp::ParameterInformation {
10366                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10367                    documentation: None,
10368                },
10369            ]),
10370            active_parameter: None,
10371        }],
10372        active_signature: Some(0),
10373        active_parameter: Some(0),
10374    };
10375    handle_signature_help_request(&mut cx, mocked_response).await;
10376
10377    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10378        .await;
10379
10380    cx.editor(|editor, _, _| {
10381        let signature_help_state = editor.signature_help_state.popover().cloned();
10382        assert_eq!(
10383            signature_help_state.unwrap().label,
10384            "param1: u8, param2: u8"
10385        );
10386    });
10387}
10388
10389#[gpui::test]
10390async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10391    init_test(cx, |_| {});
10392
10393    cx.update(|cx| {
10394        cx.update_global::<SettingsStore, _>(|settings, cx| {
10395            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10396                settings.auto_signature_help = Some(false);
10397                settings.show_signature_help_after_edits = Some(false);
10398            });
10399        });
10400    });
10401
10402    let mut cx = EditorLspTestContext::new_rust(
10403        lsp::ServerCapabilities {
10404            signature_help_provider: Some(lsp::SignatureHelpOptions {
10405                ..Default::default()
10406            }),
10407            ..Default::default()
10408        },
10409        cx,
10410    )
10411    .await;
10412
10413    let language = Language::new(
10414        LanguageConfig {
10415            name: "Rust".into(),
10416            brackets: BracketPairConfig {
10417                pairs: vec![
10418                    BracketPair {
10419                        start: "{".to_string(),
10420                        end: "}".to_string(),
10421                        close: true,
10422                        surround: true,
10423                        newline: true,
10424                    },
10425                    BracketPair {
10426                        start: "(".to_string(),
10427                        end: ")".to_string(),
10428                        close: true,
10429                        surround: true,
10430                        newline: true,
10431                    },
10432                    BracketPair {
10433                        start: "/*".to_string(),
10434                        end: " */".to_string(),
10435                        close: true,
10436                        surround: true,
10437                        newline: true,
10438                    },
10439                    BracketPair {
10440                        start: "[".to_string(),
10441                        end: "]".to_string(),
10442                        close: false,
10443                        surround: false,
10444                        newline: true,
10445                    },
10446                    BracketPair {
10447                        start: "\"".to_string(),
10448                        end: "\"".to_string(),
10449                        close: true,
10450                        surround: true,
10451                        newline: false,
10452                    },
10453                    BracketPair {
10454                        start: "<".to_string(),
10455                        end: ">".to_string(),
10456                        close: false,
10457                        surround: true,
10458                        newline: true,
10459                    },
10460                ],
10461                ..Default::default()
10462            },
10463            autoclose_before: "})]".to_string(),
10464            ..Default::default()
10465        },
10466        Some(tree_sitter_rust::LANGUAGE.into()),
10467    );
10468    let language = Arc::new(language);
10469
10470    cx.language_registry().add(language.clone());
10471    cx.update_buffer(|buffer, cx| {
10472        buffer.set_language(Some(language), cx);
10473    });
10474
10475    // Ensure that signature_help is not called when no signature help is enabled.
10476    cx.set_state(
10477        &r#"
10478            fn main() {
10479                sampleˇ
10480            }
10481        "#
10482        .unindent(),
10483    );
10484    cx.update_editor(|editor, window, cx| {
10485        editor.handle_input("(", window, cx);
10486    });
10487    cx.assert_editor_state(
10488        &"
10489            fn main() {
10490                sample(ˇ)
10491            }
10492        "
10493        .unindent(),
10494    );
10495    cx.editor(|editor, _, _| {
10496        assert!(editor.signature_help_state.task().is_none());
10497    });
10498
10499    let mocked_response = lsp::SignatureHelp {
10500        signatures: vec![lsp::SignatureInformation {
10501            label: "fn sample(param1: u8, param2: u8)".to_string(),
10502            documentation: None,
10503            parameters: Some(vec![
10504                lsp::ParameterInformation {
10505                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10506                    documentation: None,
10507                },
10508                lsp::ParameterInformation {
10509                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10510                    documentation: None,
10511                },
10512            ]),
10513            active_parameter: None,
10514        }],
10515        active_signature: Some(0),
10516        active_parameter: Some(0),
10517    };
10518
10519    // Ensure that signature_help is called when enabled afte edits
10520    cx.update(|_, cx| {
10521        cx.update_global::<SettingsStore, _>(|settings, cx| {
10522            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10523                settings.auto_signature_help = Some(false);
10524                settings.show_signature_help_after_edits = Some(true);
10525            });
10526        });
10527    });
10528    cx.set_state(
10529        &r#"
10530            fn main() {
10531                sampleˇ
10532            }
10533        "#
10534        .unindent(),
10535    );
10536    cx.update_editor(|editor, window, cx| {
10537        editor.handle_input("(", window, cx);
10538    });
10539    cx.assert_editor_state(
10540        &"
10541            fn main() {
10542                sample(ˇ)
10543            }
10544        "
10545        .unindent(),
10546    );
10547    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10548    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10549        .await;
10550    cx.update_editor(|editor, _, _| {
10551        let signature_help_state = editor.signature_help_state.popover().cloned();
10552        assert!(signature_help_state.is_some());
10553        assert_eq!(
10554            signature_help_state.unwrap().label,
10555            "param1: u8, param2: u8"
10556        );
10557        editor.signature_help_state = SignatureHelpState::default();
10558    });
10559
10560    // Ensure that signature_help is called when auto signature help override is enabled
10561    cx.update(|_, cx| {
10562        cx.update_global::<SettingsStore, _>(|settings, cx| {
10563            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10564                settings.auto_signature_help = Some(true);
10565                settings.show_signature_help_after_edits = Some(false);
10566            });
10567        });
10568    });
10569    cx.set_state(
10570        &r#"
10571            fn main() {
10572                sampleˇ
10573            }
10574        "#
10575        .unindent(),
10576    );
10577    cx.update_editor(|editor, window, cx| {
10578        editor.handle_input("(", window, cx);
10579    });
10580    cx.assert_editor_state(
10581        &"
10582            fn main() {
10583                sample(ˇ)
10584            }
10585        "
10586        .unindent(),
10587    );
10588    handle_signature_help_request(&mut cx, mocked_response).await;
10589    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10590        .await;
10591    cx.editor(|editor, _, _| {
10592        let signature_help_state = editor.signature_help_state.popover().cloned();
10593        assert!(signature_help_state.is_some());
10594        assert_eq!(
10595            signature_help_state.unwrap().label,
10596            "param1: u8, param2: u8"
10597        );
10598    });
10599}
10600
10601#[gpui::test]
10602async fn test_signature_help(cx: &mut TestAppContext) {
10603    init_test(cx, |_| {});
10604    cx.update(|cx| {
10605        cx.update_global::<SettingsStore, _>(|settings, cx| {
10606            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10607                settings.auto_signature_help = Some(true);
10608            });
10609        });
10610    });
10611
10612    let mut cx = EditorLspTestContext::new_rust(
10613        lsp::ServerCapabilities {
10614            signature_help_provider: Some(lsp::SignatureHelpOptions {
10615                ..Default::default()
10616            }),
10617            ..Default::default()
10618        },
10619        cx,
10620    )
10621    .await;
10622
10623    // A test that directly calls `show_signature_help`
10624    cx.update_editor(|editor, window, cx| {
10625        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10626    });
10627
10628    let mocked_response = lsp::SignatureHelp {
10629        signatures: vec![lsp::SignatureInformation {
10630            label: "fn sample(param1: u8, param2: u8)".to_string(),
10631            documentation: None,
10632            parameters: Some(vec![
10633                lsp::ParameterInformation {
10634                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10635                    documentation: None,
10636                },
10637                lsp::ParameterInformation {
10638                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10639                    documentation: None,
10640                },
10641            ]),
10642            active_parameter: None,
10643        }],
10644        active_signature: Some(0),
10645        active_parameter: Some(0),
10646    };
10647    handle_signature_help_request(&mut cx, mocked_response).await;
10648
10649    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10650        .await;
10651
10652    cx.editor(|editor, _, _| {
10653        let signature_help_state = editor.signature_help_state.popover().cloned();
10654        assert!(signature_help_state.is_some());
10655        assert_eq!(
10656            signature_help_state.unwrap().label,
10657            "param1: u8, param2: u8"
10658        );
10659    });
10660
10661    // When exiting outside from inside the brackets, `signature_help` is closed.
10662    cx.set_state(indoc! {"
10663        fn main() {
10664            sample(ˇ);
10665        }
10666
10667        fn sample(param1: u8, param2: u8) {}
10668    "});
10669
10670    cx.update_editor(|editor, window, cx| {
10671        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10672    });
10673
10674    let mocked_response = lsp::SignatureHelp {
10675        signatures: Vec::new(),
10676        active_signature: None,
10677        active_parameter: None,
10678    };
10679    handle_signature_help_request(&mut cx, mocked_response).await;
10680
10681    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10682        .await;
10683
10684    cx.editor(|editor, _, _| {
10685        assert!(!editor.signature_help_state.is_shown());
10686    });
10687
10688    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10689    cx.set_state(indoc! {"
10690        fn main() {
10691            sample(ˇ);
10692        }
10693
10694        fn sample(param1: u8, param2: u8) {}
10695    "});
10696
10697    let mocked_response = lsp::SignatureHelp {
10698        signatures: vec![lsp::SignatureInformation {
10699            label: "fn sample(param1: u8, param2: u8)".to_string(),
10700            documentation: None,
10701            parameters: Some(vec![
10702                lsp::ParameterInformation {
10703                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10704                    documentation: None,
10705                },
10706                lsp::ParameterInformation {
10707                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10708                    documentation: None,
10709                },
10710            ]),
10711            active_parameter: None,
10712        }],
10713        active_signature: Some(0),
10714        active_parameter: Some(0),
10715    };
10716    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10717    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10718        .await;
10719    cx.editor(|editor, _, _| {
10720        assert!(editor.signature_help_state.is_shown());
10721    });
10722
10723    // Restore the popover with more parameter input
10724    cx.set_state(indoc! {"
10725        fn main() {
10726            sample(param1, param2ˇ);
10727        }
10728
10729        fn sample(param1: u8, param2: u8) {}
10730    "});
10731
10732    let mocked_response = lsp::SignatureHelp {
10733        signatures: vec![lsp::SignatureInformation {
10734            label: "fn sample(param1: u8, param2: u8)".to_string(),
10735            documentation: None,
10736            parameters: Some(vec![
10737                lsp::ParameterInformation {
10738                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10739                    documentation: None,
10740                },
10741                lsp::ParameterInformation {
10742                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10743                    documentation: None,
10744                },
10745            ]),
10746            active_parameter: None,
10747        }],
10748        active_signature: Some(0),
10749        active_parameter: Some(1),
10750    };
10751    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10752    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10753        .await;
10754
10755    // When selecting a range, the popover is gone.
10756    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10757    cx.update_editor(|editor, window, cx| {
10758        editor.change_selections(None, window, cx, |s| {
10759            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10760        })
10761    });
10762    cx.assert_editor_state(indoc! {"
10763        fn main() {
10764            sample(param1, «ˇparam2»);
10765        }
10766
10767        fn sample(param1: u8, param2: u8) {}
10768    "});
10769    cx.editor(|editor, _, _| {
10770        assert!(!editor.signature_help_state.is_shown());
10771    });
10772
10773    // When unselecting again, the popover is back if within the brackets.
10774    cx.update_editor(|editor, window, cx| {
10775        editor.change_selections(None, window, cx, |s| {
10776            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10777        })
10778    });
10779    cx.assert_editor_state(indoc! {"
10780        fn main() {
10781            sample(param1, ˇparam2);
10782        }
10783
10784        fn sample(param1: u8, param2: u8) {}
10785    "});
10786    handle_signature_help_request(&mut cx, mocked_response).await;
10787    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10788        .await;
10789    cx.editor(|editor, _, _| {
10790        assert!(editor.signature_help_state.is_shown());
10791    });
10792
10793    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10794    cx.update_editor(|editor, window, cx| {
10795        editor.change_selections(None, window, cx, |s| {
10796            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10797            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10798        })
10799    });
10800    cx.assert_editor_state(indoc! {"
10801        fn main() {
10802            sample(param1, ˇparam2);
10803        }
10804
10805        fn sample(param1: u8, param2: u8) {}
10806    "});
10807
10808    let mocked_response = lsp::SignatureHelp {
10809        signatures: vec![lsp::SignatureInformation {
10810            label: "fn sample(param1: u8, param2: u8)".to_string(),
10811            documentation: None,
10812            parameters: Some(vec![
10813                lsp::ParameterInformation {
10814                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10815                    documentation: None,
10816                },
10817                lsp::ParameterInformation {
10818                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10819                    documentation: None,
10820                },
10821            ]),
10822            active_parameter: None,
10823        }],
10824        active_signature: Some(0),
10825        active_parameter: Some(1),
10826    };
10827    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10828    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10829        .await;
10830    cx.update_editor(|editor, _, cx| {
10831        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10832    });
10833    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10834        .await;
10835    cx.update_editor(|editor, window, cx| {
10836        editor.change_selections(None, window, cx, |s| {
10837            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10838        })
10839    });
10840    cx.assert_editor_state(indoc! {"
10841        fn main() {
10842            sample(param1, «ˇparam2»);
10843        }
10844
10845        fn sample(param1: u8, param2: u8) {}
10846    "});
10847    cx.update_editor(|editor, window, cx| {
10848        editor.change_selections(None, window, cx, |s| {
10849            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10850        })
10851    });
10852    cx.assert_editor_state(indoc! {"
10853        fn main() {
10854            sample(param1, ˇparam2);
10855        }
10856
10857        fn sample(param1: u8, param2: u8) {}
10858    "});
10859    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10860        .await;
10861}
10862
10863#[gpui::test]
10864async fn test_completion_mode(cx: &mut TestAppContext) {
10865    init_test(cx, |_| {});
10866    let mut cx = EditorLspTestContext::new_rust(
10867        lsp::ServerCapabilities {
10868            completion_provider: Some(lsp::CompletionOptions {
10869                resolve_provider: Some(true),
10870                ..Default::default()
10871            }),
10872            ..Default::default()
10873        },
10874        cx,
10875    )
10876    .await;
10877
10878    struct Run {
10879        run_description: &'static str,
10880        initial_state: String,
10881        buffer_marked_text: String,
10882        completion_label: &'static str,
10883        completion_text: &'static str,
10884        expected_with_insert_mode: String,
10885        expected_with_replace_mode: String,
10886        expected_with_replace_subsequence_mode: String,
10887        expected_with_replace_suffix_mode: String,
10888    }
10889
10890    let runs = [
10891        Run {
10892            run_description: "Start of word matches completion text",
10893            initial_state: "before ediˇ after".into(),
10894            buffer_marked_text: "before <edi|> after".into(),
10895            completion_label: "editor",
10896            completion_text: "editor",
10897            expected_with_insert_mode: "before editorˇ after".into(),
10898            expected_with_replace_mode: "before editorˇ after".into(),
10899            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10900            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10901        },
10902        Run {
10903            run_description: "Accept same text at the middle of the word",
10904            initial_state: "before ediˇtor after".into(),
10905            buffer_marked_text: "before <edi|tor> after".into(),
10906            completion_label: "editor",
10907            completion_text: "editor",
10908            expected_with_insert_mode: "before editorˇtor after".into(),
10909            expected_with_replace_mode: "before editorˇ after".into(),
10910            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10911            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10912        },
10913        Run {
10914            run_description: "End of word matches completion text -- cursor at end",
10915            initial_state: "before torˇ after".into(),
10916            buffer_marked_text: "before <tor|> after".into(),
10917            completion_label: "editor",
10918            completion_text: "editor",
10919            expected_with_insert_mode: "before editorˇ after".into(),
10920            expected_with_replace_mode: "before editorˇ after".into(),
10921            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10922            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10923        },
10924        Run {
10925            run_description: "End of word matches completion text -- cursor at start",
10926            initial_state: "before ˇtor after".into(),
10927            buffer_marked_text: "before <|tor> after".into(),
10928            completion_label: "editor",
10929            completion_text: "editor",
10930            expected_with_insert_mode: "before editorˇtor after".into(),
10931            expected_with_replace_mode: "before editorˇ after".into(),
10932            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10933            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10934        },
10935        Run {
10936            run_description: "Prepend text containing whitespace",
10937            initial_state: "pˇfield: bool".into(),
10938            buffer_marked_text: "<p|field>: bool".into(),
10939            completion_label: "pub ",
10940            completion_text: "pub ",
10941            expected_with_insert_mode: "pub ˇfield: bool".into(),
10942            expected_with_replace_mode: "pub ˇ: bool".into(),
10943            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10944            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10945        },
10946        Run {
10947            run_description: "Add element to start of list",
10948            initial_state: "[element_ˇelement_2]".into(),
10949            buffer_marked_text: "[<element_|element_2>]".into(),
10950            completion_label: "element_1",
10951            completion_text: "element_1",
10952            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10953            expected_with_replace_mode: "[element_1ˇ]".into(),
10954            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10955            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10956        },
10957        Run {
10958            run_description: "Add element to start of list -- first and second elements are equal",
10959            initial_state: "[elˇelement]".into(),
10960            buffer_marked_text: "[<el|element>]".into(),
10961            completion_label: "element",
10962            completion_text: "element",
10963            expected_with_insert_mode: "[elementˇelement]".into(),
10964            expected_with_replace_mode: "[elementˇ]".into(),
10965            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10966            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10967        },
10968        Run {
10969            run_description: "Ends with matching suffix",
10970            initial_state: "SubˇError".into(),
10971            buffer_marked_text: "<Sub|Error>".into(),
10972            completion_label: "SubscriptionError",
10973            completion_text: "SubscriptionError",
10974            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10975            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10976            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10977            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10978        },
10979        Run {
10980            run_description: "Suffix is a subsequence -- contiguous",
10981            initial_state: "SubˇErr".into(),
10982            buffer_marked_text: "<Sub|Err>".into(),
10983            completion_label: "SubscriptionError",
10984            completion_text: "SubscriptionError",
10985            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10986            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10987            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10988            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10989        },
10990        Run {
10991            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10992            initial_state: "Suˇscrirr".into(),
10993            buffer_marked_text: "<Su|scrirr>".into(),
10994            completion_label: "SubscriptionError",
10995            completion_text: "SubscriptionError",
10996            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10997            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10998            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10999            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11000        },
11001        Run {
11002            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11003            initial_state: "foo(indˇix)".into(),
11004            buffer_marked_text: "foo(<ind|ix>)".into(),
11005            completion_label: "node_index",
11006            completion_text: "node_index",
11007            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11008            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11009            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11010            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11011        },
11012        Run {
11013            run_description: "Replace range ends before cursor - should extend to cursor",
11014            initial_state: "before editˇo after".into(),
11015            buffer_marked_text: "before <{ed}>it|o after".into(),
11016            completion_label: "editor",
11017            completion_text: "editor",
11018            expected_with_insert_mode: "before editorˇo after".into(),
11019            expected_with_replace_mode: "before editorˇo after".into(),
11020            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11021            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11022        },
11023        Run {
11024            run_description: "Uses label for suffix matching",
11025            initial_state: "before ediˇtor after".into(),
11026            buffer_marked_text: "before <edi|tor> after".into(),
11027            completion_label: "editor",
11028            completion_text: "editor()",
11029            expected_with_insert_mode: "before editor()ˇtor after".into(),
11030            expected_with_replace_mode: "before editor()ˇ after".into(),
11031            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11032            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11033        },
11034        Run {
11035            run_description: "Case insensitive subsequence and suffix matching",
11036            initial_state: "before EDiˇtoR after".into(),
11037            buffer_marked_text: "before <EDi|toR> after".into(),
11038            completion_label: "editor",
11039            completion_text: "editor",
11040            expected_with_insert_mode: "before editorˇtoR after".into(),
11041            expected_with_replace_mode: "before editorˇ after".into(),
11042            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11043            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11044        },
11045    ];
11046
11047    for run in runs {
11048        let run_variations = [
11049            (LspInsertMode::Insert, run.expected_with_insert_mode),
11050            (LspInsertMode::Replace, run.expected_with_replace_mode),
11051            (
11052                LspInsertMode::ReplaceSubsequence,
11053                run.expected_with_replace_subsequence_mode,
11054            ),
11055            (
11056                LspInsertMode::ReplaceSuffix,
11057                run.expected_with_replace_suffix_mode,
11058            ),
11059        ];
11060
11061        for (lsp_insert_mode, expected_text) in run_variations {
11062            eprintln!(
11063                "run = {:?}, mode = {lsp_insert_mode:.?}",
11064                run.run_description,
11065            );
11066
11067            update_test_language_settings(&mut cx, |settings| {
11068                settings.defaults.completions = Some(CompletionSettings {
11069                    lsp_insert_mode,
11070                    words: WordsCompletionMode::Disabled,
11071                    lsp: true,
11072                    lsp_fetch_timeout_ms: 0,
11073                });
11074            });
11075
11076            cx.set_state(&run.initial_state);
11077            cx.update_editor(|editor, window, cx| {
11078                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11079            });
11080
11081            let counter = Arc::new(AtomicUsize::new(0));
11082            handle_completion_request_with_insert_and_replace(
11083                &mut cx,
11084                &run.buffer_marked_text,
11085                vec![(run.completion_label, run.completion_text)],
11086                counter.clone(),
11087            )
11088            .await;
11089            cx.condition(|editor, _| editor.context_menu_visible())
11090                .await;
11091            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11092
11093            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11094                editor
11095                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11096                    .unwrap()
11097            });
11098            cx.assert_editor_state(&expected_text);
11099            handle_resolve_completion_request(&mut cx, None).await;
11100            apply_additional_edits.await.unwrap();
11101        }
11102    }
11103}
11104
11105#[gpui::test]
11106async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11107    init_test(cx, |_| {});
11108    let mut cx = EditorLspTestContext::new_rust(
11109        lsp::ServerCapabilities {
11110            completion_provider: Some(lsp::CompletionOptions {
11111                resolve_provider: Some(true),
11112                ..Default::default()
11113            }),
11114            ..Default::default()
11115        },
11116        cx,
11117    )
11118    .await;
11119
11120    let initial_state = "SubˇError";
11121    let buffer_marked_text = "<Sub|Error>";
11122    let completion_text = "SubscriptionError";
11123    let expected_with_insert_mode = "SubscriptionErrorˇError";
11124    let expected_with_replace_mode = "SubscriptionErrorˇ";
11125
11126    update_test_language_settings(&mut cx, |settings| {
11127        settings.defaults.completions = Some(CompletionSettings {
11128            words: WordsCompletionMode::Disabled,
11129            // set the opposite here to ensure that the action is overriding the default behavior
11130            lsp_insert_mode: LspInsertMode::Insert,
11131            lsp: true,
11132            lsp_fetch_timeout_ms: 0,
11133        });
11134    });
11135
11136    cx.set_state(initial_state);
11137    cx.update_editor(|editor, window, cx| {
11138        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11139    });
11140
11141    let counter = Arc::new(AtomicUsize::new(0));
11142    handle_completion_request_with_insert_and_replace(
11143        &mut cx,
11144        &buffer_marked_text,
11145        vec![(completion_text, completion_text)],
11146        counter.clone(),
11147    )
11148    .await;
11149    cx.condition(|editor, _| editor.context_menu_visible())
11150        .await;
11151    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11152
11153    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11154        editor
11155            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11156            .unwrap()
11157    });
11158    cx.assert_editor_state(&expected_with_replace_mode);
11159    handle_resolve_completion_request(&mut cx, None).await;
11160    apply_additional_edits.await.unwrap();
11161
11162    update_test_language_settings(&mut cx, |settings| {
11163        settings.defaults.completions = Some(CompletionSettings {
11164            words: WordsCompletionMode::Disabled,
11165            // set the opposite here to ensure that the action is overriding the default behavior
11166            lsp_insert_mode: LspInsertMode::Replace,
11167            lsp: true,
11168            lsp_fetch_timeout_ms: 0,
11169        });
11170    });
11171
11172    cx.set_state(initial_state);
11173    cx.update_editor(|editor, window, cx| {
11174        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11175    });
11176    handle_completion_request_with_insert_and_replace(
11177        &mut cx,
11178        &buffer_marked_text,
11179        vec![(completion_text, completion_text)],
11180        counter.clone(),
11181    )
11182    .await;
11183    cx.condition(|editor, _| editor.context_menu_visible())
11184        .await;
11185    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11186
11187    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11188        editor
11189            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11190            .unwrap()
11191    });
11192    cx.assert_editor_state(&expected_with_insert_mode);
11193    handle_resolve_completion_request(&mut cx, None).await;
11194    apply_additional_edits.await.unwrap();
11195}
11196
11197#[gpui::test]
11198async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11199    init_test(cx, |_| {});
11200    let mut cx = EditorLspTestContext::new_rust(
11201        lsp::ServerCapabilities {
11202            completion_provider: Some(lsp::CompletionOptions {
11203                resolve_provider: Some(true),
11204                ..Default::default()
11205            }),
11206            ..Default::default()
11207        },
11208        cx,
11209    )
11210    .await;
11211
11212    // scenario: surrounding text matches completion text
11213    let completion_text = "to_offset";
11214    let initial_state = indoc! {"
11215        1. buf.to_offˇsuffix
11216        2. buf.to_offˇsuf
11217        3. buf.to_offˇfix
11218        4. buf.to_offˇ
11219        5. into_offˇensive
11220        6. ˇsuffix
11221        7. let ˇ //
11222        8. aaˇzz
11223        9. buf.to_off«zzzzzˇ»suffix
11224        10. buf.«ˇzzzzz»suffix
11225        11. to_off«ˇzzzzz»
11226
11227        buf.to_offˇsuffix  // newest cursor
11228    "};
11229    let completion_marked_buffer = indoc! {"
11230        1. buf.to_offsuffix
11231        2. buf.to_offsuf
11232        3. buf.to_offfix
11233        4. buf.to_off
11234        5. into_offensive
11235        6. suffix
11236        7. let  //
11237        8. aazz
11238        9. buf.to_offzzzzzsuffix
11239        10. buf.zzzzzsuffix
11240        11. to_offzzzzz
11241
11242        buf.<to_off|suffix>  // newest cursor
11243    "};
11244    let expected = indoc! {"
11245        1. buf.to_offsetˇ
11246        2. buf.to_offsetˇsuf
11247        3. buf.to_offsetˇfix
11248        4. buf.to_offsetˇ
11249        5. into_offsetˇensive
11250        6. to_offsetˇsuffix
11251        7. let to_offsetˇ //
11252        8. aato_offsetˇzz
11253        9. buf.to_offsetˇ
11254        10. buf.to_offsetˇsuffix
11255        11. to_offsetˇ
11256
11257        buf.to_offsetˇ  // newest cursor
11258    "};
11259    cx.set_state(initial_state);
11260    cx.update_editor(|editor, window, cx| {
11261        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11262    });
11263    handle_completion_request_with_insert_and_replace(
11264        &mut cx,
11265        completion_marked_buffer,
11266        vec![(completion_text, completion_text)],
11267        Arc::new(AtomicUsize::new(0)),
11268    )
11269    .await;
11270    cx.condition(|editor, _| editor.context_menu_visible())
11271        .await;
11272    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11273        editor
11274            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11275            .unwrap()
11276    });
11277    cx.assert_editor_state(expected);
11278    handle_resolve_completion_request(&mut cx, None).await;
11279    apply_additional_edits.await.unwrap();
11280
11281    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11282    let completion_text = "foo_and_bar";
11283    let initial_state = indoc! {"
11284        1. ooanbˇ
11285        2. zooanbˇ
11286        3. ooanbˇz
11287        4. zooanbˇz
11288        5. ooanˇ
11289        6. oanbˇ
11290
11291        ooanbˇ
11292    "};
11293    let completion_marked_buffer = indoc! {"
11294        1. ooanb
11295        2. zooanb
11296        3. ooanbz
11297        4. zooanbz
11298        5. ooan
11299        6. oanb
11300
11301        <ooanb|>
11302    "};
11303    let expected = indoc! {"
11304        1. foo_and_barˇ
11305        2. zfoo_and_barˇ
11306        3. foo_and_barˇz
11307        4. zfoo_and_barˇz
11308        5. ooanfoo_and_barˇ
11309        6. oanbfoo_and_barˇ
11310
11311        foo_and_barˇ
11312    "};
11313    cx.set_state(initial_state);
11314    cx.update_editor(|editor, window, cx| {
11315        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11316    });
11317    handle_completion_request_with_insert_and_replace(
11318        &mut cx,
11319        completion_marked_buffer,
11320        vec![(completion_text, completion_text)],
11321        Arc::new(AtomicUsize::new(0)),
11322    )
11323    .await;
11324    cx.condition(|editor, _| editor.context_menu_visible())
11325        .await;
11326    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11327        editor
11328            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11329            .unwrap()
11330    });
11331    cx.assert_editor_state(expected);
11332    handle_resolve_completion_request(&mut cx, None).await;
11333    apply_additional_edits.await.unwrap();
11334
11335    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11336    // (expects the same as if it was inserted at the end)
11337    let completion_text = "foo_and_bar";
11338    let initial_state = indoc! {"
11339        1. ooˇanb
11340        2. zooˇanb
11341        3. ooˇanbz
11342        4. zooˇanbz
11343
11344        ooˇanb
11345    "};
11346    let completion_marked_buffer = indoc! {"
11347        1. ooanb
11348        2. zooanb
11349        3. ooanbz
11350        4. zooanbz
11351
11352        <oo|anb>
11353    "};
11354    let expected = indoc! {"
11355        1. foo_and_barˇ
11356        2. zfoo_and_barˇ
11357        3. foo_and_barˇz
11358        4. zfoo_and_barˇz
11359
11360        foo_and_barˇ
11361    "};
11362    cx.set_state(initial_state);
11363    cx.update_editor(|editor, window, cx| {
11364        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11365    });
11366    handle_completion_request_with_insert_and_replace(
11367        &mut cx,
11368        completion_marked_buffer,
11369        vec![(completion_text, completion_text)],
11370        Arc::new(AtomicUsize::new(0)),
11371    )
11372    .await;
11373    cx.condition(|editor, _| editor.context_menu_visible())
11374        .await;
11375    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11376        editor
11377            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11378            .unwrap()
11379    });
11380    cx.assert_editor_state(expected);
11381    handle_resolve_completion_request(&mut cx, None).await;
11382    apply_additional_edits.await.unwrap();
11383}
11384
11385// This used to crash
11386#[gpui::test]
11387async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11388    init_test(cx, |_| {});
11389
11390    let buffer_text = indoc! {"
11391        fn main() {
11392            10.satu;
11393
11394            //
11395            // separate cursors so they open in different excerpts (manually reproducible)
11396            //
11397
11398            10.satu20;
11399        }
11400    "};
11401    let multibuffer_text_with_selections = indoc! {"
11402        fn main() {
11403            10.satuˇ;
11404
11405            //
11406
11407            //
11408
11409            10.satuˇ20;
11410        }
11411    "};
11412    let expected_multibuffer = indoc! {"
11413        fn main() {
11414            10.saturating_sub()ˇ;
11415
11416            //
11417
11418            //
11419
11420            10.saturating_sub()ˇ;
11421        }
11422    "};
11423
11424    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11425    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11426
11427    let fs = FakeFs::new(cx.executor());
11428    fs.insert_tree(
11429        path!("/a"),
11430        json!({
11431            "main.rs": buffer_text,
11432        }),
11433    )
11434    .await;
11435
11436    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11437    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11438    language_registry.add(rust_lang());
11439    let mut fake_servers = language_registry.register_fake_lsp(
11440        "Rust",
11441        FakeLspAdapter {
11442            capabilities: lsp::ServerCapabilities {
11443                completion_provider: Some(lsp::CompletionOptions {
11444                    resolve_provider: None,
11445                    ..lsp::CompletionOptions::default()
11446                }),
11447                ..lsp::ServerCapabilities::default()
11448            },
11449            ..FakeLspAdapter::default()
11450        },
11451    );
11452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11453    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11454    let buffer = project
11455        .update(cx, |project, cx| {
11456            project.open_local_buffer(path!("/a/main.rs"), cx)
11457        })
11458        .await
11459        .unwrap();
11460
11461    let multi_buffer = cx.new(|cx| {
11462        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11463        multi_buffer.push_excerpts(
11464            buffer.clone(),
11465            [ExcerptRange::new(0..first_excerpt_end)],
11466            cx,
11467        );
11468        multi_buffer.push_excerpts(
11469            buffer.clone(),
11470            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11471            cx,
11472        );
11473        multi_buffer
11474    });
11475
11476    let editor = workspace
11477        .update(cx, |_, window, cx| {
11478            cx.new(|cx| {
11479                Editor::new(
11480                    EditorMode::Full {
11481                        scale_ui_elements_with_buffer_font_size: false,
11482                        show_active_line_background: false,
11483                        sized_by_content: false,
11484                    },
11485                    multi_buffer.clone(),
11486                    Some(project.clone()),
11487                    window,
11488                    cx,
11489                )
11490            })
11491        })
11492        .unwrap();
11493
11494    let pane = workspace
11495        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11496        .unwrap();
11497    pane.update_in(cx, |pane, window, cx| {
11498        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11499    });
11500
11501    let fake_server = fake_servers.next().await.unwrap();
11502
11503    editor.update_in(cx, |editor, window, cx| {
11504        editor.change_selections(None, window, cx, |s| {
11505            s.select_ranges([
11506                Point::new(1, 11)..Point::new(1, 11),
11507                Point::new(7, 11)..Point::new(7, 11),
11508            ])
11509        });
11510
11511        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11512    });
11513
11514    editor.update_in(cx, |editor, window, cx| {
11515        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11516    });
11517
11518    fake_server
11519        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11520            let completion_item = lsp::CompletionItem {
11521                label: "saturating_sub()".into(),
11522                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11523                    lsp::InsertReplaceEdit {
11524                        new_text: "saturating_sub()".to_owned(),
11525                        insert: lsp::Range::new(
11526                            lsp::Position::new(7, 7),
11527                            lsp::Position::new(7, 11),
11528                        ),
11529                        replace: lsp::Range::new(
11530                            lsp::Position::new(7, 7),
11531                            lsp::Position::new(7, 13),
11532                        ),
11533                    },
11534                )),
11535                ..lsp::CompletionItem::default()
11536            };
11537
11538            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11539        })
11540        .next()
11541        .await
11542        .unwrap();
11543
11544    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11545        .await;
11546
11547    editor
11548        .update_in(cx, |editor, window, cx| {
11549            editor
11550                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11551                .unwrap()
11552        })
11553        .await
11554        .unwrap();
11555
11556    editor.update(cx, |editor, cx| {
11557        assert_text_with_selections(editor, expected_multibuffer, cx);
11558    })
11559}
11560
11561#[gpui::test]
11562async fn test_completion(cx: &mut TestAppContext) {
11563    init_test(cx, |_| {});
11564
11565    let mut cx = EditorLspTestContext::new_rust(
11566        lsp::ServerCapabilities {
11567            completion_provider: Some(lsp::CompletionOptions {
11568                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11569                resolve_provider: Some(true),
11570                ..Default::default()
11571            }),
11572            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11573            ..Default::default()
11574        },
11575        cx,
11576    )
11577    .await;
11578    let counter = Arc::new(AtomicUsize::new(0));
11579
11580    cx.set_state(indoc! {"
11581        oneˇ
11582        two
11583        three
11584    "});
11585    cx.simulate_keystroke(".");
11586    handle_completion_request(
11587        indoc! {"
11588            one.|<>
11589            two
11590            three
11591        "},
11592        vec!["first_completion", "second_completion"],
11593        true,
11594        counter.clone(),
11595        &mut cx,
11596    )
11597    .await;
11598    cx.condition(|editor, _| editor.context_menu_visible())
11599        .await;
11600    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11601
11602    let _handler = handle_signature_help_request(
11603        &mut cx,
11604        lsp::SignatureHelp {
11605            signatures: vec![lsp::SignatureInformation {
11606                label: "test signature".to_string(),
11607                documentation: None,
11608                parameters: Some(vec![lsp::ParameterInformation {
11609                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11610                    documentation: None,
11611                }]),
11612                active_parameter: None,
11613            }],
11614            active_signature: None,
11615            active_parameter: None,
11616        },
11617    );
11618    cx.update_editor(|editor, window, cx| {
11619        assert!(
11620            !editor.signature_help_state.is_shown(),
11621            "No signature help was called for"
11622        );
11623        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11624    });
11625    cx.run_until_parked();
11626    cx.update_editor(|editor, _, _| {
11627        assert!(
11628            !editor.signature_help_state.is_shown(),
11629            "No signature help should be shown when completions menu is open"
11630        );
11631    });
11632
11633    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11634        editor.context_menu_next(&Default::default(), window, cx);
11635        editor
11636            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11637            .unwrap()
11638    });
11639    cx.assert_editor_state(indoc! {"
11640        one.second_completionˇ
11641        two
11642        three
11643    "});
11644
11645    handle_resolve_completion_request(
11646        &mut cx,
11647        Some(vec![
11648            (
11649                //This overlaps with the primary completion edit which is
11650                //misbehavior from the LSP spec, test that we filter it out
11651                indoc! {"
11652                    one.second_ˇcompletion
11653                    two
11654                    threeˇ
11655                "},
11656                "overlapping additional edit",
11657            ),
11658            (
11659                indoc! {"
11660                    one.second_completion
11661                    two
11662                    threeˇ
11663                "},
11664                "\nadditional edit",
11665            ),
11666        ]),
11667    )
11668    .await;
11669    apply_additional_edits.await.unwrap();
11670    cx.assert_editor_state(indoc! {"
11671        one.second_completionˇ
11672        two
11673        three
11674        additional edit
11675    "});
11676
11677    cx.set_state(indoc! {"
11678        one.second_completion
11679        twoˇ
11680        threeˇ
11681        additional edit
11682    "});
11683    cx.simulate_keystroke(" ");
11684    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11685    cx.simulate_keystroke("s");
11686    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11687
11688    cx.assert_editor_state(indoc! {"
11689        one.second_completion
11690        two sˇ
11691        three sˇ
11692        additional edit
11693    "});
11694    handle_completion_request(
11695        indoc! {"
11696            one.second_completion
11697            two s
11698            three <s|>
11699            additional edit
11700        "},
11701        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11702        true,
11703        counter.clone(),
11704        &mut cx,
11705    )
11706    .await;
11707    cx.condition(|editor, _| editor.context_menu_visible())
11708        .await;
11709    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11710
11711    cx.simulate_keystroke("i");
11712
11713    handle_completion_request(
11714        indoc! {"
11715            one.second_completion
11716            two si
11717            three <si|>
11718            additional edit
11719        "},
11720        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11721        true,
11722        counter.clone(),
11723        &mut cx,
11724    )
11725    .await;
11726    cx.condition(|editor, _| editor.context_menu_visible())
11727        .await;
11728    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11729
11730    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11731        editor
11732            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11733            .unwrap()
11734    });
11735    cx.assert_editor_state(indoc! {"
11736        one.second_completion
11737        two sixth_completionˇ
11738        three sixth_completionˇ
11739        additional edit
11740    "});
11741
11742    apply_additional_edits.await.unwrap();
11743
11744    update_test_language_settings(&mut cx, |settings| {
11745        settings.defaults.show_completions_on_input = Some(false);
11746    });
11747    cx.set_state("editorˇ");
11748    cx.simulate_keystroke(".");
11749    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11750    cx.simulate_keystrokes("c l o");
11751    cx.assert_editor_state("editor.cloˇ");
11752    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11753    cx.update_editor(|editor, window, cx| {
11754        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11755    });
11756    handle_completion_request(
11757        "editor.<clo|>",
11758        vec!["close", "clobber"],
11759        true,
11760        counter.clone(),
11761        &mut cx,
11762    )
11763    .await;
11764    cx.condition(|editor, _| editor.context_menu_visible())
11765        .await;
11766    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11767
11768    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11769        editor
11770            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11771            .unwrap()
11772    });
11773    cx.assert_editor_state("editor.closeˇ");
11774    handle_resolve_completion_request(&mut cx, None).await;
11775    apply_additional_edits.await.unwrap();
11776}
11777
11778#[gpui::test]
11779async fn test_completion_reuse(cx: &mut TestAppContext) {
11780    init_test(cx, |_| {});
11781
11782    let mut cx = EditorLspTestContext::new_rust(
11783        lsp::ServerCapabilities {
11784            completion_provider: Some(lsp::CompletionOptions {
11785                trigger_characters: Some(vec![".".to_string()]),
11786                ..Default::default()
11787            }),
11788            ..Default::default()
11789        },
11790        cx,
11791    )
11792    .await;
11793
11794    let counter = Arc::new(AtomicUsize::new(0));
11795    cx.set_state("objˇ");
11796    cx.simulate_keystroke(".");
11797
11798    // Initial completion request returns complete results
11799    let is_incomplete = false;
11800    handle_completion_request(
11801        "obj.|<>",
11802        vec!["a", "ab", "abc"],
11803        is_incomplete,
11804        counter.clone(),
11805        &mut cx,
11806    )
11807    .await;
11808    cx.run_until_parked();
11809    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11810    cx.assert_editor_state("obj.ˇ");
11811    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11812
11813    // Type "a" - filters existing completions
11814    cx.simulate_keystroke("a");
11815    cx.run_until_parked();
11816    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11817    cx.assert_editor_state("obj.aˇ");
11818    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11819
11820    // Type "b" - filters existing completions
11821    cx.simulate_keystroke("b");
11822    cx.run_until_parked();
11823    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11824    cx.assert_editor_state("obj.abˇ");
11825    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11826
11827    // Type "c" - filters existing completions
11828    cx.simulate_keystroke("c");
11829    cx.run_until_parked();
11830    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11831    cx.assert_editor_state("obj.abcˇ");
11832    check_displayed_completions(vec!["abc"], &mut cx);
11833
11834    // Backspace to delete "c" - filters existing completions
11835    cx.update_editor(|editor, window, cx| {
11836        editor.backspace(&Backspace, window, cx);
11837    });
11838    cx.run_until_parked();
11839    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11840    cx.assert_editor_state("obj.abˇ");
11841    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11842
11843    // Moving cursor to the left dismisses menu.
11844    cx.update_editor(|editor, window, cx| {
11845        editor.move_left(&MoveLeft, window, cx);
11846    });
11847    cx.run_until_parked();
11848    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11849    cx.assert_editor_state("obj.aˇb");
11850    cx.update_editor(|editor, _, _| {
11851        assert_eq!(editor.context_menu_visible(), false);
11852    });
11853
11854    // Type "b" - new request
11855    cx.simulate_keystroke("b");
11856    let is_incomplete = false;
11857    handle_completion_request(
11858        "obj.<ab|>a",
11859        vec!["ab", "abc"],
11860        is_incomplete,
11861        counter.clone(),
11862        &mut cx,
11863    )
11864    .await;
11865    cx.run_until_parked();
11866    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11867    cx.assert_editor_state("obj.abˇb");
11868    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11869
11870    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
11871    cx.update_editor(|editor, window, cx| {
11872        editor.backspace(&Backspace, window, cx);
11873    });
11874    let is_incomplete = false;
11875    handle_completion_request(
11876        "obj.<a|>b",
11877        vec!["a", "ab", "abc"],
11878        is_incomplete,
11879        counter.clone(),
11880        &mut cx,
11881    )
11882    .await;
11883    cx.run_until_parked();
11884    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11885    cx.assert_editor_state("obj.aˇb");
11886    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11887
11888    // Backspace to delete "a" - dismisses menu.
11889    cx.update_editor(|editor, window, cx| {
11890        editor.backspace(&Backspace, window, cx);
11891    });
11892    cx.run_until_parked();
11893    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11894    cx.assert_editor_state("obj.ˇb");
11895    cx.update_editor(|editor, _, _| {
11896        assert_eq!(editor.context_menu_visible(), false);
11897    });
11898}
11899
11900#[gpui::test]
11901async fn test_word_completion(cx: &mut TestAppContext) {
11902    let lsp_fetch_timeout_ms = 10;
11903    init_test(cx, |language_settings| {
11904        language_settings.defaults.completions = Some(CompletionSettings {
11905            words: WordsCompletionMode::Fallback,
11906            lsp: true,
11907            lsp_fetch_timeout_ms: 10,
11908            lsp_insert_mode: LspInsertMode::Insert,
11909        });
11910    });
11911
11912    let mut cx = EditorLspTestContext::new_rust(
11913        lsp::ServerCapabilities {
11914            completion_provider: Some(lsp::CompletionOptions {
11915                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11916                ..lsp::CompletionOptions::default()
11917            }),
11918            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11919            ..lsp::ServerCapabilities::default()
11920        },
11921        cx,
11922    )
11923    .await;
11924
11925    let throttle_completions = Arc::new(AtomicBool::new(false));
11926
11927    let lsp_throttle_completions = throttle_completions.clone();
11928    let _completion_requests_handler =
11929        cx.lsp
11930            .server
11931            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11932                let lsp_throttle_completions = lsp_throttle_completions.clone();
11933                let cx = cx.clone();
11934                async move {
11935                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11936                        cx.background_executor()
11937                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11938                            .await;
11939                    }
11940                    Ok(Some(lsp::CompletionResponse::Array(vec![
11941                        lsp::CompletionItem {
11942                            label: "first".into(),
11943                            ..lsp::CompletionItem::default()
11944                        },
11945                        lsp::CompletionItem {
11946                            label: "last".into(),
11947                            ..lsp::CompletionItem::default()
11948                        },
11949                    ])))
11950                }
11951            });
11952
11953    cx.set_state(indoc! {"
11954        oneˇ
11955        two
11956        three
11957    "});
11958    cx.simulate_keystroke(".");
11959    cx.executor().run_until_parked();
11960    cx.condition(|editor, _| editor.context_menu_visible())
11961        .await;
11962    cx.update_editor(|editor, window, cx| {
11963        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11964        {
11965            assert_eq!(
11966                completion_menu_entries(&menu),
11967                &["first", "last"],
11968                "When LSP server is fast to reply, no fallback word completions are used"
11969            );
11970        } else {
11971            panic!("expected completion menu to be open");
11972        }
11973        editor.cancel(&Cancel, window, cx);
11974    });
11975    cx.executor().run_until_parked();
11976    cx.condition(|editor, _| !editor.context_menu_visible())
11977        .await;
11978
11979    throttle_completions.store(true, atomic::Ordering::Release);
11980    cx.simulate_keystroke(".");
11981    cx.executor()
11982        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11983    cx.executor().run_until_parked();
11984    cx.condition(|editor, _| editor.context_menu_visible())
11985        .await;
11986    cx.update_editor(|editor, _, _| {
11987        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11988        {
11989            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11990                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11991        } else {
11992            panic!("expected completion menu to be open");
11993        }
11994    });
11995}
11996
11997#[gpui::test]
11998async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11999    init_test(cx, |language_settings| {
12000        language_settings.defaults.completions = Some(CompletionSettings {
12001            words: WordsCompletionMode::Enabled,
12002            lsp: true,
12003            lsp_fetch_timeout_ms: 0,
12004            lsp_insert_mode: LspInsertMode::Insert,
12005        });
12006    });
12007
12008    let mut cx = EditorLspTestContext::new_rust(
12009        lsp::ServerCapabilities {
12010            completion_provider: Some(lsp::CompletionOptions {
12011                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12012                ..lsp::CompletionOptions::default()
12013            }),
12014            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12015            ..lsp::ServerCapabilities::default()
12016        },
12017        cx,
12018    )
12019    .await;
12020
12021    let _completion_requests_handler =
12022        cx.lsp
12023            .server
12024            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12025                Ok(Some(lsp::CompletionResponse::Array(vec![
12026                    lsp::CompletionItem {
12027                        label: "first".into(),
12028                        ..lsp::CompletionItem::default()
12029                    },
12030                    lsp::CompletionItem {
12031                        label: "last".into(),
12032                        ..lsp::CompletionItem::default()
12033                    },
12034                ])))
12035            });
12036
12037    cx.set_state(indoc! {"ˇ
12038        first
12039        last
12040        second
12041    "});
12042    cx.simulate_keystroke(".");
12043    cx.executor().run_until_parked();
12044    cx.condition(|editor, _| editor.context_menu_visible())
12045        .await;
12046    cx.update_editor(|editor, _, _| {
12047        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12048        {
12049            assert_eq!(
12050                completion_menu_entries(&menu),
12051                &["first", "last", "second"],
12052                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12053            );
12054        } else {
12055            panic!("expected completion menu to be open");
12056        }
12057    });
12058}
12059
12060#[gpui::test]
12061async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12062    init_test(cx, |language_settings| {
12063        language_settings.defaults.completions = Some(CompletionSettings {
12064            words: WordsCompletionMode::Disabled,
12065            lsp: true,
12066            lsp_fetch_timeout_ms: 0,
12067            lsp_insert_mode: LspInsertMode::Insert,
12068        });
12069    });
12070
12071    let mut cx = EditorLspTestContext::new_rust(
12072        lsp::ServerCapabilities {
12073            completion_provider: Some(lsp::CompletionOptions {
12074                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12075                ..lsp::CompletionOptions::default()
12076            }),
12077            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12078            ..lsp::ServerCapabilities::default()
12079        },
12080        cx,
12081    )
12082    .await;
12083
12084    let _completion_requests_handler =
12085        cx.lsp
12086            .server
12087            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12088                panic!("LSP completions should not be queried when dealing with word completions")
12089            });
12090
12091    cx.set_state(indoc! {"ˇ
12092        first
12093        last
12094        second
12095    "});
12096    cx.update_editor(|editor, window, cx| {
12097        editor.show_word_completions(&ShowWordCompletions, window, cx);
12098    });
12099    cx.executor().run_until_parked();
12100    cx.condition(|editor, _| editor.context_menu_visible())
12101        .await;
12102    cx.update_editor(|editor, _, _| {
12103        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12104        {
12105            assert_eq!(
12106                completion_menu_entries(&menu),
12107                &["first", "last", "second"],
12108                "`ShowWordCompletions` action should show word completions"
12109            );
12110        } else {
12111            panic!("expected completion menu to be open");
12112        }
12113    });
12114
12115    cx.simulate_keystroke("l");
12116    cx.executor().run_until_parked();
12117    cx.condition(|editor, _| editor.context_menu_visible())
12118        .await;
12119    cx.update_editor(|editor, _, _| {
12120        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12121        {
12122            assert_eq!(
12123                completion_menu_entries(&menu),
12124                &["last"],
12125                "After showing word completions, further editing should filter them and not query the LSP"
12126            );
12127        } else {
12128            panic!("expected completion menu to be open");
12129        }
12130    });
12131}
12132
12133#[gpui::test]
12134async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12135    init_test(cx, |language_settings| {
12136        language_settings.defaults.completions = Some(CompletionSettings {
12137            words: WordsCompletionMode::Fallback,
12138            lsp: false,
12139            lsp_fetch_timeout_ms: 0,
12140            lsp_insert_mode: LspInsertMode::Insert,
12141        });
12142    });
12143
12144    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12145
12146    cx.set_state(indoc! {"ˇ
12147        0_usize
12148        let
12149        33
12150        4.5f32
12151    "});
12152    cx.update_editor(|editor, window, cx| {
12153        editor.show_completions(&ShowCompletions::default(), window, cx);
12154    });
12155    cx.executor().run_until_parked();
12156    cx.condition(|editor, _| editor.context_menu_visible())
12157        .await;
12158    cx.update_editor(|editor, window, cx| {
12159        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12160        {
12161            assert_eq!(
12162                completion_menu_entries(&menu),
12163                &["let"],
12164                "With no digits in the completion query, no digits should be in the word completions"
12165            );
12166        } else {
12167            panic!("expected completion menu to be open");
12168        }
12169        editor.cancel(&Cancel, window, cx);
12170    });
12171
12172    cx.set_state(indoc! {"12173        0_usize
12174        let
12175        3
12176        33.35f32
12177    "});
12178    cx.update_editor(|editor, window, cx| {
12179        editor.show_completions(&ShowCompletions::default(), window, cx);
12180    });
12181    cx.executor().run_until_parked();
12182    cx.condition(|editor, _| editor.context_menu_visible())
12183        .await;
12184    cx.update_editor(|editor, _, _| {
12185        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12186        {
12187            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12188                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12189        } else {
12190            panic!("expected completion menu to be open");
12191        }
12192    });
12193}
12194
12195fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12196    let position = || lsp::Position {
12197        line: params.text_document_position.position.line,
12198        character: params.text_document_position.position.character,
12199    };
12200    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12201        range: lsp::Range {
12202            start: position(),
12203            end: position(),
12204        },
12205        new_text: text.to_string(),
12206    }))
12207}
12208
12209#[gpui::test]
12210async fn test_multiline_completion(cx: &mut TestAppContext) {
12211    init_test(cx, |_| {});
12212
12213    let fs = FakeFs::new(cx.executor());
12214    fs.insert_tree(
12215        path!("/a"),
12216        json!({
12217            "main.ts": "a",
12218        }),
12219    )
12220    .await;
12221
12222    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12223    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12224    let typescript_language = Arc::new(Language::new(
12225        LanguageConfig {
12226            name: "TypeScript".into(),
12227            matcher: LanguageMatcher {
12228                path_suffixes: vec!["ts".to_string()],
12229                ..LanguageMatcher::default()
12230            },
12231            line_comments: vec!["// ".into()],
12232            ..LanguageConfig::default()
12233        },
12234        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12235    ));
12236    language_registry.add(typescript_language.clone());
12237    let mut fake_servers = language_registry.register_fake_lsp(
12238        "TypeScript",
12239        FakeLspAdapter {
12240            capabilities: lsp::ServerCapabilities {
12241                completion_provider: Some(lsp::CompletionOptions {
12242                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12243                    ..lsp::CompletionOptions::default()
12244                }),
12245                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12246                ..lsp::ServerCapabilities::default()
12247            },
12248            // Emulate vtsls label generation
12249            label_for_completion: Some(Box::new(|item, _| {
12250                let text = if let Some(description) = item
12251                    .label_details
12252                    .as_ref()
12253                    .and_then(|label_details| label_details.description.as_ref())
12254                {
12255                    format!("{} {}", item.label, description)
12256                } else if let Some(detail) = &item.detail {
12257                    format!("{} {}", item.label, detail)
12258                } else {
12259                    item.label.clone()
12260                };
12261                let len = text.len();
12262                Some(language::CodeLabel {
12263                    text,
12264                    runs: Vec::new(),
12265                    filter_range: 0..len,
12266                })
12267            })),
12268            ..FakeLspAdapter::default()
12269        },
12270    );
12271    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12272    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12273    let worktree_id = workspace
12274        .update(cx, |workspace, _window, cx| {
12275            workspace.project().update(cx, |project, cx| {
12276                project.worktrees(cx).next().unwrap().read(cx).id()
12277            })
12278        })
12279        .unwrap();
12280    let _buffer = project
12281        .update(cx, |project, cx| {
12282            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12283        })
12284        .await
12285        .unwrap();
12286    let editor = workspace
12287        .update(cx, |workspace, window, cx| {
12288            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12289        })
12290        .unwrap()
12291        .await
12292        .unwrap()
12293        .downcast::<Editor>()
12294        .unwrap();
12295    let fake_server = fake_servers.next().await.unwrap();
12296
12297    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12298    let multiline_label_2 = "a\nb\nc\n";
12299    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12300    let multiline_description = "d\ne\nf\n";
12301    let multiline_detail_2 = "g\nh\ni\n";
12302
12303    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12304        move |params, _| async move {
12305            Ok(Some(lsp::CompletionResponse::Array(vec![
12306                lsp::CompletionItem {
12307                    label: multiline_label.to_string(),
12308                    text_edit: gen_text_edit(&params, "new_text_1"),
12309                    ..lsp::CompletionItem::default()
12310                },
12311                lsp::CompletionItem {
12312                    label: "single line label 1".to_string(),
12313                    detail: Some(multiline_detail.to_string()),
12314                    text_edit: gen_text_edit(&params, "new_text_2"),
12315                    ..lsp::CompletionItem::default()
12316                },
12317                lsp::CompletionItem {
12318                    label: "single line label 2".to_string(),
12319                    label_details: Some(lsp::CompletionItemLabelDetails {
12320                        description: Some(multiline_description.to_string()),
12321                        detail: None,
12322                    }),
12323                    text_edit: gen_text_edit(&params, "new_text_2"),
12324                    ..lsp::CompletionItem::default()
12325                },
12326                lsp::CompletionItem {
12327                    label: multiline_label_2.to_string(),
12328                    detail: Some(multiline_detail_2.to_string()),
12329                    text_edit: gen_text_edit(&params, "new_text_3"),
12330                    ..lsp::CompletionItem::default()
12331                },
12332                lsp::CompletionItem {
12333                    label: "Label with many     spaces and \t but without newlines".to_string(),
12334                    detail: Some(
12335                        "Details with many     spaces and \t but without newlines".to_string(),
12336                    ),
12337                    text_edit: gen_text_edit(&params, "new_text_4"),
12338                    ..lsp::CompletionItem::default()
12339                },
12340            ])))
12341        },
12342    );
12343
12344    editor.update_in(cx, |editor, window, cx| {
12345        cx.focus_self(window);
12346        editor.move_to_end(&MoveToEnd, window, cx);
12347        editor.handle_input(".", window, cx);
12348    });
12349    cx.run_until_parked();
12350    completion_handle.next().await.unwrap();
12351
12352    editor.update(cx, |editor, _| {
12353        assert!(editor.context_menu_visible());
12354        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12355        {
12356            let completion_labels = menu
12357                .completions
12358                .borrow()
12359                .iter()
12360                .map(|c| c.label.text.clone())
12361                .collect::<Vec<_>>();
12362            assert_eq!(
12363                completion_labels,
12364                &[
12365                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12366                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12367                    "single line label 2 d e f ",
12368                    "a b c g h i ",
12369                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12370                ],
12371                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12372            );
12373
12374            for completion in menu
12375                .completions
12376                .borrow()
12377                .iter() {
12378                    assert_eq!(
12379                        completion.label.filter_range,
12380                        0..completion.label.text.len(),
12381                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12382                    );
12383                }
12384        } else {
12385            panic!("expected completion menu to be open");
12386        }
12387    });
12388}
12389
12390#[gpui::test]
12391async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12392    init_test(cx, |_| {});
12393    let mut cx = EditorLspTestContext::new_rust(
12394        lsp::ServerCapabilities {
12395            completion_provider: Some(lsp::CompletionOptions {
12396                trigger_characters: Some(vec![".".to_string()]),
12397                ..Default::default()
12398            }),
12399            ..Default::default()
12400        },
12401        cx,
12402    )
12403    .await;
12404    cx.lsp
12405        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12406            Ok(Some(lsp::CompletionResponse::Array(vec![
12407                lsp::CompletionItem {
12408                    label: "first".into(),
12409                    ..Default::default()
12410                },
12411                lsp::CompletionItem {
12412                    label: "last".into(),
12413                    ..Default::default()
12414                },
12415            ])))
12416        });
12417    cx.set_state("variableˇ");
12418    cx.simulate_keystroke(".");
12419    cx.executor().run_until_parked();
12420
12421    cx.update_editor(|editor, _, _| {
12422        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12423        {
12424            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12425        } else {
12426            panic!("expected completion menu to be open");
12427        }
12428    });
12429
12430    cx.update_editor(|editor, window, cx| {
12431        editor.move_page_down(&MovePageDown::default(), window, cx);
12432        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12433        {
12434            assert!(
12435                menu.selected_item == 1,
12436                "expected PageDown to select the last item from the context menu"
12437            );
12438        } else {
12439            panic!("expected completion menu to stay open after PageDown");
12440        }
12441    });
12442
12443    cx.update_editor(|editor, window, cx| {
12444        editor.move_page_up(&MovePageUp::default(), window, cx);
12445        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12446        {
12447            assert!(
12448                menu.selected_item == 0,
12449                "expected PageUp to select the first item from the context menu"
12450            );
12451        } else {
12452            panic!("expected completion menu to stay open after PageUp");
12453        }
12454    });
12455}
12456
12457#[gpui::test]
12458async fn test_as_is_completions(cx: &mut TestAppContext) {
12459    init_test(cx, |_| {});
12460    let mut cx = EditorLspTestContext::new_rust(
12461        lsp::ServerCapabilities {
12462            completion_provider: Some(lsp::CompletionOptions {
12463                ..Default::default()
12464            }),
12465            ..Default::default()
12466        },
12467        cx,
12468    )
12469    .await;
12470    cx.lsp
12471        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12472            Ok(Some(lsp::CompletionResponse::Array(vec![
12473                lsp::CompletionItem {
12474                    label: "unsafe".into(),
12475                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12476                        range: lsp::Range {
12477                            start: lsp::Position {
12478                                line: 1,
12479                                character: 2,
12480                            },
12481                            end: lsp::Position {
12482                                line: 1,
12483                                character: 3,
12484                            },
12485                        },
12486                        new_text: "unsafe".to_string(),
12487                    })),
12488                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12489                    ..Default::default()
12490                },
12491            ])))
12492        });
12493    cx.set_state("fn a() {}\n");
12494    cx.executor().run_until_parked();
12495    cx.update_editor(|editor, window, cx| {
12496        editor.show_completions(
12497            &ShowCompletions {
12498                trigger: Some("\n".into()),
12499            },
12500            window,
12501            cx,
12502        );
12503    });
12504    cx.executor().run_until_parked();
12505
12506    cx.update_editor(|editor, window, cx| {
12507        editor.confirm_completion(&Default::default(), window, cx)
12508    });
12509    cx.executor().run_until_parked();
12510    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12511}
12512
12513#[gpui::test]
12514async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12515    init_test(cx, |_| {});
12516
12517    let mut cx = EditorLspTestContext::new_rust(
12518        lsp::ServerCapabilities {
12519            completion_provider: Some(lsp::CompletionOptions {
12520                trigger_characters: Some(vec![".".to_string()]),
12521                resolve_provider: Some(true),
12522                ..Default::default()
12523            }),
12524            ..Default::default()
12525        },
12526        cx,
12527    )
12528    .await;
12529
12530    cx.set_state("fn main() { let a = 2ˇ; }");
12531    cx.simulate_keystroke(".");
12532    let completion_item = lsp::CompletionItem {
12533        label: "Some".into(),
12534        kind: Some(lsp::CompletionItemKind::SNIPPET),
12535        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12536        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12537            kind: lsp::MarkupKind::Markdown,
12538            value: "```rust\nSome(2)\n```".to_string(),
12539        })),
12540        deprecated: Some(false),
12541        sort_text: Some("Some".to_string()),
12542        filter_text: Some("Some".to_string()),
12543        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12544        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12545            range: lsp::Range {
12546                start: lsp::Position {
12547                    line: 0,
12548                    character: 22,
12549                },
12550                end: lsp::Position {
12551                    line: 0,
12552                    character: 22,
12553                },
12554            },
12555            new_text: "Some(2)".to_string(),
12556        })),
12557        additional_text_edits: Some(vec![lsp::TextEdit {
12558            range: lsp::Range {
12559                start: lsp::Position {
12560                    line: 0,
12561                    character: 20,
12562                },
12563                end: lsp::Position {
12564                    line: 0,
12565                    character: 22,
12566                },
12567            },
12568            new_text: "".to_string(),
12569        }]),
12570        ..Default::default()
12571    };
12572
12573    let closure_completion_item = completion_item.clone();
12574    let counter = Arc::new(AtomicUsize::new(0));
12575    let counter_clone = counter.clone();
12576    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12577        let task_completion_item = closure_completion_item.clone();
12578        counter_clone.fetch_add(1, atomic::Ordering::Release);
12579        async move {
12580            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12581                is_incomplete: true,
12582                item_defaults: None,
12583                items: vec![task_completion_item],
12584            })))
12585        }
12586    });
12587
12588    cx.condition(|editor, _| editor.context_menu_visible())
12589        .await;
12590    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12591    assert!(request.next().await.is_some());
12592    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12593
12594    cx.simulate_keystrokes("S o m");
12595    cx.condition(|editor, _| editor.context_menu_visible())
12596        .await;
12597    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12598    assert!(request.next().await.is_some());
12599    assert!(request.next().await.is_some());
12600    assert!(request.next().await.is_some());
12601    request.close();
12602    assert!(request.next().await.is_none());
12603    assert_eq!(
12604        counter.load(atomic::Ordering::Acquire),
12605        4,
12606        "With the completions menu open, only one LSP request should happen per input"
12607    );
12608}
12609
12610#[gpui::test]
12611async fn test_toggle_comment(cx: &mut TestAppContext) {
12612    init_test(cx, |_| {});
12613    let mut cx = EditorTestContext::new(cx).await;
12614    let language = Arc::new(Language::new(
12615        LanguageConfig {
12616            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12617            ..Default::default()
12618        },
12619        Some(tree_sitter_rust::LANGUAGE.into()),
12620    ));
12621    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12622
12623    // If multiple selections intersect a line, the line is only toggled once.
12624    cx.set_state(indoc! {"
12625        fn a() {
12626            «//b();
12627            ˇ»// «c();
12628            //ˇ»  d();
12629        }
12630    "});
12631
12632    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12633
12634    cx.assert_editor_state(indoc! {"
12635        fn a() {
12636            «b();
12637            c();
12638            ˇ» d();
12639        }
12640    "});
12641
12642    // The comment prefix is inserted at the same column for every line in a
12643    // selection.
12644    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12645
12646    cx.assert_editor_state(indoc! {"
12647        fn a() {
12648            // «b();
12649            // c();
12650            ˇ»//  d();
12651        }
12652    "});
12653
12654    // If a selection ends at the beginning of a line, that line is not toggled.
12655    cx.set_selections_state(indoc! {"
12656        fn a() {
12657            // b();
12658            «// c();
12659        ˇ»    //  d();
12660        }
12661    "});
12662
12663    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12664
12665    cx.assert_editor_state(indoc! {"
12666        fn a() {
12667            // b();
12668            «c();
12669        ˇ»    //  d();
12670        }
12671    "});
12672
12673    // If a selection span a single line and is empty, the line is toggled.
12674    cx.set_state(indoc! {"
12675        fn a() {
12676            a();
12677            b();
12678        ˇ
12679        }
12680    "});
12681
12682    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12683
12684    cx.assert_editor_state(indoc! {"
12685        fn a() {
12686            a();
12687            b();
12688        //•ˇ
12689        }
12690    "});
12691
12692    // If a selection span multiple lines, empty lines are not toggled.
12693    cx.set_state(indoc! {"
12694        fn a() {
12695            «a();
12696
12697            c();ˇ»
12698        }
12699    "});
12700
12701    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12702
12703    cx.assert_editor_state(indoc! {"
12704        fn a() {
12705            // «a();
12706
12707            // c();ˇ»
12708        }
12709    "});
12710
12711    // If a selection includes multiple comment prefixes, all lines are uncommented.
12712    cx.set_state(indoc! {"
12713        fn a() {
12714            «// a();
12715            /// b();
12716            //! c();ˇ»
12717        }
12718    "});
12719
12720    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12721
12722    cx.assert_editor_state(indoc! {"
12723        fn a() {
12724            «a();
12725            b();
12726            c();ˇ»
12727        }
12728    "});
12729}
12730
12731#[gpui::test]
12732async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12733    init_test(cx, |_| {});
12734    let mut cx = EditorTestContext::new(cx).await;
12735    let language = Arc::new(Language::new(
12736        LanguageConfig {
12737            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12738            ..Default::default()
12739        },
12740        Some(tree_sitter_rust::LANGUAGE.into()),
12741    ));
12742    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12743
12744    let toggle_comments = &ToggleComments {
12745        advance_downwards: false,
12746        ignore_indent: true,
12747    };
12748
12749    // If multiple selections intersect a line, the line is only toggled once.
12750    cx.set_state(indoc! {"
12751        fn a() {
12752        //    «b();
12753        //    c();
12754        //    ˇ» d();
12755        }
12756    "});
12757
12758    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12759
12760    cx.assert_editor_state(indoc! {"
12761        fn a() {
12762            «b();
12763            c();
12764            ˇ» d();
12765        }
12766    "});
12767
12768    // The comment prefix is inserted at the beginning of each line
12769    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12770
12771    cx.assert_editor_state(indoc! {"
12772        fn a() {
12773        //    «b();
12774        //    c();
12775        //    ˇ» d();
12776        }
12777    "});
12778
12779    // If a selection ends at the beginning of a line, that line is not toggled.
12780    cx.set_selections_state(indoc! {"
12781        fn a() {
12782        //    b();
12783        //    «c();
12784        ˇ»//     d();
12785        }
12786    "});
12787
12788    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12789
12790    cx.assert_editor_state(indoc! {"
12791        fn a() {
12792        //    b();
12793            «c();
12794        ˇ»//     d();
12795        }
12796    "});
12797
12798    // If a selection span a single line and is empty, the line is toggled.
12799    cx.set_state(indoc! {"
12800        fn a() {
12801            a();
12802            b();
12803        ˇ
12804        }
12805    "});
12806
12807    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12808
12809    cx.assert_editor_state(indoc! {"
12810        fn a() {
12811            a();
12812            b();
12813        //ˇ
12814        }
12815    "});
12816
12817    // If a selection span multiple lines, empty lines are not toggled.
12818    cx.set_state(indoc! {"
12819        fn a() {
12820            «a();
12821
12822            c();ˇ»
12823        }
12824    "});
12825
12826    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12827
12828    cx.assert_editor_state(indoc! {"
12829        fn a() {
12830        //    «a();
12831
12832        //    c();ˇ»
12833        }
12834    "});
12835
12836    // If a selection includes multiple comment prefixes, all lines are uncommented.
12837    cx.set_state(indoc! {"
12838        fn a() {
12839        //    «a();
12840        ///    b();
12841        //!    c();ˇ»
12842        }
12843    "});
12844
12845    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12846
12847    cx.assert_editor_state(indoc! {"
12848        fn a() {
12849            «a();
12850            b();
12851            c();ˇ»
12852        }
12853    "});
12854}
12855
12856#[gpui::test]
12857async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12858    init_test(cx, |_| {});
12859
12860    let language = Arc::new(Language::new(
12861        LanguageConfig {
12862            line_comments: vec!["// ".into()],
12863            ..Default::default()
12864        },
12865        Some(tree_sitter_rust::LANGUAGE.into()),
12866    ));
12867
12868    let mut cx = EditorTestContext::new(cx).await;
12869
12870    cx.language_registry().add(language.clone());
12871    cx.update_buffer(|buffer, cx| {
12872        buffer.set_language(Some(language), cx);
12873    });
12874
12875    let toggle_comments = &ToggleComments {
12876        advance_downwards: true,
12877        ignore_indent: false,
12878    };
12879
12880    // Single cursor on one line -> advance
12881    // Cursor moves horizontally 3 characters as well on non-blank line
12882    cx.set_state(indoc!(
12883        "fn a() {
12884             ˇdog();
12885             cat();
12886        }"
12887    ));
12888    cx.update_editor(|editor, window, cx| {
12889        editor.toggle_comments(toggle_comments, window, cx);
12890    });
12891    cx.assert_editor_state(indoc!(
12892        "fn a() {
12893             // dog();
12894             catˇ();
12895        }"
12896    ));
12897
12898    // Single selection on one line -> don't advance
12899    cx.set_state(indoc!(
12900        "fn a() {
12901             «dog()ˇ»;
12902             cat();
12903        }"
12904    ));
12905    cx.update_editor(|editor, window, cx| {
12906        editor.toggle_comments(toggle_comments, window, cx);
12907    });
12908    cx.assert_editor_state(indoc!(
12909        "fn a() {
12910             // «dog()ˇ»;
12911             cat();
12912        }"
12913    ));
12914
12915    // Multiple cursors on one line -> advance
12916    cx.set_state(indoc!(
12917        "fn a() {
12918             ˇdˇog();
12919             cat();
12920        }"
12921    ));
12922    cx.update_editor(|editor, window, cx| {
12923        editor.toggle_comments(toggle_comments, window, cx);
12924    });
12925    cx.assert_editor_state(indoc!(
12926        "fn a() {
12927             // dog();
12928             catˇ(ˇ);
12929        }"
12930    ));
12931
12932    // Multiple cursors on one line, with selection -> don't advance
12933    cx.set_state(indoc!(
12934        "fn a() {
12935             ˇdˇog«()ˇ»;
12936             cat();
12937        }"
12938    ));
12939    cx.update_editor(|editor, window, cx| {
12940        editor.toggle_comments(toggle_comments, window, cx);
12941    });
12942    cx.assert_editor_state(indoc!(
12943        "fn a() {
12944             // ˇdˇog«()ˇ»;
12945             cat();
12946        }"
12947    ));
12948
12949    // Single cursor on one line -> advance
12950    // Cursor moves to column 0 on blank line
12951    cx.set_state(indoc!(
12952        "fn a() {
12953             ˇdog();
12954
12955             cat();
12956        }"
12957    ));
12958    cx.update_editor(|editor, window, cx| {
12959        editor.toggle_comments(toggle_comments, window, cx);
12960    });
12961    cx.assert_editor_state(indoc!(
12962        "fn a() {
12963             // dog();
12964        ˇ
12965             cat();
12966        }"
12967    ));
12968
12969    // Single cursor on one line -> advance
12970    // Cursor starts and ends at column 0
12971    cx.set_state(indoc!(
12972        "fn a() {
12973         ˇ    dog();
12974             cat();
12975        }"
12976    ));
12977    cx.update_editor(|editor, window, cx| {
12978        editor.toggle_comments(toggle_comments, window, cx);
12979    });
12980    cx.assert_editor_state(indoc!(
12981        "fn a() {
12982             // dog();
12983         ˇ    cat();
12984        }"
12985    ));
12986}
12987
12988#[gpui::test]
12989async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12990    init_test(cx, |_| {});
12991
12992    let mut cx = EditorTestContext::new(cx).await;
12993
12994    let html_language = Arc::new(
12995        Language::new(
12996            LanguageConfig {
12997                name: "HTML".into(),
12998                block_comment: Some(("<!-- ".into(), " -->".into())),
12999                ..Default::default()
13000            },
13001            Some(tree_sitter_html::LANGUAGE.into()),
13002        )
13003        .with_injection_query(
13004            r#"
13005            (script_element
13006                (raw_text) @injection.content
13007                (#set! injection.language "javascript"))
13008            "#,
13009        )
13010        .unwrap(),
13011    );
13012
13013    let javascript_language = Arc::new(Language::new(
13014        LanguageConfig {
13015            name: "JavaScript".into(),
13016            line_comments: vec!["// ".into()],
13017            ..Default::default()
13018        },
13019        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13020    ));
13021
13022    cx.language_registry().add(html_language.clone());
13023    cx.language_registry().add(javascript_language.clone());
13024    cx.update_buffer(|buffer, cx| {
13025        buffer.set_language(Some(html_language), cx);
13026    });
13027
13028    // Toggle comments for empty selections
13029    cx.set_state(
13030        &r#"
13031            <p>A</p>ˇ
13032            <p>B</p>ˇ
13033            <p>C</p>ˇ
13034        "#
13035        .unindent(),
13036    );
13037    cx.update_editor(|editor, window, cx| {
13038        editor.toggle_comments(&ToggleComments::default(), window, cx)
13039    });
13040    cx.assert_editor_state(
13041        &r#"
13042            <!-- <p>A</p>ˇ -->
13043            <!-- <p>B</p>ˇ -->
13044            <!-- <p>C</p>ˇ -->
13045        "#
13046        .unindent(),
13047    );
13048    cx.update_editor(|editor, window, cx| {
13049        editor.toggle_comments(&ToggleComments::default(), window, cx)
13050    });
13051    cx.assert_editor_state(
13052        &r#"
13053            <p>A</p>ˇ
13054            <p>B</p>ˇ
13055            <p>C</p>ˇ
13056        "#
13057        .unindent(),
13058    );
13059
13060    // Toggle comments for mixture of empty and non-empty selections, where
13061    // multiple selections occupy a given line.
13062    cx.set_state(
13063        &r#"
13064            <p>A«</p>
13065            <p>ˇ»B</p>ˇ
13066            <p>C«</p>
13067            <p>ˇ»D</p>ˇ
13068        "#
13069        .unindent(),
13070    );
13071
13072    cx.update_editor(|editor, window, cx| {
13073        editor.toggle_comments(&ToggleComments::default(), window, cx)
13074    });
13075    cx.assert_editor_state(
13076        &r#"
13077            <!-- <p>A«</p>
13078            <p>ˇ»B</p>ˇ -->
13079            <!-- <p>C«</p>
13080            <p>ˇ»D</p>ˇ -->
13081        "#
13082        .unindent(),
13083    );
13084    cx.update_editor(|editor, window, cx| {
13085        editor.toggle_comments(&ToggleComments::default(), window, cx)
13086    });
13087    cx.assert_editor_state(
13088        &r#"
13089            <p>A«</p>
13090            <p>ˇ»B</p>ˇ
13091            <p>C«</p>
13092            <p>ˇ»D</p>ˇ
13093        "#
13094        .unindent(),
13095    );
13096
13097    // Toggle comments when different languages are active for different
13098    // selections.
13099    cx.set_state(
13100        &r#"
13101            ˇ<script>
13102                ˇvar x = new Y();
13103            ˇ</script>
13104        "#
13105        .unindent(),
13106    );
13107    cx.executor().run_until_parked();
13108    cx.update_editor(|editor, window, cx| {
13109        editor.toggle_comments(&ToggleComments::default(), window, cx)
13110    });
13111    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13112    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13113    cx.assert_editor_state(
13114        &r#"
13115            <!-- ˇ<script> -->
13116                // ˇvar x = new Y();
13117            <!-- ˇ</script> -->
13118        "#
13119        .unindent(),
13120    );
13121}
13122
13123#[gpui::test]
13124fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13125    init_test(cx, |_| {});
13126
13127    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13128    let multibuffer = cx.new(|cx| {
13129        let mut multibuffer = MultiBuffer::new(ReadWrite);
13130        multibuffer.push_excerpts(
13131            buffer.clone(),
13132            [
13133                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13134                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13135            ],
13136            cx,
13137        );
13138        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13139        multibuffer
13140    });
13141
13142    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13143    editor.update_in(cx, |editor, window, cx| {
13144        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13145        editor.change_selections(None, window, cx, |s| {
13146            s.select_ranges([
13147                Point::new(0, 0)..Point::new(0, 0),
13148                Point::new(1, 0)..Point::new(1, 0),
13149            ])
13150        });
13151
13152        editor.handle_input("X", window, cx);
13153        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13154        assert_eq!(
13155            editor.selections.ranges(cx),
13156            [
13157                Point::new(0, 1)..Point::new(0, 1),
13158                Point::new(1, 1)..Point::new(1, 1),
13159            ]
13160        );
13161
13162        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13163        editor.change_selections(None, window, cx, |s| {
13164            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13165        });
13166        editor.backspace(&Default::default(), window, cx);
13167        assert_eq!(editor.text(cx), "Xa\nbbb");
13168        assert_eq!(
13169            editor.selections.ranges(cx),
13170            [Point::new(1, 0)..Point::new(1, 0)]
13171        );
13172
13173        editor.change_selections(None, window, cx, |s| {
13174            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13175        });
13176        editor.backspace(&Default::default(), window, cx);
13177        assert_eq!(editor.text(cx), "X\nbb");
13178        assert_eq!(
13179            editor.selections.ranges(cx),
13180            [Point::new(0, 1)..Point::new(0, 1)]
13181        );
13182    });
13183}
13184
13185#[gpui::test]
13186fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13187    init_test(cx, |_| {});
13188
13189    let markers = vec![('[', ']').into(), ('(', ')').into()];
13190    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13191        indoc! {"
13192            [aaaa
13193            (bbbb]
13194            cccc)",
13195        },
13196        markers.clone(),
13197    );
13198    let excerpt_ranges = markers.into_iter().map(|marker| {
13199        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13200        ExcerptRange::new(context.clone())
13201    });
13202    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13203    let multibuffer = cx.new(|cx| {
13204        let mut multibuffer = MultiBuffer::new(ReadWrite);
13205        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13206        multibuffer
13207    });
13208
13209    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13210    editor.update_in(cx, |editor, window, cx| {
13211        let (expected_text, selection_ranges) = marked_text_ranges(
13212            indoc! {"
13213                aaaa
13214                bˇbbb
13215                bˇbbˇb
13216                cccc"
13217            },
13218            true,
13219        );
13220        assert_eq!(editor.text(cx), expected_text);
13221        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13222
13223        editor.handle_input("X", window, cx);
13224
13225        let (expected_text, expected_selections) = marked_text_ranges(
13226            indoc! {"
13227                aaaa
13228                bXˇbbXb
13229                bXˇbbXˇb
13230                cccc"
13231            },
13232            false,
13233        );
13234        assert_eq!(editor.text(cx), expected_text);
13235        assert_eq!(editor.selections.ranges(cx), expected_selections);
13236
13237        editor.newline(&Newline, window, cx);
13238        let (expected_text, expected_selections) = marked_text_ranges(
13239            indoc! {"
13240                aaaa
13241                bX
13242                ˇbbX
13243                b
13244                bX
13245                ˇbbX
13246                ˇb
13247                cccc"
13248            },
13249            false,
13250        );
13251        assert_eq!(editor.text(cx), expected_text);
13252        assert_eq!(editor.selections.ranges(cx), expected_selections);
13253    });
13254}
13255
13256#[gpui::test]
13257fn test_refresh_selections(cx: &mut TestAppContext) {
13258    init_test(cx, |_| {});
13259
13260    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13261    let mut excerpt1_id = None;
13262    let multibuffer = cx.new(|cx| {
13263        let mut multibuffer = MultiBuffer::new(ReadWrite);
13264        excerpt1_id = multibuffer
13265            .push_excerpts(
13266                buffer.clone(),
13267                [
13268                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13269                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13270                ],
13271                cx,
13272            )
13273            .into_iter()
13274            .next();
13275        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13276        multibuffer
13277    });
13278
13279    let editor = cx.add_window(|window, cx| {
13280        let mut editor = build_editor(multibuffer.clone(), window, cx);
13281        let snapshot = editor.snapshot(window, cx);
13282        editor.change_selections(None, window, cx, |s| {
13283            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13284        });
13285        editor.begin_selection(
13286            Point::new(2, 1).to_display_point(&snapshot),
13287            true,
13288            1,
13289            window,
13290            cx,
13291        );
13292        assert_eq!(
13293            editor.selections.ranges(cx),
13294            [
13295                Point::new(1, 3)..Point::new(1, 3),
13296                Point::new(2, 1)..Point::new(2, 1),
13297            ]
13298        );
13299        editor
13300    });
13301
13302    // Refreshing selections is a no-op when excerpts haven't changed.
13303    _ = editor.update(cx, |editor, window, cx| {
13304        editor.change_selections(None, window, cx, |s| s.refresh());
13305        assert_eq!(
13306            editor.selections.ranges(cx),
13307            [
13308                Point::new(1, 3)..Point::new(1, 3),
13309                Point::new(2, 1)..Point::new(2, 1),
13310            ]
13311        );
13312    });
13313
13314    multibuffer.update(cx, |multibuffer, cx| {
13315        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13316    });
13317    _ = editor.update(cx, |editor, window, cx| {
13318        // Removing an excerpt causes the first selection to become degenerate.
13319        assert_eq!(
13320            editor.selections.ranges(cx),
13321            [
13322                Point::new(0, 0)..Point::new(0, 0),
13323                Point::new(0, 1)..Point::new(0, 1)
13324            ]
13325        );
13326
13327        // Refreshing selections will relocate the first selection to the original buffer
13328        // location.
13329        editor.change_selections(None, window, cx, |s| s.refresh());
13330        assert_eq!(
13331            editor.selections.ranges(cx),
13332            [
13333                Point::new(0, 1)..Point::new(0, 1),
13334                Point::new(0, 3)..Point::new(0, 3)
13335            ]
13336        );
13337        assert!(editor.selections.pending_anchor().is_some());
13338    });
13339}
13340
13341#[gpui::test]
13342fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13343    init_test(cx, |_| {});
13344
13345    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13346    let mut excerpt1_id = None;
13347    let multibuffer = cx.new(|cx| {
13348        let mut multibuffer = MultiBuffer::new(ReadWrite);
13349        excerpt1_id = multibuffer
13350            .push_excerpts(
13351                buffer.clone(),
13352                [
13353                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13354                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13355                ],
13356                cx,
13357            )
13358            .into_iter()
13359            .next();
13360        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13361        multibuffer
13362    });
13363
13364    let editor = cx.add_window(|window, cx| {
13365        let mut editor = build_editor(multibuffer.clone(), window, cx);
13366        let snapshot = editor.snapshot(window, cx);
13367        editor.begin_selection(
13368            Point::new(1, 3).to_display_point(&snapshot),
13369            false,
13370            1,
13371            window,
13372            cx,
13373        );
13374        assert_eq!(
13375            editor.selections.ranges(cx),
13376            [Point::new(1, 3)..Point::new(1, 3)]
13377        );
13378        editor
13379    });
13380
13381    multibuffer.update(cx, |multibuffer, cx| {
13382        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13383    });
13384    _ = editor.update(cx, |editor, window, cx| {
13385        assert_eq!(
13386            editor.selections.ranges(cx),
13387            [Point::new(0, 0)..Point::new(0, 0)]
13388        );
13389
13390        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13391        editor.change_selections(None, window, cx, |s| s.refresh());
13392        assert_eq!(
13393            editor.selections.ranges(cx),
13394            [Point::new(0, 3)..Point::new(0, 3)]
13395        );
13396        assert!(editor.selections.pending_anchor().is_some());
13397    });
13398}
13399
13400#[gpui::test]
13401async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13402    init_test(cx, |_| {});
13403
13404    let language = Arc::new(
13405        Language::new(
13406            LanguageConfig {
13407                brackets: BracketPairConfig {
13408                    pairs: vec![
13409                        BracketPair {
13410                            start: "{".to_string(),
13411                            end: "}".to_string(),
13412                            close: true,
13413                            surround: true,
13414                            newline: true,
13415                        },
13416                        BracketPair {
13417                            start: "/* ".to_string(),
13418                            end: " */".to_string(),
13419                            close: true,
13420                            surround: true,
13421                            newline: true,
13422                        },
13423                    ],
13424                    ..Default::default()
13425                },
13426                ..Default::default()
13427            },
13428            Some(tree_sitter_rust::LANGUAGE.into()),
13429        )
13430        .with_indents_query("")
13431        .unwrap(),
13432    );
13433
13434    let text = concat!(
13435        "{   }\n",     //
13436        "  x\n",       //
13437        "  /*   */\n", //
13438        "x\n",         //
13439        "{{} }\n",     //
13440    );
13441
13442    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13443    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13444    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13445    editor
13446        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13447        .await;
13448
13449    editor.update_in(cx, |editor, window, cx| {
13450        editor.change_selections(None, window, cx, |s| {
13451            s.select_display_ranges([
13452                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13453                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13454                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13455            ])
13456        });
13457        editor.newline(&Newline, window, cx);
13458
13459        assert_eq!(
13460            editor.buffer().read(cx).read(cx).text(),
13461            concat!(
13462                "{ \n",    // Suppress rustfmt
13463                "\n",      //
13464                "}\n",     //
13465                "  x\n",   //
13466                "  /* \n", //
13467                "  \n",    //
13468                "  */\n",  //
13469                "x\n",     //
13470                "{{} \n",  //
13471                "}\n",     //
13472            )
13473        );
13474    });
13475}
13476
13477#[gpui::test]
13478fn test_highlighted_ranges(cx: &mut TestAppContext) {
13479    init_test(cx, |_| {});
13480
13481    let editor = cx.add_window(|window, cx| {
13482        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13483        build_editor(buffer.clone(), window, cx)
13484    });
13485
13486    _ = editor.update(cx, |editor, window, cx| {
13487        struct Type1;
13488        struct Type2;
13489
13490        let buffer = editor.buffer.read(cx).snapshot(cx);
13491
13492        let anchor_range =
13493            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13494
13495        editor.highlight_background::<Type1>(
13496            &[
13497                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13498                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13499                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13500                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13501            ],
13502            |_| Hsla::red(),
13503            cx,
13504        );
13505        editor.highlight_background::<Type2>(
13506            &[
13507                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13508                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13509                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13510                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13511            ],
13512            |_| Hsla::green(),
13513            cx,
13514        );
13515
13516        let snapshot = editor.snapshot(window, cx);
13517        let mut highlighted_ranges = editor.background_highlights_in_range(
13518            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13519            &snapshot,
13520            cx.theme().colors(),
13521        );
13522        // Enforce a consistent ordering based on color without relying on the ordering of the
13523        // highlight's `TypeId` which is non-executor.
13524        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13525        assert_eq!(
13526            highlighted_ranges,
13527            &[
13528                (
13529                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13530                    Hsla::red(),
13531                ),
13532                (
13533                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13534                    Hsla::red(),
13535                ),
13536                (
13537                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13538                    Hsla::green(),
13539                ),
13540                (
13541                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13542                    Hsla::green(),
13543                ),
13544            ]
13545        );
13546        assert_eq!(
13547            editor.background_highlights_in_range(
13548                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13549                &snapshot,
13550                cx.theme().colors(),
13551            ),
13552            &[(
13553                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13554                Hsla::red(),
13555            )]
13556        );
13557    });
13558}
13559
13560#[gpui::test]
13561async fn test_following(cx: &mut TestAppContext) {
13562    init_test(cx, |_| {});
13563
13564    let fs = FakeFs::new(cx.executor());
13565    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13566
13567    let buffer = project.update(cx, |project, cx| {
13568        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13569        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13570    });
13571    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13572    let follower = cx.update(|cx| {
13573        cx.open_window(
13574            WindowOptions {
13575                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13576                    gpui::Point::new(px(0.), px(0.)),
13577                    gpui::Point::new(px(10.), px(80.)),
13578                ))),
13579                ..Default::default()
13580            },
13581            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13582        )
13583        .unwrap()
13584    });
13585
13586    let is_still_following = Rc::new(RefCell::new(true));
13587    let follower_edit_event_count = Rc::new(RefCell::new(0));
13588    let pending_update = Rc::new(RefCell::new(None));
13589    let leader_entity = leader.root(cx).unwrap();
13590    let follower_entity = follower.root(cx).unwrap();
13591    _ = follower.update(cx, {
13592        let update = pending_update.clone();
13593        let is_still_following = is_still_following.clone();
13594        let follower_edit_event_count = follower_edit_event_count.clone();
13595        |_, window, cx| {
13596            cx.subscribe_in(
13597                &leader_entity,
13598                window,
13599                move |_, leader, event, window, cx| {
13600                    leader.read(cx).add_event_to_update_proto(
13601                        event,
13602                        &mut update.borrow_mut(),
13603                        window,
13604                        cx,
13605                    );
13606                },
13607            )
13608            .detach();
13609
13610            cx.subscribe_in(
13611                &follower_entity,
13612                window,
13613                move |_, _, event: &EditorEvent, _window, _cx| {
13614                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13615                        *is_still_following.borrow_mut() = false;
13616                    }
13617
13618                    if let EditorEvent::BufferEdited = event {
13619                        *follower_edit_event_count.borrow_mut() += 1;
13620                    }
13621                },
13622            )
13623            .detach();
13624        }
13625    });
13626
13627    // Update the selections only
13628    _ = leader.update(cx, |leader, window, cx| {
13629        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13630    });
13631    follower
13632        .update(cx, |follower, window, cx| {
13633            follower.apply_update_proto(
13634                &project,
13635                pending_update.borrow_mut().take().unwrap(),
13636                window,
13637                cx,
13638            )
13639        })
13640        .unwrap()
13641        .await
13642        .unwrap();
13643    _ = follower.update(cx, |follower, _, cx| {
13644        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13645    });
13646    assert!(*is_still_following.borrow());
13647    assert_eq!(*follower_edit_event_count.borrow(), 0);
13648
13649    // Update the scroll position only
13650    _ = leader.update(cx, |leader, window, cx| {
13651        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13652    });
13653    follower
13654        .update(cx, |follower, window, cx| {
13655            follower.apply_update_proto(
13656                &project,
13657                pending_update.borrow_mut().take().unwrap(),
13658                window,
13659                cx,
13660            )
13661        })
13662        .unwrap()
13663        .await
13664        .unwrap();
13665    assert_eq!(
13666        follower
13667            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13668            .unwrap(),
13669        gpui::Point::new(1.5, 3.5)
13670    );
13671    assert!(*is_still_following.borrow());
13672    assert_eq!(*follower_edit_event_count.borrow(), 0);
13673
13674    // Update the selections and scroll position. The follower's scroll position is updated
13675    // via autoscroll, not via the leader's exact scroll position.
13676    _ = leader.update(cx, |leader, window, cx| {
13677        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13678        leader.request_autoscroll(Autoscroll::newest(), cx);
13679        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13680    });
13681    follower
13682        .update(cx, |follower, window, cx| {
13683            follower.apply_update_proto(
13684                &project,
13685                pending_update.borrow_mut().take().unwrap(),
13686                window,
13687                cx,
13688            )
13689        })
13690        .unwrap()
13691        .await
13692        .unwrap();
13693    _ = follower.update(cx, |follower, _, cx| {
13694        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13695        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13696    });
13697    assert!(*is_still_following.borrow());
13698
13699    // Creating a pending selection that precedes another selection
13700    _ = leader.update(cx, |leader, window, cx| {
13701        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13702        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13703    });
13704    follower
13705        .update(cx, |follower, window, cx| {
13706            follower.apply_update_proto(
13707                &project,
13708                pending_update.borrow_mut().take().unwrap(),
13709                window,
13710                cx,
13711            )
13712        })
13713        .unwrap()
13714        .await
13715        .unwrap();
13716    _ = follower.update(cx, |follower, _, cx| {
13717        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13718    });
13719    assert!(*is_still_following.borrow());
13720
13721    // Extend the pending selection so that it surrounds another selection
13722    _ = leader.update(cx, |leader, window, cx| {
13723        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13724    });
13725    follower
13726        .update(cx, |follower, window, cx| {
13727            follower.apply_update_proto(
13728                &project,
13729                pending_update.borrow_mut().take().unwrap(),
13730                window,
13731                cx,
13732            )
13733        })
13734        .unwrap()
13735        .await
13736        .unwrap();
13737    _ = follower.update(cx, |follower, _, cx| {
13738        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13739    });
13740
13741    // Scrolling locally breaks the follow
13742    _ = follower.update(cx, |follower, window, cx| {
13743        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13744        follower.set_scroll_anchor(
13745            ScrollAnchor {
13746                anchor: top_anchor,
13747                offset: gpui::Point::new(0.0, 0.5),
13748            },
13749            window,
13750            cx,
13751        );
13752    });
13753    assert!(!(*is_still_following.borrow()));
13754}
13755
13756#[gpui::test]
13757async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13758    init_test(cx, |_| {});
13759
13760    let fs = FakeFs::new(cx.executor());
13761    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13762    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13763    let pane = workspace
13764        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13765        .unwrap();
13766
13767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13768
13769    let leader = pane.update_in(cx, |_, window, cx| {
13770        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13771        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13772    });
13773
13774    // Start following the editor when it has no excerpts.
13775    let mut state_message =
13776        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13777    let workspace_entity = workspace.root(cx).unwrap();
13778    let follower_1 = cx
13779        .update_window(*workspace.deref(), |_, window, cx| {
13780            Editor::from_state_proto(
13781                workspace_entity,
13782                ViewId {
13783                    creator: CollaboratorId::PeerId(PeerId::default()),
13784                    id: 0,
13785                },
13786                &mut state_message,
13787                window,
13788                cx,
13789            )
13790        })
13791        .unwrap()
13792        .unwrap()
13793        .await
13794        .unwrap();
13795
13796    let update_message = Rc::new(RefCell::new(None));
13797    follower_1.update_in(cx, {
13798        let update = update_message.clone();
13799        |_, window, cx| {
13800            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13801                leader.read(cx).add_event_to_update_proto(
13802                    event,
13803                    &mut update.borrow_mut(),
13804                    window,
13805                    cx,
13806                );
13807            })
13808            .detach();
13809        }
13810    });
13811
13812    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13813        (
13814            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13815            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13816        )
13817    });
13818
13819    // Insert some excerpts.
13820    leader.update(cx, |leader, cx| {
13821        leader.buffer.update(cx, |multibuffer, cx| {
13822            multibuffer.set_excerpts_for_path(
13823                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13824                buffer_1.clone(),
13825                vec![
13826                    Point::row_range(0..3),
13827                    Point::row_range(1..6),
13828                    Point::row_range(12..15),
13829                ],
13830                0,
13831                cx,
13832            );
13833            multibuffer.set_excerpts_for_path(
13834                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13835                buffer_2.clone(),
13836                vec![Point::row_range(0..6), Point::row_range(8..12)],
13837                0,
13838                cx,
13839            );
13840        });
13841    });
13842
13843    // Apply the update of adding the excerpts.
13844    follower_1
13845        .update_in(cx, |follower, window, cx| {
13846            follower.apply_update_proto(
13847                &project,
13848                update_message.borrow().clone().unwrap(),
13849                window,
13850                cx,
13851            )
13852        })
13853        .await
13854        .unwrap();
13855    assert_eq!(
13856        follower_1.update(cx, |editor, cx| editor.text(cx)),
13857        leader.update(cx, |editor, cx| editor.text(cx))
13858    );
13859    update_message.borrow_mut().take();
13860
13861    // Start following separately after it already has excerpts.
13862    let mut state_message =
13863        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13864    let workspace_entity = workspace.root(cx).unwrap();
13865    let follower_2 = cx
13866        .update_window(*workspace.deref(), |_, window, cx| {
13867            Editor::from_state_proto(
13868                workspace_entity,
13869                ViewId {
13870                    creator: CollaboratorId::PeerId(PeerId::default()),
13871                    id: 0,
13872                },
13873                &mut state_message,
13874                window,
13875                cx,
13876            )
13877        })
13878        .unwrap()
13879        .unwrap()
13880        .await
13881        .unwrap();
13882    assert_eq!(
13883        follower_2.update(cx, |editor, cx| editor.text(cx)),
13884        leader.update(cx, |editor, cx| editor.text(cx))
13885    );
13886
13887    // Remove some excerpts.
13888    leader.update(cx, |leader, cx| {
13889        leader.buffer.update(cx, |multibuffer, cx| {
13890            let excerpt_ids = multibuffer.excerpt_ids();
13891            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13892            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13893        });
13894    });
13895
13896    // Apply the update of removing the excerpts.
13897    follower_1
13898        .update_in(cx, |follower, window, cx| {
13899            follower.apply_update_proto(
13900                &project,
13901                update_message.borrow().clone().unwrap(),
13902                window,
13903                cx,
13904            )
13905        })
13906        .await
13907        .unwrap();
13908    follower_2
13909        .update_in(cx, |follower, window, cx| {
13910            follower.apply_update_proto(
13911                &project,
13912                update_message.borrow().clone().unwrap(),
13913                window,
13914                cx,
13915            )
13916        })
13917        .await
13918        .unwrap();
13919    update_message.borrow_mut().take();
13920    assert_eq!(
13921        follower_1.update(cx, |editor, cx| editor.text(cx)),
13922        leader.update(cx, |editor, cx| editor.text(cx))
13923    );
13924}
13925
13926#[gpui::test]
13927async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13928    init_test(cx, |_| {});
13929
13930    let mut cx = EditorTestContext::new(cx).await;
13931    let lsp_store =
13932        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13933
13934    cx.set_state(indoc! {"
13935        ˇfn func(abc def: i32) -> u32 {
13936        }
13937    "});
13938
13939    cx.update(|_, cx| {
13940        lsp_store.update(cx, |lsp_store, cx| {
13941            lsp_store
13942                .update_diagnostics(
13943                    LanguageServerId(0),
13944                    lsp::PublishDiagnosticsParams {
13945                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13946                        version: None,
13947                        diagnostics: vec![
13948                            lsp::Diagnostic {
13949                                range: lsp::Range::new(
13950                                    lsp::Position::new(0, 11),
13951                                    lsp::Position::new(0, 12),
13952                                ),
13953                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13954                                ..Default::default()
13955                            },
13956                            lsp::Diagnostic {
13957                                range: lsp::Range::new(
13958                                    lsp::Position::new(0, 12),
13959                                    lsp::Position::new(0, 15),
13960                                ),
13961                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13962                                ..Default::default()
13963                            },
13964                            lsp::Diagnostic {
13965                                range: lsp::Range::new(
13966                                    lsp::Position::new(0, 25),
13967                                    lsp::Position::new(0, 28),
13968                                ),
13969                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13970                                ..Default::default()
13971                            },
13972                        ],
13973                    },
13974                    None,
13975                    DiagnosticSourceKind::Pushed,
13976                    &[],
13977                    cx,
13978                )
13979                .unwrap()
13980        });
13981    });
13982
13983    executor.run_until_parked();
13984
13985    cx.update_editor(|editor, window, cx| {
13986        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13987    });
13988
13989    cx.assert_editor_state(indoc! {"
13990        fn func(abc def: i32) -> ˇu32 {
13991        }
13992    "});
13993
13994    cx.update_editor(|editor, window, cx| {
13995        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13996    });
13997
13998    cx.assert_editor_state(indoc! {"
13999        fn func(abc ˇdef: i32) -> u32 {
14000        }
14001    "});
14002
14003    cx.update_editor(|editor, window, cx| {
14004        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14005    });
14006
14007    cx.assert_editor_state(indoc! {"
14008        fn func(abcˇ def: i32) -> u32 {
14009        }
14010    "});
14011
14012    cx.update_editor(|editor, window, cx| {
14013        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14014    });
14015
14016    cx.assert_editor_state(indoc! {"
14017        fn func(abc def: i32) -> ˇu32 {
14018        }
14019    "});
14020}
14021
14022#[gpui::test]
14023async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14024    init_test(cx, |_| {});
14025
14026    let mut cx = EditorTestContext::new(cx).await;
14027
14028    let diff_base = r#"
14029        use some::mod;
14030
14031        const A: u32 = 42;
14032
14033        fn main() {
14034            println!("hello");
14035
14036            println!("world");
14037        }
14038        "#
14039    .unindent();
14040
14041    // Edits are modified, removed, modified, added
14042    cx.set_state(
14043        &r#"
14044        use some::modified;
14045
14046        ˇ
14047        fn main() {
14048            println!("hello there");
14049
14050            println!("around the");
14051            println!("world");
14052        }
14053        "#
14054        .unindent(),
14055    );
14056
14057    cx.set_head_text(&diff_base);
14058    executor.run_until_parked();
14059
14060    cx.update_editor(|editor, window, cx| {
14061        //Wrap around the bottom of the buffer
14062        for _ in 0..3 {
14063            editor.go_to_next_hunk(&GoToHunk, window, cx);
14064        }
14065    });
14066
14067    cx.assert_editor_state(
14068        &r#"
14069        ˇuse some::modified;
14070
14071
14072        fn main() {
14073            println!("hello there");
14074
14075            println!("around the");
14076            println!("world");
14077        }
14078        "#
14079        .unindent(),
14080    );
14081
14082    cx.update_editor(|editor, window, cx| {
14083        //Wrap around the top of the buffer
14084        for _ in 0..2 {
14085            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14086        }
14087    });
14088
14089    cx.assert_editor_state(
14090        &r#"
14091        use some::modified;
14092
14093
14094        fn main() {
14095        ˇ    println!("hello there");
14096
14097            println!("around the");
14098            println!("world");
14099        }
14100        "#
14101        .unindent(),
14102    );
14103
14104    cx.update_editor(|editor, window, cx| {
14105        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14106    });
14107
14108    cx.assert_editor_state(
14109        &r#"
14110        use some::modified;
14111
14112        ˇ
14113        fn main() {
14114            println!("hello there");
14115
14116            println!("around the");
14117            println!("world");
14118        }
14119        "#
14120        .unindent(),
14121    );
14122
14123    cx.update_editor(|editor, window, cx| {
14124        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14125    });
14126
14127    cx.assert_editor_state(
14128        &r#"
14129        ˇuse some::modified;
14130
14131
14132        fn main() {
14133            println!("hello there");
14134
14135            println!("around the");
14136            println!("world");
14137        }
14138        "#
14139        .unindent(),
14140    );
14141
14142    cx.update_editor(|editor, window, cx| {
14143        for _ in 0..2 {
14144            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14145        }
14146    });
14147
14148    cx.assert_editor_state(
14149        &r#"
14150        use some::modified;
14151
14152
14153        fn main() {
14154        ˇ    println!("hello there");
14155
14156            println!("around the");
14157            println!("world");
14158        }
14159        "#
14160        .unindent(),
14161    );
14162
14163    cx.update_editor(|editor, window, cx| {
14164        editor.fold(&Fold, window, cx);
14165    });
14166
14167    cx.update_editor(|editor, window, cx| {
14168        editor.go_to_next_hunk(&GoToHunk, window, cx);
14169    });
14170
14171    cx.assert_editor_state(
14172        &r#"
14173        ˇuse some::modified;
14174
14175
14176        fn main() {
14177            println!("hello there");
14178
14179            println!("around the");
14180            println!("world");
14181        }
14182        "#
14183        .unindent(),
14184    );
14185}
14186
14187#[test]
14188fn test_split_words() {
14189    fn split(text: &str) -> Vec<&str> {
14190        split_words(text).collect()
14191    }
14192
14193    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14194    assert_eq!(split("hello_world"), &["hello_", "world"]);
14195    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14196    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14197    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14198    assert_eq!(split("helloworld"), &["helloworld"]);
14199
14200    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14201}
14202
14203#[gpui::test]
14204async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14205    init_test(cx, |_| {});
14206
14207    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14208    let mut assert = |before, after| {
14209        let _state_context = cx.set_state(before);
14210        cx.run_until_parked();
14211        cx.update_editor(|editor, window, cx| {
14212            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14213        });
14214        cx.run_until_parked();
14215        cx.assert_editor_state(after);
14216    };
14217
14218    // Outside bracket jumps to outside of matching bracket
14219    assert("console.logˇ(var);", "console.log(var)ˇ;");
14220    assert("console.log(var)ˇ;", "console.logˇ(var);");
14221
14222    // Inside bracket jumps to inside of matching bracket
14223    assert("console.log(ˇvar);", "console.log(varˇ);");
14224    assert("console.log(varˇ);", "console.log(ˇvar);");
14225
14226    // When outside a bracket and inside, favor jumping to the inside bracket
14227    assert(
14228        "console.log('foo', [1, 2, 3]ˇ);",
14229        "console.log(ˇ'foo', [1, 2, 3]);",
14230    );
14231    assert(
14232        "console.log(ˇ'foo', [1, 2, 3]);",
14233        "console.log('foo', [1, 2, 3]ˇ);",
14234    );
14235
14236    // Bias forward if two options are equally likely
14237    assert(
14238        "let result = curried_fun()ˇ();",
14239        "let result = curried_fun()()ˇ;",
14240    );
14241
14242    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14243    assert(
14244        indoc! {"
14245            function test() {
14246                console.log('test')ˇ
14247            }"},
14248        indoc! {"
14249            function test() {
14250                console.logˇ('test')
14251            }"},
14252    );
14253}
14254
14255#[gpui::test]
14256async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14257    init_test(cx, |_| {});
14258
14259    let fs = FakeFs::new(cx.executor());
14260    fs.insert_tree(
14261        path!("/a"),
14262        json!({
14263            "main.rs": "fn main() { let a = 5; }",
14264            "other.rs": "// Test file",
14265        }),
14266    )
14267    .await;
14268    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14269
14270    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14271    language_registry.add(Arc::new(Language::new(
14272        LanguageConfig {
14273            name: "Rust".into(),
14274            matcher: LanguageMatcher {
14275                path_suffixes: vec!["rs".to_string()],
14276                ..Default::default()
14277            },
14278            brackets: BracketPairConfig {
14279                pairs: vec![BracketPair {
14280                    start: "{".to_string(),
14281                    end: "}".to_string(),
14282                    close: true,
14283                    surround: true,
14284                    newline: true,
14285                }],
14286                disabled_scopes_by_bracket_ix: Vec::new(),
14287            },
14288            ..Default::default()
14289        },
14290        Some(tree_sitter_rust::LANGUAGE.into()),
14291    )));
14292    let mut fake_servers = language_registry.register_fake_lsp(
14293        "Rust",
14294        FakeLspAdapter {
14295            capabilities: lsp::ServerCapabilities {
14296                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14297                    first_trigger_character: "{".to_string(),
14298                    more_trigger_character: None,
14299                }),
14300                ..Default::default()
14301            },
14302            ..Default::default()
14303        },
14304    );
14305
14306    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14307
14308    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14309
14310    let worktree_id = workspace
14311        .update(cx, |workspace, _, cx| {
14312            workspace.project().update(cx, |project, cx| {
14313                project.worktrees(cx).next().unwrap().read(cx).id()
14314            })
14315        })
14316        .unwrap();
14317
14318    let buffer = project
14319        .update(cx, |project, cx| {
14320            project.open_local_buffer(path!("/a/main.rs"), cx)
14321        })
14322        .await
14323        .unwrap();
14324    let editor_handle = workspace
14325        .update(cx, |workspace, window, cx| {
14326            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14327        })
14328        .unwrap()
14329        .await
14330        .unwrap()
14331        .downcast::<Editor>()
14332        .unwrap();
14333
14334    cx.executor().start_waiting();
14335    let fake_server = fake_servers.next().await.unwrap();
14336
14337    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14338        |params, _| async move {
14339            assert_eq!(
14340                params.text_document_position.text_document.uri,
14341                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14342            );
14343            assert_eq!(
14344                params.text_document_position.position,
14345                lsp::Position::new(0, 21),
14346            );
14347
14348            Ok(Some(vec![lsp::TextEdit {
14349                new_text: "]".to_string(),
14350                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14351            }]))
14352        },
14353    );
14354
14355    editor_handle.update_in(cx, |editor, window, cx| {
14356        window.focus(&editor.focus_handle(cx));
14357        editor.change_selections(None, window, cx, |s| {
14358            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14359        });
14360        editor.handle_input("{", window, cx);
14361    });
14362
14363    cx.executor().run_until_parked();
14364
14365    buffer.update(cx, |buffer, _| {
14366        assert_eq!(
14367            buffer.text(),
14368            "fn main() { let a = {5}; }",
14369            "No extra braces from on type formatting should appear in the buffer"
14370        )
14371    });
14372}
14373
14374#[gpui::test]
14375async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14376    init_test(cx, |_| {});
14377
14378    let fs = FakeFs::new(cx.executor());
14379    fs.insert_tree(
14380        path!("/a"),
14381        json!({
14382            "main.rs": "fn main() { let a = 5; }",
14383            "other.rs": "// Test file",
14384        }),
14385    )
14386    .await;
14387
14388    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14389
14390    let server_restarts = Arc::new(AtomicUsize::new(0));
14391    let closure_restarts = Arc::clone(&server_restarts);
14392    let language_server_name = "test language server";
14393    let language_name: LanguageName = "Rust".into();
14394
14395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14396    language_registry.add(Arc::new(Language::new(
14397        LanguageConfig {
14398            name: language_name.clone(),
14399            matcher: LanguageMatcher {
14400                path_suffixes: vec!["rs".to_string()],
14401                ..Default::default()
14402            },
14403            ..Default::default()
14404        },
14405        Some(tree_sitter_rust::LANGUAGE.into()),
14406    )));
14407    let mut fake_servers = language_registry.register_fake_lsp(
14408        "Rust",
14409        FakeLspAdapter {
14410            name: language_server_name,
14411            initialization_options: Some(json!({
14412                "testOptionValue": true
14413            })),
14414            initializer: Some(Box::new(move |fake_server| {
14415                let task_restarts = Arc::clone(&closure_restarts);
14416                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14417                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14418                    futures::future::ready(Ok(()))
14419                });
14420            })),
14421            ..Default::default()
14422        },
14423    );
14424
14425    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14426    let _buffer = project
14427        .update(cx, |project, cx| {
14428            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14429        })
14430        .await
14431        .unwrap();
14432    let _fake_server = fake_servers.next().await.unwrap();
14433    update_test_language_settings(cx, |language_settings| {
14434        language_settings.languages.insert(
14435            language_name.clone(),
14436            LanguageSettingsContent {
14437                tab_size: NonZeroU32::new(8),
14438                ..Default::default()
14439            },
14440        );
14441    });
14442    cx.executor().run_until_parked();
14443    assert_eq!(
14444        server_restarts.load(atomic::Ordering::Acquire),
14445        0,
14446        "Should not restart LSP server on an unrelated change"
14447    );
14448
14449    update_test_project_settings(cx, |project_settings| {
14450        project_settings.lsp.insert(
14451            "Some other server name".into(),
14452            LspSettings {
14453                binary: None,
14454                settings: None,
14455                initialization_options: Some(json!({
14456                    "some other init value": false
14457                })),
14458                enable_lsp_tasks: false,
14459            },
14460        );
14461    });
14462    cx.executor().run_until_parked();
14463    assert_eq!(
14464        server_restarts.load(atomic::Ordering::Acquire),
14465        0,
14466        "Should not restart LSP server on an unrelated LSP settings change"
14467    );
14468
14469    update_test_project_settings(cx, |project_settings| {
14470        project_settings.lsp.insert(
14471            language_server_name.into(),
14472            LspSettings {
14473                binary: None,
14474                settings: None,
14475                initialization_options: Some(json!({
14476                    "anotherInitValue": false
14477                })),
14478                enable_lsp_tasks: false,
14479            },
14480        );
14481    });
14482    cx.executor().run_until_parked();
14483    assert_eq!(
14484        server_restarts.load(atomic::Ordering::Acquire),
14485        1,
14486        "Should restart LSP server on a related LSP settings change"
14487    );
14488
14489    update_test_project_settings(cx, |project_settings| {
14490        project_settings.lsp.insert(
14491            language_server_name.into(),
14492            LspSettings {
14493                binary: None,
14494                settings: None,
14495                initialization_options: Some(json!({
14496                    "anotherInitValue": false
14497                })),
14498                enable_lsp_tasks: false,
14499            },
14500        );
14501    });
14502    cx.executor().run_until_parked();
14503    assert_eq!(
14504        server_restarts.load(atomic::Ordering::Acquire),
14505        1,
14506        "Should not restart LSP server on a related LSP settings change that is the same"
14507    );
14508
14509    update_test_project_settings(cx, |project_settings| {
14510        project_settings.lsp.insert(
14511            language_server_name.into(),
14512            LspSettings {
14513                binary: None,
14514                settings: None,
14515                initialization_options: None,
14516                enable_lsp_tasks: false,
14517            },
14518        );
14519    });
14520    cx.executor().run_until_parked();
14521    assert_eq!(
14522        server_restarts.load(atomic::Ordering::Acquire),
14523        2,
14524        "Should restart LSP server on another related LSP settings change"
14525    );
14526}
14527
14528#[gpui::test]
14529async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14530    init_test(cx, |_| {});
14531
14532    let mut cx = EditorLspTestContext::new_rust(
14533        lsp::ServerCapabilities {
14534            completion_provider: Some(lsp::CompletionOptions {
14535                trigger_characters: Some(vec![".".to_string()]),
14536                resolve_provider: Some(true),
14537                ..Default::default()
14538            }),
14539            ..Default::default()
14540        },
14541        cx,
14542    )
14543    .await;
14544
14545    cx.set_state("fn main() { let a = 2ˇ; }");
14546    cx.simulate_keystroke(".");
14547    let completion_item = lsp::CompletionItem {
14548        label: "some".into(),
14549        kind: Some(lsp::CompletionItemKind::SNIPPET),
14550        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14551        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14552            kind: lsp::MarkupKind::Markdown,
14553            value: "```rust\nSome(2)\n```".to_string(),
14554        })),
14555        deprecated: Some(false),
14556        sort_text: Some("fffffff2".to_string()),
14557        filter_text: Some("some".to_string()),
14558        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14559        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14560            range: lsp::Range {
14561                start: lsp::Position {
14562                    line: 0,
14563                    character: 22,
14564                },
14565                end: lsp::Position {
14566                    line: 0,
14567                    character: 22,
14568                },
14569            },
14570            new_text: "Some(2)".to_string(),
14571        })),
14572        additional_text_edits: Some(vec![lsp::TextEdit {
14573            range: lsp::Range {
14574                start: lsp::Position {
14575                    line: 0,
14576                    character: 20,
14577                },
14578                end: lsp::Position {
14579                    line: 0,
14580                    character: 22,
14581                },
14582            },
14583            new_text: "".to_string(),
14584        }]),
14585        ..Default::default()
14586    };
14587
14588    let closure_completion_item = completion_item.clone();
14589    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14590        let task_completion_item = closure_completion_item.clone();
14591        async move {
14592            Ok(Some(lsp::CompletionResponse::Array(vec![
14593                task_completion_item,
14594            ])))
14595        }
14596    });
14597
14598    request.next().await;
14599
14600    cx.condition(|editor, _| editor.context_menu_visible())
14601        .await;
14602    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14603        editor
14604            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14605            .unwrap()
14606    });
14607    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14608
14609    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14610        let task_completion_item = completion_item.clone();
14611        async move { Ok(task_completion_item) }
14612    })
14613    .next()
14614    .await
14615    .unwrap();
14616    apply_additional_edits.await.unwrap();
14617    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14618}
14619
14620#[gpui::test]
14621async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14622    init_test(cx, |_| {});
14623
14624    let mut cx = EditorLspTestContext::new_rust(
14625        lsp::ServerCapabilities {
14626            completion_provider: Some(lsp::CompletionOptions {
14627                trigger_characters: Some(vec![".".to_string()]),
14628                resolve_provider: Some(true),
14629                ..Default::default()
14630            }),
14631            ..Default::default()
14632        },
14633        cx,
14634    )
14635    .await;
14636
14637    cx.set_state("fn main() { let a = 2ˇ; }");
14638    cx.simulate_keystroke(".");
14639
14640    let item1 = lsp::CompletionItem {
14641        label: "method id()".to_string(),
14642        filter_text: Some("id".to_string()),
14643        detail: None,
14644        documentation: None,
14645        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14646            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14647            new_text: ".id".to_string(),
14648        })),
14649        ..lsp::CompletionItem::default()
14650    };
14651
14652    let item2 = lsp::CompletionItem {
14653        label: "other".to_string(),
14654        filter_text: Some("other".to_string()),
14655        detail: None,
14656        documentation: None,
14657        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14658            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14659            new_text: ".other".to_string(),
14660        })),
14661        ..lsp::CompletionItem::default()
14662    };
14663
14664    let item1 = item1.clone();
14665    cx.set_request_handler::<lsp::request::Completion, _, _>({
14666        let item1 = item1.clone();
14667        move |_, _, _| {
14668            let item1 = item1.clone();
14669            let item2 = item2.clone();
14670            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14671        }
14672    })
14673    .next()
14674    .await;
14675
14676    cx.condition(|editor, _| editor.context_menu_visible())
14677        .await;
14678    cx.update_editor(|editor, _, _| {
14679        let context_menu = editor.context_menu.borrow_mut();
14680        let context_menu = context_menu
14681            .as_ref()
14682            .expect("Should have the context menu deployed");
14683        match context_menu {
14684            CodeContextMenu::Completions(completions_menu) => {
14685                let completions = completions_menu.completions.borrow_mut();
14686                assert_eq!(
14687                    completions
14688                        .iter()
14689                        .map(|completion| &completion.label.text)
14690                        .collect::<Vec<_>>(),
14691                    vec!["method id()", "other"]
14692                )
14693            }
14694            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14695        }
14696    });
14697
14698    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14699        let item1 = item1.clone();
14700        move |_, item_to_resolve, _| {
14701            let item1 = item1.clone();
14702            async move {
14703                if item1 == item_to_resolve {
14704                    Ok(lsp::CompletionItem {
14705                        label: "method id()".to_string(),
14706                        filter_text: Some("id".to_string()),
14707                        detail: Some("Now resolved!".to_string()),
14708                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14709                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14710                            range: lsp::Range::new(
14711                                lsp::Position::new(0, 22),
14712                                lsp::Position::new(0, 22),
14713                            ),
14714                            new_text: ".id".to_string(),
14715                        })),
14716                        ..lsp::CompletionItem::default()
14717                    })
14718                } else {
14719                    Ok(item_to_resolve)
14720                }
14721            }
14722        }
14723    })
14724    .next()
14725    .await
14726    .unwrap();
14727    cx.run_until_parked();
14728
14729    cx.update_editor(|editor, window, cx| {
14730        editor.context_menu_next(&Default::default(), window, cx);
14731    });
14732
14733    cx.update_editor(|editor, _, _| {
14734        let context_menu = editor.context_menu.borrow_mut();
14735        let context_menu = context_menu
14736            .as_ref()
14737            .expect("Should have the context menu deployed");
14738        match context_menu {
14739            CodeContextMenu::Completions(completions_menu) => {
14740                let completions = completions_menu.completions.borrow_mut();
14741                assert_eq!(
14742                    completions
14743                        .iter()
14744                        .map(|completion| &completion.label.text)
14745                        .collect::<Vec<_>>(),
14746                    vec!["method id() Now resolved!", "other"],
14747                    "Should update first completion label, but not second as the filter text did not match."
14748                );
14749            }
14750            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14751        }
14752    });
14753}
14754
14755#[gpui::test]
14756async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14757    init_test(cx, |_| {});
14758    let mut cx = EditorLspTestContext::new_rust(
14759        lsp::ServerCapabilities {
14760            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14761            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14762            completion_provider: Some(lsp::CompletionOptions {
14763                resolve_provider: Some(true),
14764                ..Default::default()
14765            }),
14766            ..Default::default()
14767        },
14768        cx,
14769    )
14770    .await;
14771    cx.set_state(indoc! {"
14772        struct TestStruct {
14773            field: i32
14774        }
14775
14776        fn mainˇ() {
14777            let unused_var = 42;
14778            let test_struct = TestStruct { field: 42 };
14779        }
14780    "});
14781    let symbol_range = cx.lsp_range(indoc! {"
14782        struct TestStruct {
14783            field: i32
14784        }
14785
14786        «fn main»() {
14787            let unused_var = 42;
14788            let test_struct = TestStruct { field: 42 };
14789        }
14790    "});
14791    let mut hover_requests =
14792        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14793            Ok(Some(lsp::Hover {
14794                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14795                    kind: lsp::MarkupKind::Markdown,
14796                    value: "Function documentation".to_string(),
14797                }),
14798                range: Some(symbol_range),
14799            }))
14800        });
14801
14802    // Case 1: Test that code action menu hide hover popover
14803    cx.dispatch_action(Hover);
14804    hover_requests.next().await;
14805    cx.condition(|editor, _| editor.hover_state.visible()).await;
14806    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14807        move |_, _, _| async move {
14808            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14809                lsp::CodeAction {
14810                    title: "Remove unused variable".to_string(),
14811                    kind: Some(CodeActionKind::QUICKFIX),
14812                    edit: Some(lsp::WorkspaceEdit {
14813                        changes: Some(
14814                            [(
14815                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14816                                vec![lsp::TextEdit {
14817                                    range: lsp::Range::new(
14818                                        lsp::Position::new(5, 4),
14819                                        lsp::Position::new(5, 27),
14820                                    ),
14821                                    new_text: "".to_string(),
14822                                }],
14823                            )]
14824                            .into_iter()
14825                            .collect(),
14826                        ),
14827                        ..Default::default()
14828                    }),
14829                    ..Default::default()
14830                },
14831            )]))
14832        },
14833    );
14834    cx.update_editor(|editor, window, cx| {
14835        editor.toggle_code_actions(
14836            &ToggleCodeActions {
14837                deployed_from: None,
14838                quick_launch: false,
14839            },
14840            window,
14841            cx,
14842        );
14843    });
14844    code_action_requests.next().await;
14845    cx.run_until_parked();
14846    cx.condition(|editor, _| editor.context_menu_visible())
14847        .await;
14848    cx.update_editor(|editor, _, _| {
14849        assert!(
14850            !editor.hover_state.visible(),
14851            "Hover popover should be hidden when code action menu is shown"
14852        );
14853        // Hide code actions
14854        editor.context_menu.take();
14855    });
14856
14857    // Case 2: Test that code completions hide hover popover
14858    cx.dispatch_action(Hover);
14859    hover_requests.next().await;
14860    cx.condition(|editor, _| editor.hover_state.visible()).await;
14861    let counter = Arc::new(AtomicUsize::new(0));
14862    let mut completion_requests =
14863        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14864            let counter = counter.clone();
14865            async move {
14866                counter.fetch_add(1, atomic::Ordering::Release);
14867                Ok(Some(lsp::CompletionResponse::Array(vec![
14868                    lsp::CompletionItem {
14869                        label: "main".into(),
14870                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14871                        detail: Some("() -> ()".to_string()),
14872                        ..Default::default()
14873                    },
14874                    lsp::CompletionItem {
14875                        label: "TestStruct".into(),
14876                        kind: Some(lsp::CompletionItemKind::STRUCT),
14877                        detail: Some("struct TestStruct".to_string()),
14878                        ..Default::default()
14879                    },
14880                ])))
14881            }
14882        });
14883    cx.update_editor(|editor, window, cx| {
14884        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14885    });
14886    completion_requests.next().await;
14887    cx.condition(|editor, _| editor.context_menu_visible())
14888        .await;
14889    cx.update_editor(|editor, _, _| {
14890        assert!(
14891            !editor.hover_state.visible(),
14892            "Hover popover should be hidden when completion menu is shown"
14893        );
14894    });
14895}
14896
14897#[gpui::test]
14898async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14899    init_test(cx, |_| {});
14900
14901    let mut cx = EditorLspTestContext::new_rust(
14902        lsp::ServerCapabilities {
14903            completion_provider: Some(lsp::CompletionOptions {
14904                trigger_characters: Some(vec![".".to_string()]),
14905                resolve_provider: Some(true),
14906                ..Default::default()
14907            }),
14908            ..Default::default()
14909        },
14910        cx,
14911    )
14912    .await;
14913
14914    cx.set_state("fn main() { let a = 2ˇ; }");
14915    cx.simulate_keystroke(".");
14916
14917    let unresolved_item_1 = lsp::CompletionItem {
14918        label: "id".to_string(),
14919        filter_text: Some("id".to_string()),
14920        detail: None,
14921        documentation: None,
14922        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14923            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14924            new_text: ".id".to_string(),
14925        })),
14926        ..lsp::CompletionItem::default()
14927    };
14928    let resolved_item_1 = lsp::CompletionItem {
14929        additional_text_edits: Some(vec![lsp::TextEdit {
14930            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14931            new_text: "!!".to_string(),
14932        }]),
14933        ..unresolved_item_1.clone()
14934    };
14935    let unresolved_item_2 = lsp::CompletionItem {
14936        label: "other".to_string(),
14937        filter_text: Some("other".to_string()),
14938        detail: None,
14939        documentation: None,
14940        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14941            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14942            new_text: ".other".to_string(),
14943        })),
14944        ..lsp::CompletionItem::default()
14945    };
14946    let resolved_item_2 = lsp::CompletionItem {
14947        additional_text_edits: Some(vec![lsp::TextEdit {
14948            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14949            new_text: "??".to_string(),
14950        }]),
14951        ..unresolved_item_2.clone()
14952    };
14953
14954    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14955    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14956    cx.lsp
14957        .server
14958        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14959            let unresolved_item_1 = unresolved_item_1.clone();
14960            let resolved_item_1 = resolved_item_1.clone();
14961            let unresolved_item_2 = unresolved_item_2.clone();
14962            let resolved_item_2 = resolved_item_2.clone();
14963            let resolve_requests_1 = resolve_requests_1.clone();
14964            let resolve_requests_2 = resolve_requests_2.clone();
14965            move |unresolved_request, _| {
14966                let unresolved_item_1 = unresolved_item_1.clone();
14967                let resolved_item_1 = resolved_item_1.clone();
14968                let unresolved_item_2 = unresolved_item_2.clone();
14969                let resolved_item_2 = resolved_item_2.clone();
14970                let resolve_requests_1 = resolve_requests_1.clone();
14971                let resolve_requests_2 = resolve_requests_2.clone();
14972                async move {
14973                    if unresolved_request == unresolved_item_1 {
14974                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14975                        Ok(resolved_item_1.clone())
14976                    } else if unresolved_request == unresolved_item_2 {
14977                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14978                        Ok(resolved_item_2.clone())
14979                    } else {
14980                        panic!("Unexpected completion item {unresolved_request:?}")
14981                    }
14982                }
14983            }
14984        })
14985        .detach();
14986
14987    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14988        let unresolved_item_1 = unresolved_item_1.clone();
14989        let unresolved_item_2 = unresolved_item_2.clone();
14990        async move {
14991            Ok(Some(lsp::CompletionResponse::Array(vec![
14992                unresolved_item_1,
14993                unresolved_item_2,
14994            ])))
14995        }
14996    })
14997    .next()
14998    .await;
14999
15000    cx.condition(|editor, _| editor.context_menu_visible())
15001        .await;
15002    cx.update_editor(|editor, _, _| {
15003        let context_menu = editor.context_menu.borrow_mut();
15004        let context_menu = context_menu
15005            .as_ref()
15006            .expect("Should have the context menu deployed");
15007        match context_menu {
15008            CodeContextMenu::Completions(completions_menu) => {
15009                let completions = completions_menu.completions.borrow_mut();
15010                assert_eq!(
15011                    completions
15012                        .iter()
15013                        .map(|completion| &completion.label.text)
15014                        .collect::<Vec<_>>(),
15015                    vec!["id", "other"]
15016                )
15017            }
15018            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15019        }
15020    });
15021    cx.run_until_parked();
15022
15023    cx.update_editor(|editor, window, cx| {
15024        editor.context_menu_next(&ContextMenuNext, window, cx);
15025    });
15026    cx.run_until_parked();
15027    cx.update_editor(|editor, window, cx| {
15028        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15029    });
15030    cx.run_until_parked();
15031    cx.update_editor(|editor, window, cx| {
15032        editor.context_menu_next(&ContextMenuNext, window, cx);
15033    });
15034    cx.run_until_parked();
15035    cx.update_editor(|editor, window, cx| {
15036        editor
15037            .compose_completion(&ComposeCompletion::default(), window, cx)
15038            .expect("No task returned")
15039    })
15040    .await
15041    .expect("Completion failed");
15042    cx.run_until_parked();
15043
15044    cx.update_editor(|editor, _, cx| {
15045        assert_eq!(
15046            resolve_requests_1.load(atomic::Ordering::Acquire),
15047            1,
15048            "Should always resolve once despite multiple selections"
15049        );
15050        assert_eq!(
15051            resolve_requests_2.load(atomic::Ordering::Acquire),
15052            1,
15053            "Should always resolve once after multiple selections and applying the completion"
15054        );
15055        assert_eq!(
15056            editor.text(cx),
15057            "fn main() { let a = ??.other; }",
15058            "Should use resolved data when applying the completion"
15059        );
15060    });
15061}
15062
15063#[gpui::test]
15064async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15065    init_test(cx, |_| {});
15066
15067    let item_0 = lsp::CompletionItem {
15068        label: "abs".into(),
15069        insert_text: Some("abs".into()),
15070        data: Some(json!({ "very": "special"})),
15071        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15072        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15073            lsp::InsertReplaceEdit {
15074                new_text: "abs".to_string(),
15075                insert: lsp::Range::default(),
15076                replace: lsp::Range::default(),
15077            },
15078        )),
15079        ..lsp::CompletionItem::default()
15080    };
15081    let items = iter::once(item_0.clone())
15082        .chain((11..51).map(|i| lsp::CompletionItem {
15083            label: format!("item_{}", i),
15084            insert_text: Some(format!("item_{}", i)),
15085            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15086            ..lsp::CompletionItem::default()
15087        }))
15088        .collect::<Vec<_>>();
15089
15090    let default_commit_characters = vec!["?".to_string()];
15091    let default_data = json!({ "default": "data"});
15092    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15093    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15094    let default_edit_range = lsp::Range {
15095        start: lsp::Position {
15096            line: 0,
15097            character: 5,
15098        },
15099        end: lsp::Position {
15100            line: 0,
15101            character: 5,
15102        },
15103    };
15104
15105    let mut cx = EditorLspTestContext::new_rust(
15106        lsp::ServerCapabilities {
15107            completion_provider: Some(lsp::CompletionOptions {
15108                trigger_characters: Some(vec![".".to_string()]),
15109                resolve_provider: Some(true),
15110                ..Default::default()
15111            }),
15112            ..Default::default()
15113        },
15114        cx,
15115    )
15116    .await;
15117
15118    cx.set_state("fn main() { let a = 2ˇ; }");
15119    cx.simulate_keystroke(".");
15120
15121    let completion_data = default_data.clone();
15122    let completion_characters = default_commit_characters.clone();
15123    let completion_items = items.clone();
15124    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15125        let default_data = completion_data.clone();
15126        let default_commit_characters = completion_characters.clone();
15127        let items = completion_items.clone();
15128        async move {
15129            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15130                items,
15131                item_defaults: Some(lsp::CompletionListItemDefaults {
15132                    data: Some(default_data.clone()),
15133                    commit_characters: Some(default_commit_characters.clone()),
15134                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15135                        default_edit_range,
15136                    )),
15137                    insert_text_format: Some(default_insert_text_format),
15138                    insert_text_mode: Some(default_insert_text_mode),
15139                }),
15140                ..lsp::CompletionList::default()
15141            })))
15142        }
15143    })
15144    .next()
15145    .await;
15146
15147    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15148    cx.lsp
15149        .server
15150        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15151            let closure_resolved_items = resolved_items.clone();
15152            move |item_to_resolve, _| {
15153                let closure_resolved_items = closure_resolved_items.clone();
15154                async move {
15155                    closure_resolved_items.lock().push(item_to_resolve.clone());
15156                    Ok(item_to_resolve)
15157                }
15158            }
15159        })
15160        .detach();
15161
15162    cx.condition(|editor, _| editor.context_menu_visible())
15163        .await;
15164    cx.run_until_parked();
15165    cx.update_editor(|editor, _, _| {
15166        let menu = editor.context_menu.borrow_mut();
15167        match menu.as_ref().expect("should have the completions menu") {
15168            CodeContextMenu::Completions(completions_menu) => {
15169                assert_eq!(
15170                    completions_menu
15171                        .entries
15172                        .borrow()
15173                        .iter()
15174                        .map(|mat| mat.string.clone())
15175                        .collect::<Vec<String>>(),
15176                    items
15177                        .iter()
15178                        .map(|completion| completion.label.clone())
15179                        .collect::<Vec<String>>()
15180                );
15181            }
15182            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15183        }
15184    });
15185    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15186    // with 4 from the end.
15187    assert_eq!(
15188        *resolved_items.lock(),
15189        [&items[0..16], &items[items.len() - 4..items.len()]]
15190            .concat()
15191            .iter()
15192            .cloned()
15193            .map(|mut item| {
15194                if item.data.is_none() {
15195                    item.data = Some(default_data.clone());
15196                }
15197                item
15198            })
15199            .collect::<Vec<lsp::CompletionItem>>(),
15200        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15201    );
15202    resolved_items.lock().clear();
15203
15204    cx.update_editor(|editor, window, cx| {
15205        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15206    });
15207    cx.run_until_parked();
15208    // Completions that have already been resolved are skipped.
15209    assert_eq!(
15210        *resolved_items.lock(),
15211        items[items.len() - 16..items.len() - 4]
15212            .iter()
15213            .cloned()
15214            .map(|mut item| {
15215                if item.data.is_none() {
15216                    item.data = Some(default_data.clone());
15217                }
15218                item
15219            })
15220            .collect::<Vec<lsp::CompletionItem>>()
15221    );
15222    resolved_items.lock().clear();
15223}
15224
15225#[gpui::test]
15226async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15227    init_test(cx, |_| {});
15228
15229    let mut cx = EditorLspTestContext::new(
15230        Language::new(
15231            LanguageConfig {
15232                matcher: LanguageMatcher {
15233                    path_suffixes: vec!["jsx".into()],
15234                    ..Default::default()
15235                },
15236                overrides: [(
15237                    "element".into(),
15238                    LanguageConfigOverride {
15239                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15240                        ..Default::default()
15241                    },
15242                )]
15243                .into_iter()
15244                .collect(),
15245                ..Default::default()
15246            },
15247            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15248        )
15249        .with_override_query("(jsx_self_closing_element) @element")
15250        .unwrap(),
15251        lsp::ServerCapabilities {
15252            completion_provider: Some(lsp::CompletionOptions {
15253                trigger_characters: Some(vec![":".to_string()]),
15254                ..Default::default()
15255            }),
15256            ..Default::default()
15257        },
15258        cx,
15259    )
15260    .await;
15261
15262    cx.lsp
15263        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15264            Ok(Some(lsp::CompletionResponse::Array(vec![
15265                lsp::CompletionItem {
15266                    label: "bg-blue".into(),
15267                    ..Default::default()
15268                },
15269                lsp::CompletionItem {
15270                    label: "bg-red".into(),
15271                    ..Default::default()
15272                },
15273                lsp::CompletionItem {
15274                    label: "bg-yellow".into(),
15275                    ..Default::default()
15276                },
15277            ])))
15278        });
15279
15280    cx.set_state(r#"<p class="bgˇ" />"#);
15281
15282    // Trigger completion when typing a dash, because the dash is an extra
15283    // word character in the 'element' scope, which contains the cursor.
15284    cx.simulate_keystroke("-");
15285    cx.executor().run_until_parked();
15286    cx.update_editor(|editor, _, _| {
15287        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15288        {
15289            assert_eq!(
15290                completion_menu_entries(&menu),
15291                &["bg-red", "bg-blue", "bg-yellow"]
15292            );
15293        } else {
15294            panic!("expected completion menu to be open");
15295        }
15296    });
15297
15298    cx.simulate_keystroke("l");
15299    cx.executor().run_until_parked();
15300    cx.update_editor(|editor, _, _| {
15301        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15302        {
15303            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15304        } else {
15305            panic!("expected completion menu to be open");
15306        }
15307    });
15308
15309    // When filtering completions, consider the character after the '-' to
15310    // be the start of a subword.
15311    cx.set_state(r#"<p class="yelˇ" />"#);
15312    cx.simulate_keystroke("l");
15313    cx.executor().run_until_parked();
15314    cx.update_editor(|editor, _, _| {
15315        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15316        {
15317            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15318        } else {
15319            panic!("expected completion menu to be open");
15320        }
15321    });
15322}
15323
15324fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15325    let entries = menu.entries.borrow();
15326    entries.iter().map(|mat| mat.string.clone()).collect()
15327}
15328
15329#[gpui::test]
15330async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15331    init_test(cx, |settings| {
15332        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15333            FormatterList(vec![Formatter::Prettier].into()),
15334        ))
15335    });
15336
15337    let fs = FakeFs::new(cx.executor());
15338    fs.insert_file(path!("/file.ts"), Default::default()).await;
15339
15340    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15341    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15342
15343    language_registry.add(Arc::new(Language::new(
15344        LanguageConfig {
15345            name: "TypeScript".into(),
15346            matcher: LanguageMatcher {
15347                path_suffixes: vec!["ts".to_string()],
15348                ..Default::default()
15349            },
15350            ..Default::default()
15351        },
15352        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15353    )));
15354    update_test_language_settings(cx, |settings| {
15355        settings.defaults.prettier = Some(PrettierSettings {
15356            allowed: true,
15357            ..PrettierSettings::default()
15358        });
15359    });
15360
15361    let test_plugin = "test_plugin";
15362    let _ = language_registry.register_fake_lsp(
15363        "TypeScript",
15364        FakeLspAdapter {
15365            prettier_plugins: vec![test_plugin],
15366            ..Default::default()
15367        },
15368    );
15369
15370    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15371    let buffer = project
15372        .update(cx, |project, cx| {
15373            project.open_local_buffer(path!("/file.ts"), cx)
15374        })
15375        .await
15376        .unwrap();
15377
15378    let buffer_text = "one\ntwo\nthree\n";
15379    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15380    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15381    editor.update_in(cx, |editor, window, cx| {
15382        editor.set_text(buffer_text, window, cx)
15383    });
15384
15385    editor
15386        .update_in(cx, |editor, window, cx| {
15387            editor.perform_format(
15388                project.clone(),
15389                FormatTrigger::Manual,
15390                FormatTarget::Buffers,
15391                window,
15392                cx,
15393            )
15394        })
15395        .unwrap()
15396        .await;
15397    assert_eq!(
15398        editor.update(cx, |editor, cx| editor.text(cx)),
15399        buffer_text.to_string() + prettier_format_suffix,
15400        "Test prettier formatting was not applied to the original buffer text",
15401    );
15402
15403    update_test_language_settings(cx, |settings| {
15404        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15405    });
15406    let format = editor.update_in(cx, |editor, window, cx| {
15407        editor.perform_format(
15408            project.clone(),
15409            FormatTrigger::Manual,
15410            FormatTarget::Buffers,
15411            window,
15412            cx,
15413        )
15414    });
15415    format.await.unwrap();
15416    assert_eq!(
15417        editor.update(cx, |editor, cx| editor.text(cx)),
15418        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15419        "Autoformatting (via test prettier) was not applied to the original buffer text",
15420    );
15421}
15422
15423#[gpui::test]
15424async fn test_addition_reverts(cx: &mut TestAppContext) {
15425    init_test(cx, |_| {});
15426    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15427    let base_text = indoc! {r#"
15428        struct Row;
15429        struct Row1;
15430        struct Row2;
15431
15432        struct Row4;
15433        struct Row5;
15434        struct Row6;
15435
15436        struct Row8;
15437        struct Row9;
15438        struct Row10;"#};
15439
15440    // When addition hunks are not adjacent to carets, no hunk revert is performed
15441    assert_hunk_revert(
15442        indoc! {r#"struct Row;
15443                   struct Row1;
15444                   struct Row1.1;
15445                   struct Row1.2;
15446                   struct Row2;ˇ
15447
15448                   struct Row4;
15449                   struct Row5;
15450                   struct Row6;
15451
15452                   struct Row8;
15453                   ˇstruct Row9;
15454                   struct Row9.1;
15455                   struct Row9.2;
15456                   struct Row9.3;
15457                   struct Row10;"#},
15458        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15459        indoc! {r#"struct Row;
15460                   struct Row1;
15461                   struct Row1.1;
15462                   struct Row1.2;
15463                   struct Row2;ˇ
15464
15465                   struct Row4;
15466                   struct Row5;
15467                   struct Row6;
15468
15469                   struct Row8;
15470                   ˇstruct Row9;
15471                   struct Row9.1;
15472                   struct Row9.2;
15473                   struct Row9.3;
15474                   struct Row10;"#},
15475        base_text,
15476        &mut cx,
15477    );
15478    // Same for selections
15479    assert_hunk_revert(
15480        indoc! {r#"struct Row;
15481                   struct Row1;
15482                   struct Row2;
15483                   struct Row2.1;
15484                   struct Row2.2;
15485                   «ˇ
15486                   struct Row4;
15487                   struct» Row5;
15488                   «struct Row6;
15489                   ˇ»
15490                   struct Row9.1;
15491                   struct Row9.2;
15492                   struct Row9.3;
15493                   struct Row8;
15494                   struct Row9;
15495                   struct Row10;"#},
15496        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15497        indoc! {r#"struct Row;
15498                   struct Row1;
15499                   struct Row2;
15500                   struct Row2.1;
15501                   struct Row2.2;
15502                   «ˇ
15503                   struct Row4;
15504                   struct» Row5;
15505                   «struct Row6;
15506                   ˇ»
15507                   struct Row9.1;
15508                   struct Row9.2;
15509                   struct Row9.3;
15510                   struct Row8;
15511                   struct Row9;
15512                   struct Row10;"#},
15513        base_text,
15514        &mut cx,
15515    );
15516
15517    // When carets and selections intersect the addition hunks, those are reverted.
15518    // Adjacent carets got merged.
15519    assert_hunk_revert(
15520        indoc! {r#"struct Row;
15521                   ˇ// something on the top
15522                   struct Row1;
15523                   struct Row2;
15524                   struct Roˇw3.1;
15525                   struct Row2.2;
15526                   struct Row2.3;ˇ
15527
15528                   struct Row4;
15529                   struct ˇRow5.1;
15530                   struct Row5.2;
15531                   struct «Rowˇ»5.3;
15532                   struct Row5;
15533                   struct Row6;
15534                   ˇ
15535                   struct Row9.1;
15536                   struct «Rowˇ»9.2;
15537                   struct «ˇRow»9.3;
15538                   struct Row8;
15539                   struct Row9;
15540                   «ˇ// something on bottom»
15541                   struct Row10;"#},
15542        vec![
15543            DiffHunkStatusKind::Added,
15544            DiffHunkStatusKind::Added,
15545            DiffHunkStatusKind::Added,
15546            DiffHunkStatusKind::Added,
15547            DiffHunkStatusKind::Added,
15548        ],
15549        indoc! {r#"struct Row;
15550                   ˇstruct Row1;
15551                   struct Row2;
15552                   ˇ
15553                   struct Row4;
15554                   ˇstruct Row5;
15555                   struct Row6;
15556                   ˇ
15557                   ˇstruct Row8;
15558                   struct Row9;
15559                   ˇstruct Row10;"#},
15560        base_text,
15561        &mut cx,
15562    );
15563}
15564
15565#[gpui::test]
15566async fn test_modification_reverts(cx: &mut TestAppContext) {
15567    init_test(cx, |_| {});
15568    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15569    let base_text = indoc! {r#"
15570        struct Row;
15571        struct Row1;
15572        struct Row2;
15573
15574        struct Row4;
15575        struct Row5;
15576        struct Row6;
15577
15578        struct Row8;
15579        struct Row9;
15580        struct Row10;"#};
15581
15582    // Modification hunks behave the same as the addition ones.
15583    assert_hunk_revert(
15584        indoc! {r#"struct Row;
15585                   struct Row1;
15586                   struct Row33;
15587                   ˇ
15588                   struct Row4;
15589                   struct Row5;
15590                   struct Row6;
15591                   ˇ
15592                   struct Row99;
15593                   struct Row9;
15594                   struct Row10;"#},
15595        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15596        indoc! {r#"struct Row;
15597                   struct Row1;
15598                   struct Row33;
15599                   ˇ
15600                   struct Row4;
15601                   struct Row5;
15602                   struct Row6;
15603                   ˇ
15604                   struct Row99;
15605                   struct Row9;
15606                   struct Row10;"#},
15607        base_text,
15608        &mut cx,
15609    );
15610    assert_hunk_revert(
15611        indoc! {r#"struct Row;
15612                   struct Row1;
15613                   struct Row33;
15614                   «ˇ
15615                   struct Row4;
15616                   struct» Row5;
15617                   «struct Row6;
15618                   ˇ»
15619                   struct Row99;
15620                   struct Row9;
15621                   struct Row10;"#},
15622        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15623        indoc! {r#"struct Row;
15624                   struct Row1;
15625                   struct Row33;
15626                   «ˇ
15627                   struct Row4;
15628                   struct» Row5;
15629                   «struct Row6;
15630                   ˇ»
15631                   struct Row99;
15632                   struct Row9;
15633                   struct Row10;"#},
15634        base_text,
15635        &mut cx,
15636    );
15637
15638    assert_hunk_revert(
15639        indoc! {r#"ˇstruct Row1.1;
15640                   struct Row1;
15641                   «ˇstr»uct Row22;
15642
15643                   struct ˇRow44;
15644                   struct Row5;
15645                   struct «Rˇ»ow66;ˇ
15646
15647                   «struˇ»ct Row88;
15648                   struct Row9;
15649                   struct Row1011;ˇ"#},
15650        vec![
15651            DiffHunkStatusKind::Modified,
15652            DiffHunkStatusKind::Modified,
15653            DiffHunkStatusKind::Modified,
15654            DiffHunkStatusKind::Modified,
15655            DiffHunkStatusKind::Modified,
15656            DiffHunkStatusKind::Modified,
15657        ],
15658        indoc! {r#"struct Row;
15659                   ˇstruct Row1;
15660                   struct Row2;
15661                   ˇ
15662                   struct Row4;
15663                   ˇstruct Row5;
15664                   struct Row6;
15665                   ˇ
15666                   struct Row8;
15667                   ˇstruct Row9;
15668                   struct Row10;ˇ"#},
15669        base_text,
15670        &mut cx,
15671    );
15672}
15673
15674#[gpui::test]
15675async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15676    init_test(cx, |_| {});
15677    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15678    let base_text = indoc! {r#"
15679        one
15680
15681        two
15682        three
15683        "#};
15684
15685    cx.set_head_text(base_text);
15686    cx.set_state("\nˇ\n");
15687    cx.executor().run_until_parked();
15688    cx.update_editor(|editor, _window, cx| {
15689        editor.expand_selected_diff_hunks(cx);
15690    });
15691    cx.executor().run_until_parked();
15692    cx.update_editor(|editor, window, cx| {
15693        editor.backspace(&Default::default(), window, cx);
15694    });
15695    cx.run_until_parked();
15696    cx.assert_state_with_diff(
15697        indoc! {r#"
15698
15699        - two
15700        - threeˇ
15701        +
15702        "#}
15703        .to_string(),
15704    );
15705}
15706
15707#[gpui::test]
15708async fn test_deletion_reverts(cx: &mut TestAppContext) {
15709    init_test(cx, |_| {});
15710    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15711    let base_text = indoc! {r#"struct Row;
15712struct Row1;
15713struct Row2;
15714
15715struct Row4;
15716struct Row5;
15717struct Row6;
15718
15719struct Row8;
15720struct Row9;
15721struct Row10;"#};
15722
15723    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15724    assert_hunk_revert(
15725        indoc! {r#"struct Row;
15726                   struct Row2;
15727
15728                   ˇstruct Row4;
15729                   struct Row5;
15730                   struct Row6;
15731                   ˇ
15732                   struct Row8;
15733                   struct Row10;"#},
15734        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15735        indoc! {r#"struct Row;
15736                   struct Row2;
15737
15738                   ˇstruct Row4;
15739                   struct Row5;
15740                   struct Row6;
15741                   ˇ
15742                   struct Row8;
15743                   struct Row10;"#},
15744        base_text,
15745        &mut cx,
15746    );
15747    assert_hunk_revert(
15748        indoc! {r#"struct Row;
15749                   struct Row2;
15750
15751                   «ˇstruct Row4;
15752                   struct» Row5;
15753                   «struct Row6;
15754                   ˇ»
15755                   struct Row8;
15756                   struct Row10;"#},
15757        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15758        indoc! {r#"struct Row;
15759                   struct Row2;
15760
15761                   «ˇstruct Row4;
15762                   struct» Row5;
15763                   «struct Row6;
15764                   ˇ»
15765                   struct Row8;
15766                   struct Row10;"#},
15767        base_text,
15768        &mut cx,
15769    );
15770
15771    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15772    assert_hunk_revert(
15773        indoc! {r#"struct Row;
15774                   ˇstruct Row2;
15775
15776                   struct Row4;
15777                   struct Row5;
15778                   struct Row6;
15779
15780                   struct Row8;ˇ
15781                   struct Row10;"#},
15782        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15783        indoc! {r#"struct Row;
15784                   struct Row1;
15785                   ˇstruct Row2;
15786
15787                   struct Row4;
15788                   struct Row5;
15789                   struct Row6;
15790
15791                   struct Row8;ˇ
15792                   struct Row9;
15793                   struct Row10;"#},
15794        base_text,
15795        &mut cx,
15796    );
15797    assert_hunk_revert(
15798        indoc! {r#"struct Row;
15799                   struct Row2«ˇ;
15800                   struct Row4;
15801                   struct» Row5;
15802                   «struct Row6;
15803
15804                   struct Row8;ˇ»
15805                   struct Row10;"#},
15806        vec![
15807            DiffHunkStatusKind::Deleted,
15808            DiffHunkStatusKind::Deleted,
15809            DiffHunkStatusKind::Deleted,
15810        ],
15811        indoc! {r#"struct Row;
15812                   struct Row1;
15813                   struct Row2«ˇ;
15814
15815                   struct Row4;
15816                   struct» Row5;
15817                   «struct Row6;
15818
15819                   struct Row8;ˇ»
15820                   struct Row9;
15821                   struct Row10;"#},
15822        base_text,
15823        &mut cx,
15824    );
15825}
15826
15827#[gpui::test]
15828async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15829    init_test(cx, |_| {});
15830
15831    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15832    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15833    let base_text_3 =
15834        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15835
15836    let text_1 = edit_first_char_of_every_line(base_text_1);
15837    let text_2 = edit_first_char_of_every_line(base_text_2);
15838    let text_3 = edit_first_char_of_every_line(base_text_3);
15839
15840    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15841    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15842    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15843
15844    let multibuffer = cx.new(|cx| {
15845        let mut multibuffer = MultiBuffer::new(ReadWrite);
15846        multibuffer.push_excerpts(
15847            buffer_1.clone(),
15848            [
15849                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15850                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15851                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15852            ],
15853            cx,
15854        );
15855        multibuffer.push_excerpts(
15856            buffer_2.clone(),
15857            [
15858                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15859                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15860                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15861            ],
15862            cx,
15863        );
15864        multibuffer.push_excerpts(
15865            buffer_3.clone(),
15866            [
15867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15870            ],
15871            cx,
15872        );
15873        multibuffer
15874    });
15875
15876    let fs = FakeFs::new(cx.executor());
15877    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15878    let (editor, cx) = cx
15879        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15880    editor.update_in(cx, |editor, _window, cx| {
15881        for (buffer, diff_base) in [
15882            (buffer_1.clone(), base_text_1),
15883            (buffer_2.clone(), base_text_2),
15884            (buffer_3.clone(), base_text_3),
15885        ] {
15886            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15887            editor
15888                .buffer
15889                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15890        }
15891    });
15892    cx.executor().run_until_parked();
15893
15894    editor.update_in(cx, |editor, window, cx| {
15895        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
15896        editor.select_all(&SelectAll, window, cx);
15897        editor.git_restore(&Default::default(), window, cx);
15898    });
15899    cx.executor().run_until_parked();
15900
15901    // When all ranges are selected, all buffer hunks are reverted.
15902    editor.update(cx, |editor, cx| {
15903        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");
15904    });
15905    buffer_1.update(cx, |buffer, _| {
15906        assert_eq!(buffer.text(), base_text_1);
15907    });
15908    buffer_2.update(cx, |buffer, _| {
15909        assert_eq!(buffer.text(), base_text_2);
15910    });
15911    buffer_3.update(cx, |buffer, _| {
15912        assert_eq!(buffer.text(), base_text_3);
15913    });
15914
15915    editor.update_in(cx, |editor, window, cx| {
15916        editor.undo(&Default::default(), window, cx);
15917    });
15918
15919    editor.update_in(cx, |editor, window, cx| {
15920        editor.change_selections(None, window, cx, |s| {
15921            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15922        });
15923        editor.git_restore(&Default::default(), window, cx);
15924    });
15925
15926    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15927    // but not affect buffer_2 and its related excerpts.
15928    editor.update(cx, |editor, cx| {
15929        assert_eq!(
15930            editor.text(cx),
15931            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
15932        );
15933    });
15934    buffer_1.update(cx, |buffer, _| {
15935        assert_eq!(buffer.text(), base_text_1);
15936    });
15937    buffer_2.update(cx, |buffer, _| {
15938        assert_eq!(
15939            buffer.text(),
15940            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15941        );
15942    });
15943    buffer_3.update(cx, |buffer, _| {
15944        assert_eq!(
15945            buffer.text(),
15946            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15947        );
15948    });
15949
15950    fn edit_first_char_of_every_line(text: &str) -> String {
15951        text.split('\n')
15952            .map(|line| format!("X{}", &line[1..]))
15953            .collect::<Vec<_>>()
15954            .join("\n")
15955    }
15956}
15957
15958#[gpui::test]
15959async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15960    init_test(cx, |_| {});
15961
15962    let cols = 4;
15963    let rows = 10;
15964    let sample_text_1 = sample_text(rows, cols, 'a');
15965    assert_eq!(
15966        sample_text_1,
15967        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15968    );
15969    let sample_text_2 = sample_text(rows, cols, 'l');
15970    assert_eq!(
15971        sample_text_2,
15972        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15973    );
15974    let sample_text_3 = sample_text(rows, cols, 'v');
15975    assert_eq!(
15976        sample_text_3,
15977        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15978    );
15979
15980    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15981    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15982    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15983
15984    let multi_buffer = cx.new(|cx| {
15985        let mut multibuffer = MultiBuffer::new(ReadWrite);
15986        multibuffer.push_excerpts(
15987            buffer_1.clone(),
15988            [
15989                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15990                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15991                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15992            ],
15993            cx,
15994        );
15995        multibuffer.push_excerpts(
15996            buffer_2.clone(),
15997            [
15998                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15999                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16000                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16001            ],
16002            cx,
16003        );
16004        multibuffer.push_excerpts(
16005            buffer_3.clone(),
16006            [
16007                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16008                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16009                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16010            ],
16011            cx,
16012        );
16013        multibuffer
16014    });
16015
16016    let fs = FakeFs::new(cx.executor());
16017    fs.insert_tree(
16018        "/a",
16019        json!({
16020            "main.rs": sample_text_1,
16021            "other.rs": sample_text_2,
16022            "lib.rs": sample_text_3,
16023        }),
16024    )
16025    .await;
16026    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16027    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16028    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16029    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16030        Editor::new(
16031            EditorMode::full(),
16032            multi_buffer,
16033            Some(project.clone()),
16034            window,
16035            cx,
16036        )
16037    });
16038    let multibuffer_item_id = workspace
16039        .update(cx, |workspace, window, cx| {
16040            assert!(
16041                workspace.active_item(cx).is_none(),
16042                "active item should be None before the first item is added"
16043            );
16044            workspace.add_item_to_active_pane(
16045                Box::new(multi_buffer_editor.clone()),
16046                None,
16047                true,
16048                window,
16049                cx,
16050            );
16051            let active_item = workspace
16052                .active_item(cx)
16053                .expect("should have an active item after adding the multi buffer");
16054            assert!(
16055                !active_item.is_singleton(cx),
16056                "A multi buffer was expected to active after adding"
16057            );
16058            active_item.item_id()
16059        })
16060        .unwrap();
16061    cx.executor().run_until_parked();
16062
16063    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16064        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16065            s.select_ranges(Some(1..2))
16066        });
16067        editor.open_excerpts(&OpenExcerpts, window, cx);
16068    });
16069    cx.executor().run_until_parked();
16070    let first_item_id = workspace
16071        .update(cx, |workspace, window, cx| {
16072            let active_item = workspace
16073                .active_item(cx)
16074                .expect("should have an active item after navigating into the 1st buffer");
16075            let first_item_id = active_item.item_id();
16076            assert_ne!(
16077                first_item_id, multibuffer_item_id,
16078                "Should navigate into the 1st buffer and activate it"
16079            );
16080            assert!(
16081                active_item.is_singleton(cx),
16082                "New active item should be a singleton buffer"
16083            );
16084            assert_eq!(
16085                active_item
16086                    .act_as::<Editor>(cx)
16087                    .expect("should have navigated into an editor for the 1st buffer")
16088                    .read(cx)
16089                    .text(cx),
16090                sample_text_1
16091            );
16092
16093            workspace
16094                .go_back(workspace.active_pane().downgrade(), window, cx)
16095                .detach_and_log_err(cx);
16096
16097            first_item_id
16098        })
16099        .unwrap();
16100    cx.executor().run_until_parked();
16101    workspace
16102        .update(cx, |workspace, _, cx| {
16103            let active_item = workspace
16104                .active_item(cx)
16105                .expect("should have an active item after navigating back");
16106            assert_eq!(
16107                active_item.item_id(),
16108                multibuffer_item_id,
16109                "Should navigate back to the multi buffer"
16110            );
16111            assert!(!active_item.is_singleton(cx));
16112        })
16113        .unwrap();
16114
16115    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16116        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16117            s.select_ranges(Some(39..40))
16118        });
16119        editor.open_excerpts(&OpenExcerpts, window, cx);
16120    });
16121    cx.executor().run_until_parked();
16122    let second_item_id = workspace
16123        .update(cx, |workspace, window, cx| {
16124            let active_item = workspace
16125                .active_item(cx)
16126                .expect("should have an active item after navigating into the 2nd buffer");
16127            let second_item_id = active_item.item_id();
16128            assert_ne!(
16129                second_item_id, multibuffer_item_id,
16130                "Should navigate away from the multibuffer"
16131            );
16132            assert_ne!(
16133                second_item_id, first_item_id,
16134                "Should navigate into the 2nd buffer and activate it"
16135            );
16136            assert!(
16137                active_item.is_singleton(cx),
16138                "New active item should be a singleton buffer"
16139            );
16140            assert_eq!(
16141                active_item
16142                    .act_as::<Editor>(cx)
16143                    .expect("should have navigated into an editor")
16144                    .read(cx)
16145                    .text(cx),
16146                sample_text_2
16147            );
16148
16149            workspace
16150                .go_back(workspace.active_pane().downgrade(), window, cx)
16151                .detach_and_log_err(cx);
16152
16153            second_item_id
16154        })
16155        .unwrap();
16156    cx.executor().run_until_parked();
16157    workspace
16158        .update(cx, |workspace, _, cx| {
16159            let active_item = workspace
16160                .active_item(cx)
16161                .expect("should have an active item after navigating back from the 2nd buffer");
16162            assert_eq!(
16163                active_item.item_id(),
16164                multibuffer_item_id,
16165                "Should navigate back from the 2nd buffer to the multi buffer"
16166            );
16167            assert!(!active_item.is_singleton(cx));
16168        })
16169        .unwrap();
16170
16171    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16172        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16173            s.select_ranges(Some(70..70))
16174        });
16175        editor.open_excerpts(&OpenExcerpts, window, cx);
16176    });
16177    cx.executor().run_until_parked();
16178    workspace
16179        .update(cx, |workspace, window, cx| {
16180            let active_item = workspace
16181                .active_item(cx)
16182                .expect("should have an active item after navigating into the 3rd buffer");
16183            let third_item_id = active_item.item_id();
16184            assert_ne!(
16185                third_item_id, multibuffer_item_id,
16186                "Should navigate into the 3rd buffer and activate it"
16187            );
16188            assert_ne!(third_item_id, first_item_id);
16189            assert_ne!(third_item_id, second_item_id);
16190            assert!(
16191                active_item.is_singleton(cx),
16192                "New active item should be a singleton buffer"
16193            );
16194            assert_eq!(
16195                active_item
16196                    .act_as::<Editor>(cx)
16197                    .expect("should have navigated into an editor")
16198                    .read(cx)
16199                    .text(cx),
16200                sample_text_3
16201            );
16202
16203            workspace
16204                .go_back(workspace.active_pane().downgrade(), window, cx)
16205                .detach_and_log_err(cx);
16206        })
16207        .unwrap();
16208    cx.executor().run_until_parked();
16209    workspace
16210        .update(cx, |workspace, _, cx| {
16211            let active_item = workspace
16212                .active_item(cx)
16213                .expect("should have an active item after navigating back from the 3rd buffer");
16214            assert_eq!(
16215                active_item.item_id(),
16216                multibuffer_item_id,
16217                "Should navigate back from the 3rd buffer to the multi buffer"
16218            );
16219            assert!(!active_item.is_singleton(cx));
16220        })
16221        .unwrap();
16222}
16223
16224#[gpui::test]
16225async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16226    init_test(cx, |_| {});
16227
16228    let mut cx = EditorTestContext::new(cx).await;
16229
16230    let diff_base = r#"
16231        use some::mod;
16232
16233        const A: u32 = 42;
16234
16235        fn main() {
16236            println!("hello");
16237
16238            println!("world");
16239        }
16240        "#
16241    .unindent();
16242
16243    cx.set_state(
16244        &r#"
16245        use some::modified;
16246
16247        ˇ
16248        fn main() {
16249            println!("hello there");
16250
16251            println!("around the");
16252            println!("world");
16253        }
16254        "#
16255        .unindent(),
16256    );
16257
16258    cx.set_head_text(&diff_base);
16259    executor.run_until_parked();
16260
16261    cx.update_editor(|editor, window, cx| {
16262        editor.go_to_next_hunk(&GoToHunk, window, cx);
16263        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16264    });
16265    executor.run_until_parked();
16266    cx.assert_state_with_diff(
16267        r#"
16268          use some::modified;
16269
16270
16271          fn main() {
16272        -     println!("hello");
16273        + ˇ    println!("hello there");
16274
16275              println!("around the");
16276              println!("world");
16277          }
16278        "#
16279        .unindent(),
16280    );
16281
16282    cx.update_editor(|editor, window, cx| {
16283        for _ in 0..2 {
16284            editor.go_to_next_hunk(&GoToHunk, window, cx);
16285            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16286        }
16287    });
16288    executor.run_until_parked();
16289    cx.assert_state_with_diff(
16290        r#"
16291        - use some::mod;
16292        + ˇuse some::modified;
16293
16294
16295          fn main() {
16296        -     println!("hello");
16297        +     println!("hello there");
16298
16299        +     println!("around the");
16300              println!("world");
16301          }
16302        "#
16303        .unindent(),
16304    );
16305
16306    cx.update_editor(|editor, window, cx| {
16307        editor.go_to_next_hunk(&GoToHunk, window, cx);
16308        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16309    });
16310    executor.run_until_parked();
16311    cx.assert_state_with_diff(
16312        r#"
16313        - use some::mod;
16314        + use some::modified;
16315
16316        - const A: u32 = 42;
16317          ˇ
16318          fn main() {
16319        -     println!("hello");
16320        +     println!("hello there");
16321
16322        +     println!("around the");
16323              println!("world");
16324          }
16325        "#
16326        .unindent(),
16327    );
16328
16329    cx.update_editor(|editor, window, cx| {
16330        editor.cancel(&Cancel, window, cx);
16331    });
16332
16333    cx.assert_state_with_diff(
16334        r#"
16335          use some::modified;
16336
16337          ˇ
16338          fn main() {
16339              println!("hello there");
16340
16341              println!("around the");
16342              println!("world");
16343          }
16344        "#
16345        .unindent(),
16346    );
16347}
16348
16349#[gpui::test]
16350async fn test_diff_base_change_with_expanded_diff_hunks(
16351    executor: BackgroundExecutor,
16352    cx: &mut TestAppContext,
16353) {
16354    init_test(cx, |_| {});
16355
16356    let mut cx = EditorTestContext::new(cx).await;
16357
16358    let diff_base = r#"
16359        use some::mod1;
16360        use some::mod2;
16361
16362        const A: u32 = 42;
16363        const B: u32 = 42;
16364        const C: u32 = 42;
16365
16366        fn main() {
16367            println!("hello");
16368
16369            println!("world");
16370        }
16371        "#
16372    .unindent();
16373
16374    cx.set_state(
16375        &r#"
16376        use some::mod2;
16377
16378        const A: u32 = 42;
16379        const C: u32 = 42;
16380
16381        fn main(ˇ) {
16382            //println!("hello");
16383
16384            println!("world");
16385            //
16386            //
16387        }
16388        "#
16389        .unindent(),
16390    );
16391
16392    cx.set_head_text(&diff_base);
16393    executor.run_until_parked();
16394
16395    cx.update_editor(|editor, window, cx| {
16396        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16397    });
16398    executor.run_until_parked();
16399    cx.assert_state_with_diff(
16400        r#"
16401        - use some::mod1;
16402          use some::mod2;
16403
16404          const A: u32 = 42;
16405        - const B: u32 = 42;
16406          const C: u32 = 42;
16407
16408          fn main(ˇ) {
16409        -     println!("hello");
16410        +     //println!("hello");
16411
16412              println!("world");
16413        +     //
16414        +     //
16415          }
16416        "#
16417        .unindent(),
16418    );
16419
16420    cx.set_head_text("new diff base!");
16421    executor.run_until_parked();
16422    cx.assert_state_with_diff(
16423        r#"
16424        - new diff base!
16425        + use some::mod2;
16426        +
16427        + const A: u32 = 42;
16428        + const C: u32 = 42;
16429        +
16430        + fn main(ˇ) {
16431        +     //println!("hello");
16432        +
16433        +     println!("world");
16434        +     //
16435        +     //
16436        + }
16437        "#
16438        .unindent(),
16439    );
16440}
16441
16442#[gpui::test]
16443async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16444    init_test(cx, |_| {});
16445
16446    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16447    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16448    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16449    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16450    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16451    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16452
16453    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16454    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16455    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16456
16457    let multi_buffer = cx.new(|cx| {
16458        let mut multibuffer = MultiBuffer::new(ReadWrite);
16459        multibuffer.push_excerpts(
16460            buffer_1.clone(),
16461            [
16462                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16463                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16464                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16465            ],
16466            cx,
16467        );
16468        multibuffer.push_excerpts(
16469            buffer_2.clone(),
16470            [
16471                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16472                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16473                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16474            ],
16475            cx,
16476        );
16477        multibuffer.push_excerpts(
16478            buffer_3.clone(),
16479            [
16480                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16481                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16482                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16483            ],
16484            cx,
16485        );
16486        multibuffer
16487    });
16488
16489    let editor =
16490        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16491    editor
16492        .update(cx, |editor, _window, cx| {
16493            for (buffer, diff_base) in [
16494                (buffer_1.clone(), file_1_old),
16495                (buffer_2.clone(), file_2_old),
16496                (buffer_3.clone(), file_3_old),
16497            ] {
16498                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16499                editor
16500                    .buffer
16501                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16502            }
16503        })
16504        .unwrap();
16505
16506    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16507    cx.run_until_parked();
16508
16509    cx.assert_editor_state(
16510        &"
16511            ˇaaa
16512            ccc
16513            ddd
16514
16515            ggg
16516            hhh
16517
16518
16519            lll
16520            mmm
16521            NNN
16522
16523            qqq
16524            rrr
16525
16526            uuu
16527            111
16528            222
16529            333
16530
16531            666
16532            777
16533
16534            000
16535            !!!"
16536        .unindent(),
16537    );
16538
16539    cx.update_editor(|editor, window, cx| {
16540        editor.select_all(&SelectAll, window, cx);
16541        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16542    });
16543    cx.executor().run_until_parked();
16544
16545    cx.assert_state_with_diff(
16546        "
16547            «aaa
16548          - bbb
16549            ccc
16550            ddd
16551
16552            ggg
16553            hhh
16554
16555
16556            lll
16557            mmm
16558          - nnn
16559          + NNN
16560
16561            qqq
16562            rrr
16563
16564            uuu
16565            111
16566            222
16567            333
16568
16569          + 666
16570            777
16571
16572            000
16573            !!!ˇ»"
16574            .unindent(),
16575    );
16576}
16577
16578#[gpui::test]
16579async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16580    init_test(cx, |_| {});
16581
16582    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16583    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16584
16585    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16586    let multi_buffer = cx.new(|cx| {
16587        let mut multibuffer = MultiBuffer::new(ReadWrite);
16588        multibuffer.push_excerpts(
16589            buffer.clone(),
16590            [
16591                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16592                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16593                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16594            ],
16595            cx,
16596        );
16597        multibuffer
16598    });
16599
16600    let editor =
16601        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16602    editor
16603        .update(cx, |editor, _window, cx| {
16604            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16605            editor
16606                .buffer
16607                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16608        })
16609        .unwrap();
16610
16611    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16612    cx.run_until_parked();
16613
16614    cx.update_editor(|editor, window, cx| {
16615        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16616    });
16617    cx.executor().run_until_parked();
16618
16619    // When the start of a hunk coincides with the start of its excerpt,
16620    // the hunk is expanded. When the start of a a hunk is earlier than
16621    // the start of its excerpt, the hunk is not expanded.
16622    cx.assert_state_with_diff(
16623        "
16624            ˇaaa
16625          - bbb
16626          + BBB
16627
16628          - ddd
16629          - eee
16630          + DDD
16631          + EEE
16632            fff
16633
16634            iii
16635        "
16636        .unindent(),
16637    );
16638}
16639
16640#[gpui::test]
16641async fn test_edits_around_expanded_insertion_hunks(
16642    executor: BackgroundExecutor,
16643    cx: &mut TestAppContext,
16644) {
16645    init_test(cx, |_| {});
16646
16647    let mut cx = EditorTestContext::new(cx).await;
16648
16649    let diff_base = r#"
16650        use some::mod1;
16651        use some::mod2;
16652
16653        const A: u32 = 42;
16654
16655        fn main() {
16656            println!("hello");
16657
16658            println!("world");
16659        }
16660        "#
16661    .unindent();
16662    executor.run_until_parked();
16663    cx.set_state(
16664        &r#"
16665        use some::mod1;
16666        use some::mod2;
16667
16668        const A: u32 = 42;
16669        const B: u32 = 42;
16670        const C: u32 = 42;
16671        ˇ
16672
16673        fn main() {
16674            println!("hello");
16675
16676            println!("world");
16677        }
16678        "#
16679        .unindent(),
16680    );
16681
16682    cx.set_head_text(&diff_base);
16683    executor.run_until_parked();
16684
16685    cx.update_editor(|editor, window, cx| {
16686        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16687    });
16688    executor.run_until_parked();
16689
16690    cx.assert_state_with_diff(
16691        r#"
16692        use some::mod1;
16693        use some::mod2;
16694
16695        const A: u32 = 42;
16696      + const B: u32 = 42;
16697      + const C: u32 = 42;
16698      + ˇ
16699
16700        fn main() {
16701            println!("hello");
16702
16703            println!("world");
16704        }
16705      "#
16706        .unindent(),
16707    );
16708
16709    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16710    executor.run_until_parked();
16711
16712    cx.assert_state_with_diff(
16713        r#"
16714        use some::mod1;
16715        use some::mod2;
16716
16717        const A: u32 = 42;
16718      + const B: u32 = 42;
16719      + const C: u32 = 42;
16720      + const D: u32 = 42;
16721      + ˇ
16722
16723        fn main() {
16724            println!("hello");
16725
16726            println!("world");
16727        }
16728      "#
16729        .unindent(),
16730    );
16731
16732    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16733    executor.run_until_parked();
16734
16735    cx.assert_state_with_diff(
16736        r#"
16737        use some::mod1;
16738        use some::mod2;
16739
16740        const A: u32 = 42;
16741      + const B: u32 = 42;
16742      + const C: u32 = 42;
16743      + const D: u32 = 42;
16744      + const E: u32 = 42;
16745      + ˇ
16746
16747        fn main() {
16748            println!("hello");
16749
16750            println!("world");
16751        }
16752      "#
16753        .unindent(),
16754    );
16755
16756    cx.update_editor(|editor, window, cx| {
16757        editor.delete_line(&DeleteLine, window, cx);
16758    });
16759    executor.run_until_parked();
16760
16761    cx.assert_state_with_diff(
16762        r#"
16763        use some::mod1;
16764        use some::mod2;
16765
16766        const A: u32 = 42;
16767      + const B: u32 = 42;
16768      + const C: u32 = 42;
16769      + const D: u32 = 42;
16770      + const E: u32 = 42;
16771        ˇ
16772        fn main() {
16773            println!("hello");
16774
16775            println!("world");
16776        }
16777      "#
16778        .unindent(),
16779    );
16780
16781    cx.update_editor(|editor, window, cx| {
16782        editor.move_up(&MoveUp, window, cx);
16783        editor.delete_line(&DeleteLine, window, cx);
16784        editor.move_up(&MoveUp, window, cx);
16785        editor.delete_line(&DeleteLine, window, cx);
16786        editor.move_up(&MoveUp, window, cx);
16787        editor.delete_line(&DeleteLine, window, cx);
16788    });
16789    executor.run_until_parked();
16790    cx.assert_state_with_diff(
16791        r#"
16792        use some::mod1;
16793        use some::mod2;
16794
16795        const A: u32 = 42;
16796      + const B: u32 = 42;
16797        ˇ
16798        fn main() {
16799            println!("hello");
16800
16801            println!("world");
16802        }
16803      "#
16804        .unindent(),
16805    );
16806
16807    cx.update_editor(|editor, window, cx| {
16808        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16809        editor.delete_line(&DeleteLine, window, cx);
16810    });
16811    executor.run_until_parked();
16812    cx.assert_state_with_diff(
16813        r#"
16814        ˇ
16815        fn main() {
16816            println!("hello");
16817
16818            println!("world");
16819        }
16820      "#
16821        .unindent(),
16822    );
16823}
16824
16825#[gpui::test]
16826async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16827    init_test(cx, |_| {});
16828
16829    let mut cx = EditorTestContext::new(cx).await;
16830    cx.set_head_text(indoc! { "
16831        one
16832        two
16833        three
16834        four
16835        five
16836        "
16837    });
16838    cx.set_state(indoc! { "
16839        one
16840        ˇthree
16841        five
16842    "});
16843    cx.run_until_parked();
16844    cx.update_editor(|editor, window, cx| {
16845        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16846    });
16847    cx.assert_state_with_diff(
16848        indoc! { "
16849        one
16850      - two
16851        ˇthree
16852      - four
16853        five
16854    "}
16855        .to_string(),
16856    );
16857    cx.update_editor(|editor, window, cx| {
16858        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16859    });
16860
16861    cx.assert_state_with_diff(
16862        indoc! { "
16863        one
16864        ˇthree
16865        five
16866    "}
16867        .to_string(),
16868    );
16869
16870    cx.set_state(indoc! { "
16871        one
16872        ˇTWO
16873        three
16874        four
16875        five
16876    "});
16877    cx.run_until_parked();
16878    cx.update_editor(|editor, window, cx| {
16879        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16880    });
16881
16882    cx.assert_state_with_diff(
16883        indoc! { "
16884            one
16885          - two
16886          + ˇTWO
16887            three
16888            four
16889            five
16890        "}
16891        .to_string(),
16892    );
16893    cx.update_editor(|editor, window, cx| {
16894        editor.move_up(&Default::default(), window, cx);
16895        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16896    });
16897    cx.assert_state_with_diff(
16898        indoc! { "
16899            one
16900            ˇTWO
16901            three
16902            four
16903            five
16904        "}
16905        .to_string(),
16906    );
16907}
16908
16909#[gpui::test]
16910async fn test_edits_around_expanded_deletion_hunks(
16911    executor: BackgroundExecutor,
16912    cx: &mut TestAppContext,
16913) {
16914    init_test(cx, |_| {});
16915
16916    let mut cx = EditorTestContext::new(cx).await;
16917
16918    let diff_base = r#"
16919        use some::mod1;
16920        use some::mod2;
16921
16922        const A: u32 = 42;
16923        const B: u32 = 42;
16924        const C: u32 = 42;
16925
16926
16927        fn main() {
16928            println!("hello");
16929
16930            println!("world");
16931        }
16932    "#
16933    .unindent();
16934    executor.run_until_parked();
16935    cx.set_state(
16936        &r#"
16937        use some::mod1;
16938        use some::mod2;
16939
16940        ˇconst B: u32 = 42;
16941        const C: u32 = 42;
16942
16943
16944        fn main() {
16945            println!("hello");
16946
16947            println!("world");
16948        }
16949        "#
16950        .unindent(),
16951    );
16952
16953    cx.set_head_text(&diff_base);
16954    executor.run_until_parked();
16955
16956    cx.update_editor(|editor, window, cx| {
16957        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16958    });
16959    executor.run_until_parked();
16960
16961    cx.assert_state_with_diff(
16962        r#"
16963        use some::mod1;
16964        use some::mod2;
16965
16966      - const A: u32 = 42;
16967        ˇconst B: u32 = 42;
16968        const C: u32 = 42;
16969
16970
16971        fn main() {
16972            println!("hello");
16973
16974            println!("world");
16975        }
16976      "#
16977        .unindent(),
16978    );
16979
16980    cx.update_editor(|editor, window, cx| {
16981        editor.delete_line(&DeleteLine, window, cx);
16982    });
16983    executor.run_until_parked();
16984    cx.assert_state_with_diff(
16985        r#"
16986        use some::mod1;
16987        use some::mod2;
16988
16989      - const A: u32 = 42;
16990      - const B: u32 = 42;
16991        ˇconst C: u32 = 42;
16992
16993
16994        fn main() {
16995            println!("hello");
16996
16997            println!("world");
16998        }
16999      "#
17000        .unindent(),
17001    );
17002
17003    cx.update_editor(|editor, window, cx| {
17004        editor.delete_line(&DeleteLine, window, cx);
17005    });
17006    executor.run_until_parked();
17007    cx.assert_state_with_diff(
17008        r#"
17009        use some::mod1;
17010        use some::mod2;
17011
17012      - const A: u32 = 42;
17013      - const B: u32 = 42;
17014      - const C: u32 = 42;
17015        ˇ
17016
17017        fn main() {
17018            println!("hello");
17019
17020            println!("world");
17021        }
17022      "#
17023        .unindent(),
17024    );
17025
17026    cx.update_editor(|editor, window, cx| {
17027        editor.handle_input("replacement", window, cx);
17028    });
17029    executor.run_until_parked();
17030    cx.assert_state_with_diff(
17031        r#"
17032        use some::mod1;
17033        use some::mod2;
17034
17035      - const A: u32 = 42;
17036      - const B: u32 = 42;
17037      - const C: u32 = 42;
17038      -
17039      + replacementˇ
17040
17041        fn main() {
17042            println!("hello");
17043
17044            println!("world");
17045        }
17046      "#
17047        .unindent(),
17048    );
17049}
17050
17051#[gpui::test]
17052async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17053    init_test(cx, |_| {});
17054
17055    let mut cx = EditorTestContext::new(cx).await;
17056
17057    let base_text = r#"
17058        one
17059        two
17060        three
17061        four
17062        five
17063    "#
17064    .unindent();
17065    executor.run_until_parked();
17066    cx.set_state(
17067        &r#"
17068        one
17069        two
17070        fˇour
17071        five
17072        "#
17073        .unindent(),
17074    );
17075
17076    cx.set_head_text(&base_text);
17077    executor.run_until_parked();
17078
17079    cx.update_editor(|editor, window, cx| {
17080        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17081    });
17082    executor.run_until_parked();
17083
17084    cx.assert_state_with_diff(
17085        r#"
17086          one
17087          two
17088        - three
17089          fˇour
17090          five
17091        "#
17092        .unindent(),
17093    );
17094
17095    cx.update_editor(|editor, window, cx| {
17096        editor.backspace(&Backspace, window, cx);
17097        editor.backspace(&Backspace, window, cx);
17098    });
17099    executor.run_until_parked();
17100    cx.assert_state_with_diff(
17101        r#"
17102          one
17103          two
17104        - threeˇ
17105        - four
17106        + our
17107          five
17108        "#
17109        .unindent(),
17110    );
17111}
17112
17113#[gpui::test]
17114async fn test_edit_after_expanded_modification_hunk(
17115    executor: BackgroundExecutor,
17116    cx: &mut TestAppContext,
17117) {
17118    init_test(cx, |_| {});
17119
17120    let mut cx = EditorTestContext::new(cx).await;
17121
17122    let diff_base = r#"
17123        use some::mod1;
17124        use some::mod2;
17125
17126        const A: u32 = 42;
17127        const B: u32 = 42;
17128        const C: u32 = 42;
17129        const D: u32 = 42;
17130
17131
17132        fn main() {
17133            println!("hello");
17134
17135            println!("world");
17136        }"#
17137    .unindent();
17138
17139    cx.set_state(
17140        &r#"
17141        use some::mod1;
17142        use some::mod2;
17143
17144        const A: u32 = 42;
17145        const B: u32 = 42;
17146        const C: u32 = 43ˇ
17147        const D: u32 = 42;
17148
17149
17150        fn main() {
17151            println!("hello");
17152
17153            println!("world");
17154        }"#
17155        .unindent(),
17156    );
17157
17158    cx.set_head_text(&diff_base);
17159    executor.run_until_parked();
17160    cx.update_editor(|editor, window, cx| {
17161        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17162    });
17163    executor.run_until_parked();
17164
17165    cx.assert_state_with_diff(
17166        r#"
17167        use some::mod1;
17168        use some::mod2;
17169
17170        const A: u32 = 42;
17171        const B: u32 = 42;
17172      - const C: u32 = 42;
17173      + const C: u32 = 43ˇ
17174        const D: u32 = 42;
17175
17176
17177        fn main() {
17178            println!("hello");
17179
17180            println!("world");
17181        }"#
17182        .unindent(),
17183    );
17184
17185    cx.update_editor(|editor, window, cx| {
17186        editor.handle_input("\nnew_line\n", window, cx);
17187    });
17188    executor.run_until_parked();
17189
17190    cx.assert_state_with_diff(
17191        r#"
17192        use some::mod1;
17193        use some::mod2;
17194
17195        const A: u32 = 42;
17196        const B: u32 = 42;
17197      - const C: u32 = 42;
17198      + const C: u32 = 43
17199      + new_line
17200      + ˇ
17201        const D: u32 = 42;
17202
17203
17204        fn main() {
17205            println!("hello");
17206
17207            println!("world");
17208        }"#
17209        .unindent(),
17210    );
17211}
17212
17213#[gpui::test]
17214async fn test_stage_and_unstage_added_file_hunk(
17215    executor: BackgroundExecutor,
17216    cx: &mut TestAppContext,
17217) {
17218    init_test(cx, |_| {});
17219
17220    let mut cx = EditorTestContext::new(cx).await;
17221    cx.update_editor(|editor, _, cx| {
17222        editor.set_expand_all_diff_hunks(cx);
17223    });
17224
17225    let working_copy = r#"
17226            ˇfn main() {
17227                println!("hello, world!");
17228            }
17229        "#
17230    .unindent();
17231
17232    cx.set_state(&working_copy);
17233    executor.run_until_parked();
17234
17235    cx.assert_state_with_diff(
17236        r#"
17237            + ˇfn main() {
17238            +     println!("hello, world!");
17239            + }
17240        "#
17241        .unindent(),
17242    );
17243    cx.assert_index_text(None);
17244
17245    cx.update_editor(|editor, window, cx| {
17246        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17247    });
17248    executor.run_until_parked();
17249    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17250    cx.assert_state_with_diff(
17251        r#"
17252            + ˇfn main() {
17253            +     println!("hello, world!");
17254            + }
17255        "#
17256        .unindent(),
17257    );
17258
17259    cx.update_editor(|editor, window, cx| {
17260        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17261    });
17262    executor.run_until_parked();
17263    cx.assert_index_text(None);
17264}
17265
17266async fn setup_indent_guides_editor(
17267    text: &str,
17268    cx: &mut TestAppContext,
17269) -> (BufferId, EditorTestContext) {
17270    init_test(cx, |_| {});
17271
17272    let mut cx = EditorTestContext::new(cx).await;
17273
17274    let buffer_id = cx.update_editor(|editor, window, cx| {
17275        editor.set_text(text, window, cx);
17276        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17277
17278        buffer_ids[0]
17279    });
17280
17281    (buffer_id, cx)
17282}
17283
17284fn assert_indent_guides(
17285    range: Range<u32>,
17286    expected: Vec<IndentGuide>,
17287    active_indices: Option<Vec<usize>>,
17288    cx: &mut EditorTestContext,
17289) {
17290    let indent_guides = cx.update_editor(|editor, window, cx| {
17291        let snapshot = editor.snapshot(window, cx).display_snapshot;
17292        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17293            editor,
17294            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17295            true,
17296            &snapshot,
17297            cx,
17298        );
17299
17300        indent_guides.sort_by(|a, b| {
17301            a.depth.cmp(&b.depth).then(
17302                a.start_row
17303                    .cmp(&b.start_row)
17304                    .then(a.end_row.cmp(&b.end_row)),
17305            )
17306        });
17307        indent_guides
17308    });
17309
17310    if let Some(expected) = active_indices {
17311        let active_indices = cx.update_editor(|editor, window, cx| {
17312            let snapshot = editor.snapshot(window, cx).display_snapshot;
17313            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17314        });
17315
17316        assert_eq!(
17317            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17318            expected,
17319            "Active indent guide indices do not match"
17320        );
17321    }
17322
17323    assert_eq!(indent_guides, expected, "Indent guides do not match");
17324}
17325
17326fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17327    IndentGuide {
17328        buffer_id,
17329        start_row: MultiBufferRow(start_row),
17330        end_row: MultiBufferRow(end_row),
17331        depth,
17332        tab_size: 4,
17333        settings: IndentGuideSettings {
17334            enabled: true,
17335            line_width: 1,
17336            active_line_width: 1,
17337            ..Default::default()
17338        },
17339    }
17340}
17341
17342#[gpui::test]
17343async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17344    let (buffer_id, mut cx) = setup_indent_guides_editor(
17345        &"
17346        fn main() {
17347            let a = 1;
17348        }"
17349        .unindent(),
17350        cx,
17351    )
17352    .await;
17353
17354    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17355}
17356
17357#[gpui::test]
17358async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17359    let (buffer_id, mut cx) = setup_indent_guides_editor(
17360        &"
17361        fn main() {
17362            let a = 1;
17363            let b = 2;
17364        }"
17365        .unindent(),
17366        cx,
17367    )
17368    .await;
17369
17370    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17371}
17372
17373#[gpui::test]
17374async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17375    let (buffer_id, mut cx) = setup_indent_guides_editor(
17376        &"
17377        fn main() {
17378            let a = 1;
17379            if a == 3 {
17380                let b = 2;
17381            } else {
17382                let c = 3;
17383            }
17384        }"
17385        .unindent(),
17386        cx,
17387    )
17388    .await;
17389
17390    assert_indent_guides(
17391        0..8,
17392        vec![
17393            indent_guide(buffer_id, 1, 6, 0),
17394            indent_guide(buffer_id, 3, 3, 1),
17395            indent_guide(buffer_id, 5, 5, 1),
17396        ],
17397        None,
17398        &mut cx,
17399    );
17400}
17401
17402#[gpui::test]
17403async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17404    let (buffer_id, mut cx) = setup_indent_guides_editor(
17405        &"
17406        fn main() {
17407            let a = 1;
17408                let b = 2;
17409            let c = 3;
17410        }"
17411        .unindent(),
17412        cx,
17413    )
17414    .await;
17415
17416    assert_indent_guides(
17417        0..5,
17418        vec![
17419            indent_guide(buffer_id, 1, 3, 0),
17420            indent_guide(buffer_id, 2, 2, 1),
17421        ],
17422        None,
17423        &mut cx,
17424    );
17425}
17426
17427#[gpui::test]
17428async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17429    let (buffer_id, mut cx) = setup_indent_guides_editor(
17430        &"
17431        fn main() {
17432            let a = 1;
17433
17434            let c = 3;
17435        }"
17436        .unindent(),
17437        cx,
17438    )
17439    .await;
17440
17441    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17442}
17443
17444#[gpui::test]
17445async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17446    let (buffer_id, mut cx) = setup_indent_guides_editor(
17447        &"
17448        fn main() {
17449            let a = 1;
17450
17451            let c = 3;
17452
17453            if a == 3 {
17454                let b = 2;
17455            } else {
17456                let c = 3;
17457            }
17458        }"
17459        .unindent(),
17460        cx,
17461    )
17462    .await;
17463
17464    assert_indent_guides(
17465        0..11,
17466        vec![
17467            indent_guide(buffer_id, 1, 9, 0),
17468            indent_guide(buffer_id, 6, 6, 1),
17469            indent_guide(buffer_id, 8, 8, 1),
17470        ],
17471        None,
17472        &mut cx,
17473    );
17474}
17475
17476#[gpui::test]
17477async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17478    let (buffer_id, mut cx) = setup_indent_guides_editor(
17479        &"
17480        fn main() {
17481            let a = 1;
17482
17483            let c = 3;
17484
17485            if a == 3 {
17486                let b = 2;
17487            } else {
17488                let c = 3;
17489            }
17490        }"
17491        .unindent(),
17492        cx,
17493    )
17494    .await;
17495
17496    assert_indent_guides(
17497        1..11,
17498        vec![
17499            indent_guide(buffer_id, 1, 9, 0),
17500            indent_guide(buffer_id, 6, 6, 1),
17501            indent_guide(buffer_id, 8, 8, 1),
17502        ],
17503        None,
17504        &mut cx,
17505    );
17506}
17507
17508#[gpui::test]
17509async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17510    let (buffer_id, mut cx) = setup_indent_guides_editor(
17511        &"
17512        fn main() {
17513            let a = 1;
17514
17515            let c = 3;
17516
17517            if a == 3 {
17518                let b = 2;
17519            } else {
17520                let c = 3;
17521            }
17522        }"
17523        .unindent(),
17524        cx,
17525    )
17526    .await;
17527
17528    assert_indent_guides(
17529        1..10,
17530        vec![
17531            indent_guide(buffer_id, 1, 9, 0),
17532            indent_guide(buffer_id, 6, 6, 1),
17533            indent_guide(buffer_id, 8, 8, 1),
17534        ],
17535        None,
17536        &mut cx,
17537    );
17538}
17539
17540#[gpui::test]
17541async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17542    let (buffer_id, mut cx) = setup_indent_guides_editor(
17543        &"
17544        fn main() {
17545            if a {
17546                b(
17547                    c,
17548                    d,
17549                )
17550            } else {
17551                e(
17552                    f
17553                )
17554            }
17555        }"
17556        .unindent(),
17557        cx,
17558    )
17559    .await;
17560
17561    assert_indent_guides(
17562        0..11,
17563        vec![
17564            indent_guide(buffer_id, 1, 10, 0),
17565            indent_guide(buffer_id, 2, 5, 1),
17566            indent_guide(buffer_id, 7, 9, 1),
17567            indent_guide(buffer_id, 3, 4, 2),
17568            indent_guide(buffer_id, 8, 8, 2),
17569        ],
17570        None,
17571        &mut cx,
17572    );
17573
17574    cx.update_editor(|editor, window, cx| {
17575        editor.fold_at(MultiBufferRow(2), window, cx);
17576        assert_eq!(
17577            editor.display_text(cx),
17578            "
17579            fn main() {
17580                if a {
17581                    b(⋯
17582                    )
17583                } else {
17584                    e(
17585                        f
17586                    )
17587                }
17588            }"
17589            .unindent()
17590        );
17591    });
17592
17593    assert_indent_guides(
17594        0..11,
17595        vec![
17596            indent_guide(buffer_id, 1, 10, 0),
17597            indent_guide(buffer_id, 2, 5, 1),
17598            indent_guide(buffer_id, 7, 9, 1),
17599            indent_guide(buffer_id, 8, 8, 2),
17600        ],
17601        None,
17602        &mut cx,
17603    );
17604}
17605
17606#[gpui::test]
17607async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17608    let (buffer_id, mut cx) = setup_indent_guides_editor(
17609        &"
17610        block1
17611            block2
17612                block3
17613                    block4
17614            block2
17615        block1
17616        block1"
17617            .unindent(),
17618        cx,
17619    )
17620    .await;
17621
17622    assert_indent_guides(
17623        1..10,
17624        vec![
17625            indent_guide(buffer_id, 1, 4, 0),
17626            indent_guide(buffer_id, 2, 3, 1),
17627            indent_guide(buffer_id, 3, 3, 2),
17628        ],
17629        None,
17630        &mut cx,
17631    );
17632}
17633
17634#[gpui::test]
17635async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17636    let (buffer_id, mut cx) = setup_indent_guides_editor(
17637        &"
17638        block1
17639            block2
17640                block3
17641
17642        block1
17643        block1"
17644            .unindent(),
17645        cx,
17646    )
17647    .await;
17648
17649    assert_indent_guides(
17650        0..6,
17651        vec![
17652            indent_guide(buffer_id, 1, 2, 0),
17653            indent_guide(buffer_id, 2, 2, 1),
17654        ],
17655        None,
17656        &mut cx,
17657    );
17658}
17659
17660#[gpui::test]
17661async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17662    let (buffer_id, mut cx) = setup_indent_guides_editor(
17663        &"
17664        function component() {
17665        \treturn (
17666        \t\t\t
17667        \t\t<div>
17668        \t\t\t<abc></abc>
17669        \t\t</div>
17670        \t)
17671        }"
17672        .unindent(),
17673        cx,
17674    )
17675    .await;
17676
17677    assert_indent_guides(
17678        0..8,
17679        vec![
17680            indent_guide(buffer_id, 1, 6, 0),
17681            indent_guide(buffer_id, 2, 5, 1),
17682            indent_guide(buffer_id, 4, 4, 2),
17683        ],
17684        None,
17685        &mut cx,
17686    );
17687}
17688
17689#[gpui::test]
17690async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17691    let (buffer_id, mut cx) = setup_indent_guides_editor(
17692        &"
17693        function component() {
17694        \treturn (
17695        \t
17696        \t\t<div>
17697        \t\t\t<abc></abc>
17698        \t\t</div>
17699        \t)
17700        }"
17701        .unindent(),
17702        cx,
17703    )
17704    .await;
17705
17706    assert_indent_guides(
17707        0..8,
17708        vec![
17709            indent_guide(buffer_id, 1, 6, 0),
17710            indent_guide(buffer_id, 2, 5, 1),
17711            indent_guide(buffer_id, 4, 4, 2),
17712        ],
17713        None,
17714        &mut cx,
17715    );
17716}
17717
17718#[gpui::test]
17719async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17720    let (buffer_id, mut cx) = setup_indent_guides_editor(
17721        &"
17722        block1
17723
17724
17725
17726            block2
17727        "
17728        .unindent(),
17729        cx,
17730    )
17731    .await;
17732
17733    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17734}
17735
17736#[gpui::test]
17737async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17738    let (buffer_id, mut cx) = setup_indent_guides_editor(
17739        &"
17740        def a:
17741        \tb = 3
17742        \tif True:
17743        \t\tc = 4
17744        \t\td = 5
17745        \tprint(b)
17746        "
17747        .unindent(),
17748        cx,
17749    )
17750    .await;
17751
17752    assert_indent_guides(
17753        0..6,
17754        vec![
17755            indent_guide(buffer_id, 1, 5, 0),
17756            indent_guide(buffer_id, 3, 4, 1),
17757        ],
17758        None,
17759        &mut cx,
17760    );
17761}
17762
17763#[gpui::test]
17764async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17765    let (buffer_id, mut cx) = setup_indent_guides_editor(
17766        &"
17767    fn main() {
17768        let a = 1;
17769    }"
17770        .unindent(),
17771        cx,
17772    )
17773    .await;
17774
17775    cx.update_editor(|editor, window, cx| {
17776        editor.change_selections(None, window, cx, |s| {
17777            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17778        });
17779    });
17780
17781    assert_indent_guides(
17782        0..3,
17783        vec![indent_guide(buffer_id, 1, 1, 0)],
17784        Some(vec![0]),
17785        &mut cx,
17786    );
17787}
17788
17789#[gpui::test]
17790async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17791    let (buffer_id, mut cx) = setup_indent_guides_editor(
17792        &"
17793    fn main() {
17794        if 1 == 2 {
17795            let a = 1;
17796        }
17797    }"
17798        .unindent(),
17799        cx,
17800    )
17801    .await;
17802
17803    cx.update_editor(|editor, window, cx| {
17804        editor.change_selections(None, window, cx, |s| {
17805            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17806        });
17807    });
17808
17809    assert_indent_guides(
17810        0..4,
17811        vec![
17812            indent_guide(buffer_id, 1, 3, 0),
17813            indent_guide(buffer_id, 2, 2, 1),
17814        ],
17815        Some(vec![1]),
17816        &mut cx,
17817    );
17818
17819    cx.update_editor(|editor, window, cx| {
17820        editor.change_selections(None, window, cx, |s| {
17821            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17822        });
17823    });
17824
17825    assert_indent_guides(
17826        0..4,
17827        vec![
17828            indent_guide(buffer_id, 1, 3, 0),
17829            indent_guide(buffer_id, 2, 2, 1),
17830        ],
17831        Some(vec![1]),
17832        &mut cx,
17833    );
17834
17835    cx.update_editor(|editor, window, cx| {
17836        editor.change_selections(None, window, cx, |s| {
17837            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17838        });
17839    });
17840
17841    assert_indent_guides(
17842        0..4,
17843        vec![
17844            indent_guide(buffer_id, 1, 3, 0),
17845            indent_guide(buffer_id, 2, 2, 1),
17846        ],
17847        Some(vec![0]),
17848        &mut cx,
17849    );
17850}
17851
17852#[gpui::test]
17853async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17854    let (buffer_id, mut cx) = setup_indent_guides_editor(
17855        &"
17856    fn main() {
17857        let a = 1;
17858
17859        let b = 2;
17860    }"
17861        .unindent(),
17862        cx,
17863    )
17864    .await;
17865
17866    cx.update_editor(|editor, window, cx| {
17867        editor.change_selections(None, window, cx, |s| {
17868            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17869        });
17870    });
17871
17872    assert_indent_guides(
17873        0..5,
17874        vec![indent_guide(buffer_id, 1, 3, 0)],
17875        Some(vec![0]),
17876        &mut cx,
17877    );
17878}
17879
17880#[gpui::test]
17881async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17882    let (buffer_id, mut cx) = setup_indent_guides_editor(
17883        &"
17884    def m:
17885        a = 1
17886        pass"
17887            .unindent(),
17888        cx,
17889    )
17890    .await;
17891
17892    cx.update_editor(|editor, window, cx| {
17893        editor.change_selections(None, window, cx, |s| {
17894            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17895        });
17896    });
17897
17898    assert_indent_guides(
17899        0..3,
17900        vec![indent_guide(buffer_id, 1, 2, 0)],
17901        Some(vec![0]),
17902        &mut cx,
17903    );
17904}
17905
17906#[gpui::test]
17907async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17908    init_test(cx, |_| {});
17909    let mut cx = EditorTestContext::new(cx).await;
17910    let text = indoc! {
17911        "
17912        impl A {
17913            fn b() {
17914                0;
17915                3;
17916                5;
17917                6;
17918                7;
17919            }
17920        }
17921        "
17922    };
17923    let base_text = indoc! {
17924        "
17925        impl A {
17926            fn b() {
17927                0;
17928                1;
17929                2;
17930                3;
17931                4;
17932            }
17933            fn c() {
17934                5;
17935                6;
17936                7;
17937            }
17938        }
17939        "
17940    };
17941
17942    cx.update_editor(|editor, window, cx| {
17943        editor.set_text(text, window, cx);
17944
17945        editor.buffer().update(cx, |multibuffer, cx| {
17946            let buffer = multibuffer.as_singleton().unwrap();
17947            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17948
17949            multibuffer.set_all_diff_hunks_expanded(cx);
17950            multibuffer.add_diff(diff, cx);
17951
17952            buffer.read(cx).remote_id()
17953        })
17954    });
17955    cx.run_until_parked();
17956
17957    cx.assert_state_with_diff(
17958        indoc! { "
17959          impl A {
17960              fn b() {
17961                  0;
17962        -         1;
17963        -         2;
17964                  3;
17965        -         4;
17966        -     }
17967        -     fn c() {
17968                  5;
17969                  6;
17970                  7;
17971              }
17972          }
17973          ˇ"
17974        }
17975        .to_string(),
17976    );
17977
17978    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17979        editor
17980            .snapshot(window, cx)
17981            .buffer_snapshot
17982            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17983            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17984            .collect::<Vec<_>>()
17985    });
17986    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17987    assert_eq!(
17988        actual_guides,
17989        vec![
17990            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17991            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17992            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17993        ]
17994    );
17995}
17996
17997#[gpui::test]
17998async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17999    init_test(cx, |_| {});
18000    let mut cx = EditorTestContext::new(cx).await;
18001
18002    let diff_base = r#"
18003        a
18004        b
18005        c
18006        "#
18007    .unindent();
18008
18009    cx.set_state(
18010        &r#"
18011        ˇA
18012        b
18013        C
18014        "#
18015        .unindent(),
18016    );
18017    cx.set_head_text(&diff_base);
18018    cx.update_editor(|editor, window, cx| {
18019        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18020    });
18021    executor.run_until_parked();
18022
18023    let both_hunks_expanded = r#"
18024        - a
18025        + ˇA
18026          b
18027        - c
18028        + C
18029        "#
18030    .unindent();
18031
18032    cx.assert_state_with_diff(both_hunks_expanded.clone());
18033
18034    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18035        let snapshot = editor.snapshot(window, cx);
18036        let hunks = editor
18037            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18038            .collect::<Vec<_>>();
18039        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18040        let buffer_id = hunks[0].buffer_id;
18041        hunks
18042            .into_iter()
18043            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18044            .collect::<Vec<_>>()
18045    });
18046    assert_eq!(hunk_ranges.len(), 2);
18047
18048    cx.update_editor(|editor, _, cx| {
18049        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18050    });
18051    executor.run_until_parked();
18052
18053    let second_hunk_expanded = r#"
18054          ˇA
18055          b
18056        - c
18057        + C
18058        "#
18059    .unindent();
18060
18061    cx.assert_state_with_diff(second_hunk_expanded);
18062
18063    cx.update_editor(|editor, _, cx| {
18064        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18065    });
18066    executor.run_until_parked();
18067
18068    cx.assert_state_with_diff(both_hunks_expanded.clone());
18069
18070    cx.update_editor(|editor, _, cx| {
18071        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18072    });
18073    executor.run_until_parked();
18074
18075    let first_hunk_expanded = r#"
18076        - a
18077        + ˇA
18078          b
18079          C
18080        "#
18081    .unindent();
18082
18083    cx.assert_state_with_diff(first_hunk_expanded);
18084
18085    cx.update_editor(|editor, _, cx| {
18086        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18087    });
18088    executor.run_until_parked();
18089
18090    cx.assert_state_with_diff(both_hunks_expanded);
18091
18092    cx.set_state(
18093        &r#"
18094        ˇA
18095        b
18096        "#
18097        .unindent(),
18098    );
18099    cx.run_until_parked();
18100
18101    // TODO this cursor position seems bad
18102    cx.assert_state_with_diff(
18103        r#"
18104        - ˇa
18105        + A
18106          b
18107        "#
18108        .unindent(),
18109    );
18110
18111    cx.update_editor(|editor, window, cx| {
18112        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18113    });
18114
18115    cx.assert_state_with_diff(
18116        r#"
18117            - ˇa
18118            + A
18119              b
18120            - c
18121            "#
18122        .unindent(),
18123    );
18124
18125    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18126        let snapshot = editor.snapshot(window, cx);
18127        let hunks = editor
18128            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18129            .collect::<Vec<_>>();
18130        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18131        let buffer_id = hunks[0].buffer_id;
18132        hunks
18133            .into_iter()
18134            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18135            .collect::<Vec<_>>()
18136    });
18137    assert_eq!(hunk_ranges.len(), 2);
18138
18139    cx.update_editor(|editor, _, cx| {
18140        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18141    });
18142    executor.run_until_parked();
18143
18144    cx.assert_state_with_diff(
18145        r#"
18146        - ˇa
18147        + A
18148          b
18149        "#
18150        .unindent(),
18151    );
18152}
18153
18154#[gpui::test]
18155async fn test_toggle_deletion_hunk_at_start_of_file(
18156    executor: BackgroundExecutor,
18157    cx: &mut TestAppContext,
18158) {
18159    init_test(cx, |_| {});
18160    let mut cx = EditorTestContext::new(cx).await;
18161
18162    let diff_base = r#"
18163        a
18164        b
18165        c
18166        "#
18167    .unindent();
18168
18169    cx.set_state(
18170        &r#"
18171        ˇb
18172        c
18173        "#
18174        .unindent(),
18175    );
18176    cx.set_head_text(&diff_base);
18177    cx.update_editor(|editor, window, cx| {
18178        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18179    });
18180    executor.run_until_parked();
18181
18182    let hunk_expanded = r#"
18183        - a
18184          ˇb
18185          c
18186        "#
18187    .unindent();
18188
18189    cx.assert_state_with_diff(hunk_expanded.clone());
18190
18191    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18192        let snapshot = editor.snapshot(window, cx);
18193        let hunks = editor
18194            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18195            .collect::<Vec<_>>();
18196        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18197        let buffer_id = hunks[0].buffer_id;
18198        hunks
18199            .into_iter()
18200            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18201            .collect::<Vec<_>>()
18202    });
18203    assert_eq!(hunk_ranges.len(), 1);
18204
18205    cx.update_editor(|editor, _, cx| {
18206        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18207    });
18208    executor.run_until_parked();
18209
18210    let hunk_collapsed = r#"
18211          ˇb
18212          c
18213        "#
18214    .unindent();
18215
18216    cx.assert_state_with_diff(hunk_collapsed);
18217
18218    cx.update_editor(|editor, _, cx| {
18219        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18220    });
18221    executor.run_until_parked();
18222
18223    cx.assert_state_with_diff(hunk_expanded.clone());
18224}
18225
18226#[gpui::test]
18227async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18228    init_test(cx, |_| {});
18229
18230    let fs = FakeFs::new(cx.executor());
18231    fs.insert_tree(
18232        path!("/test"),
18233        json!({
18234            ".git": {},
18235            "file-1": "ONE\n",
18236            "file-2": "TWO\n",
18237            "file-3": "THREE\n",
18238        }),
18239    )
18240    .await;
18241
18242    fs.set_head_for_repo(
18243        path!("/test/.git").as_ref(),
18244        &[
18245            ("file-1".into(), "one\n".into()),
18246            ("file-2".into(), "two\n".into()),
18247            ("file-3".into(), "three\n".into()),
18248        ],
18249        "deadbeef",
18250    );
18251
18252    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18253    let mut buffers = vec![];
18254    for i in 1..=3 {
18255        let buffer = project
18256            .update(cx, |project, cx| {
18257                let path = format!(path!("/test/file-{}"), i);
18258                project.open_local_buffer(path, cx)
18259            })
18260            .await
18261            .unwrap();
18262        buffers.push(buffer);
18263    }
18264
18265    let multibuffer = cx.new(|cx| {
18266        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18267        multibuffer.set_all_diff_hunks_expanded(cx);
18268        for buffer in &buffers {
18269            let snapshot = buffer.read(cx).snapshot();
18270            multibuffer.set_excerpts_for_path(
18271                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18272                buffer.clone(),
18273                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18274                DEFAULT_MULTIBUFFER_CONTEXT,
18275                cx,
18276            );
18277        }
18278        multibuffer
18279    });
18280
18281    let editor = cx.add_window(|window, cx| {
18282        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18283    });
18284    cx.run_until_parked();
18285
18286    let snapshot = editor
18287        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18288        .unwrap();
18289    let hunks = snapshot
18290        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18291        .map(|hunk| match hunk {
18292            DisplayDiffHunk::Unfolded {
18293                display_row_range, ..
18294            } => display_row_range,
18295            DisplayDiffHunk::Folded { .. } => unreachable!(),
18296        })
18297        .collect::<Vec<_>>();
18298    assert_eq!(
18299        hunks,
18300        [
18301            DisplayRow(2)..DisplayRow(4),
18302            DisplayRow(7)..DisplayRow(9),
18303            DisplayRow(12)..DisplayRow(14),
18304        ]
18305    );
18306}
18307
18308#[gpui::test]
18309async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18310    init_test(cx, |_| {});
18311
18312    let mut cx = EditorTestContext::new(cx).await;
18313    cx.set_head_text(indoc! { "
18314        one
18315        two
18316        three
18317        four
18318        five
18319        "
18320    });
18321    cx.set_index_text(indoc! { "
18322        one
18323        two
18324        three
18325        four
18326        five
18327        "
18328    });
18329    cx.set_state(indoc! {"
18330        one
18331        TWO
18332        ˇTHREE
18333        FOUR
18334        five
18335    "});
18336    cx.run_until_parked();
18337    cx.update_editor(|editor, window, cx| {
18338        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18339    });
18340    cx.run_until_parked();
18341    cx.assert_index_text(Some(indoc! {"
18342        one
18343        TWO
18344        THREE
18345        FOUR
18346        five
18347    "}));
18348    cx.set_state(indoc! { "
18349        one
18350        TWO
18351        ˇTHREE-HUNDRED
18352        FOUR
18353        five
18354    "});
18355    cx.run_until_parked();
18356    cx.update_editor(|editor, window, cx| {
18357        let snapshot = editor.snapshot(window, cx);
18358        let hunks = editor
18359            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18360            .collect::<Vec<_>>();
18361        assert_eq!(hunks.len(), 1);
18362        assert_eq!(
18363            hunks[0].status(),
18364            DiffHunkStatus {
18365                kind: DiffHunkStatusKind::Modified,
18366                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18367            }
18368        );
18369
18370        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18371    });
18372    cx.run_until_parked();
18373    cx.assert_index_text(Some(indoc! {"
18374        one
18375        TWO
18376        THREE-HUNDRED
18377        FOUR
18378        five
18379    "}));
18380}
18381
18382#[gpui::test]
18383fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18384    init_test(cx, |_| {});
18385
18386    let editor = cx.add_window(|window, cx| {
18387        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18388        build_editor(buffer, window, cx)
18389    });
18390
18391    let render_args = Arc::new(Mutex::new(None));
18392    let snapshot = editor
18393        .update(cx, |editor, window, cx| {
18394            let snapshot = editor.buffer().read(cx).snapshot(cx);
18395            let range =
18396                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18397
18398            struct RenderArgs {
18399                row: MultiBufferRow,
18400                folded: bool,
18401                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18402            }
18403
18404            let crease = Crease::inline(
18405                range,
18406                FoldPlaceholder::test(),
18407                {
18408                    let toggle_callback = render_args.clone();
18409                    move |row, folded, callback, _window, _cx| {
18410                        *toggle_callback.lock() = Some(RenderArgs {
18411                            row,
18412                            folded,
18413                            callback,
18414                        });
18415                        div()
18416                    }
18417                },
18418                |_row, _folded, _window, _cx| div(),
18419            );
18420
18421            editor.insert_creases(Some(crease), cx);
18422            let snapshot = editor.snapshot(window, cx);
18423            let _div = snapshot.render_crease_toggle(
18424                MultiBufferRow(1),
18425                false,
18426                cx.entity().clone(),
18427                window,
18428                cx,
18429            );
18430            snapshot
18431        })
18432        .unwrap();
18433
18434    let render_args = render_args.lock().take().unwrap();
18435    assert_eq!(render_args.row, MultiBufferRow(1));
18436    assert!(!render_args.folded);
18437    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18438
18439    cx.update_window(*editor, |_, window, cx| {
18440        (render_args.callback)(true, window, cx)
18441    })
18442    .unwrap();
18443    let snapshot = editor
18444        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18445        .unwrap();
18446    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18447
18448    cx.update_window(*editor, |_, window, cx| {
18449        (render_args.callback)(false, window, cx)
18450    })
18451    .unwrap();
18452    let snapshot = editor
18453        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18454        .unwrap();
18455    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18456}
18457
18458#[gpui::test]
18459async fn test_input_text(cx: &mut TestAppContext) {
18460    init_test(cx, |_| {});
18461    let mut cx = EditorTestContext::new(cx).await;
18462
18463    cx.set_state(
18464        &r#"ˇone
18465        two
18466
18467        three
18468        fourˇ
18469        five
18470
18471        siˇx"#
18472            .unindent(),
18473    );
18474
18475    cx.dispatch_action(HandleInput(String::new()));
18476    cx.assert_editor_state(
18477        &r#"ˇone
18478        two
18479
18480        three
18481        fourˇ
18482        five
18483
18484        siˇx"#
18485            .unindent(),
18486    );
18487
18488    cx.dispatch_action(HandleInput("AAAA".to_string()));
18489    cx.assert_editor_state(
18490        &r#"AAAAˇone
18491        two
18492
18493        three
18494        fourAAAAˇ
18495        five
18496
18497        siAAAAˇx"#
18498            .unindent(),
18499    );
18500}
18501
18502#[gpui::test]
18503async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18504    init_test(cx, |_| {});
18505
18506    let mut cx = EditorTestContext::new(cx).await;
18507    cx.set_state(
18508        r#"let foo = 1;
18509let foo = 2;
18510let foo = 3;
18511let fooˇ = 4;
18512let foo = 5;
18513let foo = 6;
18514let foo = 7;
18515let foo = 8;
18516let foo = 9;
18517let foo = 10;
18518let foo = 11;
18519let foo = 12;
18520let foo = 13;
18521let foo = 14;
18522let foo = 15;"#,
18523    );
18524
18525    cx.update_editor(|e, window, cx| {
18526        assert_eq!(
18527            e.next_scroll_position,
18528            NextScrollCursorCenterTopBottom::Center,
18529            "Default next scroll direction is center",
18530        );
18531
18532        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18533        assert_eq!(
18534            e.next_scroll_position,
18535            NextScrollCursorCenterTopBottom::Top,
18536            "After center, next scroll direction should be top",
18537        );
18538
18539        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18540        assert_eq!(
18541            e.next_scroll_position,
18542            NextScrollCursorCenterTopBottom::Bottom,
18543            "After top, next scroll direction should be bottom",
18544        );
18545
18546        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18547        assert_eq!(
18548            e.next_scroll_position,
18549            NextScrollCursorCenterTopBottom::Center,
18550            "After bottom, scrolling should start over",
18551        );
18552
18553        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18554        assert_eq!(
18555            e.next_scroll_position,
18556            NextScrollCursorCenterTopBottom::Top,
18557            "Scrolling continues if retriggered fast enough"
18558        );
18559    });
18560
18561    cx.executor()
18562        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18563    cx.executor().run_until_parked();
18564    cx.update_editor(|e, _, _| {
18565        assert_eq!(
18566            e.next_scroll_position,
18567            NextScrollCursorCenterTopBottom::Center,
18568            "If scrolling is not triggered fast enough, it should reset"
18569        );
18570    });
18571}
18572
18573#[gpui::test]
18574async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18575    init_test(cx, |_| {});
18576    let mut cx = EditorLspTestContext::new_rust(
18577        lsp::ServerCapabilities {
18578            definition_provider: Some(lsp::OneOf::Left(true)),
18579            references_provider: Some(lsp::OneOf::Left(true)),
18580            ..lsp::ServerCapabilities::default()
18581        },
18582        cx,
18583    )
18584    .await;
18585
18586    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18587        let go_to_definition = cx
18588            .lsp
18589            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18590                move |params, _| async move {
18591                    if empty_go_to_definition {
18592                        Ok(None)
18593                    } else {
18594                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18595                            uri: params.text_document_position_params.text_document.uri,
18596                            range: lsp::Range::new(
18597                                lsp::Position::new(4, 3),
18598                                lsp::Position::new(4, 6),
18599                            ),
18600                        })))
18601                    }
18602                },
18603            );
18604        let references = cx
18605            .lsp
18606            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18607                Ok(Some(vec![lsp::Location {
18608                    uri: params.text_document_position.text_document.uri,
18609                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18610                }]))
18611            });
18612        (go_to_definition, references)
18613    };
18614
18615    cx.set_state(
18616        &r#"fn one() {
18617            let mut a = ˇtwo();
18618        }
18619
18620        fn two() {}"#
18621            .unindent(),
18622    );
18623    set_up_lsp_handlers(false, &mut cx);
18624    let navigated = cx
18625        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18626        .await
18627        .expect("Failed to navigate to definition");
18628    assert_eq!(
18629        navigated,
18630        Navigated::Yes,
18631        "Should have navigated to definition from the GetDefinition response"
18632    );
18633    cx.assert_editor_state(
18634        &r#"fn one() {
18635            let mut a = two();
18636        }
18637
18638        fn «twoˇ»() {}"#
18639            .unindent(),
18640    );
18641
18642    let editors = cx.update_workspace(|workspace, _, cx| {
18643        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18644    });
18645    cx.update_editor(|_, _, test_editor_cx| {
18646        assert_eq!(
18647            editors.len(),
18648            1,
18649            "Initially, only one, test, editor should be open in the workspace"
18650        );
18651        assert_eq!(
18652            test_editor_cx.entity(),
18653            editors.last().expect("Asserted len is 1").clone()
18654        );
18655    });
18656
18657    set_up_lsp_handlers(true, &mut cx);
18658    let navigated = cx
18659        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18660        .await
18661        .expect("Failed to navigate to lookup references");
18662    assert_eq!(
18663        navigated,
18664        Navigated::Yes,
18665        "Should have navigated to references as a fallback after empty GoToDefinition response"
18666    );
18667    // We should not change the selections in the existing file,
18668    // if opening another milti buffer with the references
18669    cx.assert_editor_state(
18670        &r#"fn one() {
18671            let mut a = two();
18672        }
18673
18674        fn «twoˇ»() {}"#
18675            .unindent(),
18676    );
18677    let editors = cx.update_workspace(|workspace, _, cx| {
18678        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18679    });
18680    cx.update_editor(|_, _, test_editor_cx| {
18681        assert_eq!(
18682            editors.len(),
18683            2,
18684            "After falling back to references search, we open a new editor with the results"
18685        );
18686        let references_fallback_text = editors
18687            .into_iter()
18688            .find(|new_editor| *new_editor != test_editor_cx.entity())
18689            .expect("Should have one non-test editor now")
18690            .read(test_editor_cx)
18691            .text(test_editor_cx);
18692        assert_eq!(
18693            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18694            "Should use the range from the references response and not the GoToDefinition one"
18695        );
18696    });
18697}
18698
18699#[gpui::test]
18700async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18701    init_test(cx, |_| {});
18702    cx.update(|cx| {
18703        let mut editor_settings = EditorSettings::get_global(cx).clone();
18704        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18705        EditorSettings::override_global(editor_settings, cx);
18706    });
18707    let mut cx = EditorLspTestContext::new_rust(
18708        lsp::ServerCapabilities {
18709            definition_provider: Some(lsp::OneOf::Left(true)),
18710            references_provider: Some(lsp::OneOf::Left(true)),
18711            ..lsp::ServerCapabilities::default()
18712        },
18713        cx,
18714    )
18715    .await;
18716    let original_state = r#"fn one() {
18717        let mut a = ˇtwo();
18718    }
18719
18720    fn two() {}"#
18721        .unindent();
18722    cx.set_state(&original_state);
18723
18724    let mut go_to_definition = cx
18725        .lsp
18726        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18727            move |_, _| async move { Ok(None) },
18728        );
18729    let _references = cx
18730        .lsp
18731        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18732            panic!("Should not call for references with no go to definition fallback")
18733        });
18734
18735    let navigated = cx
18736        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18737        .await
18738        .expect("Failed to navigate to lookup references");
18739    go_to_definition
18740        .next()
18741        .await
18742        .expect("Should have called the go_to_definition handler");
18743
18744    assert_eq!(
18745        navigated,
18746        Navigated::No,
18747        "Should have navigated to references as a fallback after empty GoToDefinition response"
18748    );
18749    cx.assert_editor_state(&original_state);
18750    let editors = cx.update_workspace(|workspace, _, cx| {
18751        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18752    });
18753    cx.update_editor(|_, _, _| {
18754        assert_eq!(
18755            editors.len(),
18756            1,
18757            "After unsuccessful fallback, no other editor should have been opened"
18758        );
18759    });
18760}
18761
18762#[gpui::test]
18763async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18764    init_test(cx, |_| {});
18765
18766    let language = Arc::new(Language::new(
18767        LanguageConfig::default(),
18768        Some(tree_sitter_rust::LANGUAGE.into()),
18769    ));
18770
18771    let text = r#"
18772        #[cfg(test)]
18773        mod tests() {
18774            #[test]
18775            fn runnable_1() {
18776                let a = 1;
18777            }
18778
18779            #[test]
18780            fn runnable_2() {
18781                let a = 1;
18782                let b = 2;
18783            }
18784        }
18785    "#
18786    .unindent();
18787
18788    let fs = FakeFs::new(cx.executor());
18789    fs.insert_file("/file.rs", Default::default()).await;
18790
18791    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18792    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18794    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18795    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18796
18797    let editor = cx.new_window_entity(|window, cx| {
18798        Editor::new(
18799            EditorMode::full(),
18800            multi_buffer,
18801            Some(project.clone()),
18802            window,
18803            cx,
18804        )
18805    });
18806
18807    editor.update_in(cx, |editor, window, cx| {
18808        let snapshot = editor.buffer().read(cx).snapshot(cx);
18809        editor.tasks.insert(
18810            (buffer.read(cx).remote_id(), 3),
18811            RunnableTasks {
18812                templates: vec![],
18813                offset: snapshot.anchor_before(43),
18814                column: 0,
18815                extra_variables: HashMap::default(),
18816                context_range: BufferOffset(43)..BufferOffset(85),
18817            },
18818        );
18819        editor.tasks.insert(
18820            (buffer.read(cx).remote_id(), 8),
18821            RunnableTasks {
18822                templates: vec![],
18823                offset: snapshot.anchor_before(86),
18824                column: 0,
18825                extra_variables: HashMap::default(),
18826                context_range: BufferOffset(86)..BufferOffset(191),
18827            },
18828        );
18829
18830        // Test finding task when cursor is inside function body
18831        editor.change_selections(None, window, cx, |s| {
18832            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18833        });
18834        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18835        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18836
18837        // Test finding task when cursor is on function name
18838        editor.change_selections(None, window, cx, |s| {
18839            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18840        });
18841        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18842        assert_eq!(row, 8, "Should find task when cursor is on function name");
18843    });
18844}
18845
18846#[gpui::test]
18847async fn test_folding_buffers(cx: &mut TestAppContext) {
18848    init_test(cx, |_| {});
18849
18850    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18851    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18852    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18853
18854    let fs = FakeFs::new(cx.executor());
18855    fs.insert_tree(
18856        path!("/a"),
18857        json!({
18858            "first.rs": sample_text_1,
18859            "second.rs": sample_text_2,
18860            "third.rs": sample_text_3,
18861        }),
18862    )
18863    .await;
18864    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18865    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18866    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18867    let worktree = project.update(cx, |project, cx| {
18868        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18869        assert_eq!(worktrees.len(), 1);
18870        worktrees.pop().unwrap()
18871    });
18872    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18873
18874    let buffer_1 = project
18875        .update(cx, |project, cx| {
18876            project.open_buffer((worktree_id, "first.rs"), cx)
18877        })
18878        .await
18879        .unwrap();
18880    let buffer_2 = project
18881        .update(cx, |project, cx| {
18882            project.open_buffer((worktree_id, "second.rs"), cx)
18883        })
18884        .await
18885        .unwrap();
18886    let buffer_3 = project
18887        .update(cx, |project, cx| {
18888            project.open_buffer((worktree_id, "third.rs"), cx)
18889        })
18890        .await
18891        .unwrap();
18892
18893    let multi_buffer = cx.new(|cx| {
18894        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18895        multi_buffer.push_excerpts(
18896            buffer_1.clone(),
18897            [
18898                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18899                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18900                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18901            ],
18902            cx,
18903        );
18904        multi_buffer.push_excerpts(
18905            buffer_2.clone(),
18906            [
18907                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18908                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18909                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18910            ],
18911            cx,
18912        );
18913        multi_buffer.push_excerpts(
18914            buffer_3.clone(),
18915            [
18916                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18917                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18918                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18919            ],
18920            cx,
18921        );
18922        multi_buffer
18923    });
18924    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18925        Editor::new(
18926            EditorMode::full(),
18927            multi_buffer.clone(),
18928            Some(project.clone()),
18929            window,
18930            cx,
18931        )
18932    });
18933
18934    assert_eq!(
18935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18936        "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18937    );
18938
18939    multi_buffer_editor.update(cx, |editor, cx| {
18940        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18941    });
18942    assert_eq!(
18943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18944        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18945        "After folding the first buffer, its text should not be displayed"
18946    );
18947
18948    multi_buffer_editor.update(cx, |editor, cx| {
18949        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18950    });
18951    assert_eq!(
18952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18953        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18954        "After folding the second buffer, its text should not be displayed"
18955    );
18956
18957    multi_buffer_editor.update(cx, |editor, cx| {
18958        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18959    });
18960    assert_eq!(
18961        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18962        "\n\n\n\n\n",
18963        "After folding the third buffer, its text should not be displayed"
18964    );
18965
18966    // Emulate selection inside the fold logic, that should work
18967    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18968        editor
18969            .snapshot(window, cx)
18970            .next_line_boundary(Point::new(0, 4));
18971    });
18972
18973    multi_buffer_editor.update(cx, |editor, cx| {
18974        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18975    });
18976    assert_eq!(
18977        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18978        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18979        "After unfolding the second buffer, its text should be displayed"
18980    );
18981
18982    // Typing inside of buffer 1 causes that buffer to be unfolded.
18983    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18984        assert_eq!(
18985            multi_buffer
18986                .read(cx)
18987                .snapshot(cx)
18988                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18989                .collect::<String>(),
18990            "bbbb"
18991        );
18992        editor.change_selections(None, window, cx, |selections| {
18993            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18994        });
18995        editor.handle_input("B", window, cx);
18996    });
18997
18998    assert_eq!(
18999        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19000        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19001        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19002    );
19003
19004    multi_buffer_editor.update(cx, |editor, cx| {
19005        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19006    });
19007    assert_eq!(
19008        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19009        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19010        "After unfolding the all buffers, all original text should be displayed"
19011    );
19012}
19013
19014#[gpui::test]
19015async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19016    init_test(cx, |_| {});
19017
19018    let sample_text_1 = "1111\n2222\n3333".to_string();
19019    let sample_text_2 = "4444\n5555\n6666".to_string();
19020    let sample_text_3 = "7777\n8888\n9999".to_string();
19021
19022    let fs = FakeFs::new(cx.executor());
19023    fs.insert_tree(
19024        path!("/a"),
19025        json!({
19026            "first.rs": sample_text_1,
19027            "second.rs": sample_text_2,
19028            "third.rs": sample_text_3,
19029        }),
19030    )
19031    .await;
19032    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19033    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19034    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19035    let worktree = project.update(cx, |project, cx| {
19036        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19037        assert_eq!(worktrees.len(), 1);
19038        worktrees.pop().unwrap()
19039    });
19040    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19041
19042    let buffer_1 = project
19043        .update(cx, |project, cx| {
19044            project.open_buffer((worktree_id, "first.rs"), cx)
19045        })
19046        .await
19047        .unwrap();
19048    let buffer_2 = project
19049        .update(cx, |project, cx| {
19050            project.open_buffer((worktree_id, "second.rs"), cx)
19051        })
19052        .await
19053        .unwrap();
19054    let buffer_3 = project
19055        .update(cx, |project, cx| {
19056            project.open_buffer((worktree_id, "third.rs"), cx)
19057        })
19058        .await
19059        .unwrap();
19060
19061    let multi_buffer = cx.new(|cx| {
19062        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19063        multi_buffer.push_excerpts(
19064            buffer_1.clone(),
19065            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19066            cx,
19067        );
19068        multi_buffer.push_excerpts(
19069            buffer_2.clone(),
19070            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19071            cx,
19072        );
19073        multi_buffer.push_excerpts(
19074            buffer_3.clone(),
19075            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19076            cx,
19077        );
19078        multi_buffer
19079    });
19080
19081    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19082        Editor::new(
19083            EditorMode::full(),
19084            multi_buffer,
19085            Some(project.clone()),
19086            window,
19087            cx,
19088        )
19089    });
19090
19091    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19092    assert_eq!(
19093        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19094        full_text,
19095    );
19096
19097    multi_buffer_editor.update(cx, |editor, cx| {
19098        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19099    });
19100    assert_eq!(
19101        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19102        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19103        "After folding the first buffer, its text should not be displayed"
19104    );
19105
19106    multi_buffer_editor.update(cx, |editor, cx| {
19107        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19108    });
19109
19110    assert_eq!(
19111        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19112        "\n\n\n\n\n\n7777\n8888\n9999",
19113        "After folding the second buffer, its text should not be displayed"
19114    );
19115
19116    multi_buffer_editor.update(cx, |editor, cx| {
19117        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19118    });
19119    assert_eq!(
19120        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19121        "\n\n\n\n\n",
19122        "After folding the third buffer, its text should not be displayed"
19123    );
19124
19125    multi_buffer_editor.update(cx, |editor, cx| {
19126        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19127    });
19128    assert_eq!(
19129        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19130        "\n\n\n\n4444\n5555\n6666\n\n",
19131        "After unfolding the second buffer, its text should be displayed"
19132    );
19133
19134    multi_buffer_editor.update(cx, |editor, cx| {
19135        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19136    });
19137    assert_eq!(
19138        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19139        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19140        "After unfolding the first buffer, its text should be displayed"
19141    );
19142
19143    multi_buffer_editor.update(cx, |editor, cx| {
19144        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19145    });
19146    assert_eq!(
19147        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19148        full_text,
19149        "After unfolding all buffers, all original text should be displayed"
19150    );
19151}
19152
19153#[gpui::test]
19154async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19155    init_test(cx, |_| {});
19156
19157    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19158
19159    let fs = FakeFs::new(cx.executor());
19160    fs.insert_tree(
19161        path!("/a"),
19162        json!({
19163            "main.rs": sample_text,
19164        }),
19165    )
19166    .await;
19167    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19168    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19169    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19170    let worktree = project.update(cx, |project, cx| {
19171        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19172        assert_eq!(worktrees.len(), 1);
19173        worktrees.pop().unwrap()
19174    });
19175    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19176
19177    let buffer_1 = project
19178        .update(cx, |project, cx| {
19179            project.open_buffer((worktree_id, "main.rs"), cx)
19180        })
19181        .await
19182        .unwrap();
19183
19184    let multi_buffer = cx.new(|cx| {
19185        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19186        multi_buffer.push_excerpts(
19187            buffer_1.clone(),
19188            [ExcerptRange::new(
19189                Point::new(0, 0)
19190                    ..Point::new(
19191                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19192                        0,
19193                    ),
19194            )],
19195            cx,
19196        );
19197        multi_buffer
19198    });
19199    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19200        Editor::new(
19201            EditorMode::full(),
19202            multi_buffer,
19203            Some(project.clone()),
19204            window,
19205            cx,
19206        )
19207    });
19208
19209    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19210    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19211        enum TestHighlight {}
19212        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19213        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19214        editor.highlight_text::<TestHighlight>(
19215            vec![highlight_range.clone()],
19216            HighlightStyle::color(Hsla::green()),
19217            cx,
19218        );
19219        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19220    });
19221
19222    let full_text = format!("\n\n{sample_text}");
19223    assert_eq!(
19224        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19225        full_text,
19226    );
19227}
19228
19229#[gpui::test]
19230async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19231    init_test(cx, |_| {});
19232    cx.update(|cx| {
19233        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19234            "keymaps/default-linux.json",
19235            cx,
19236        )
19237        .unwrap();
19238        cx.bind_keys(default_key_bindings);
19239    });
19240
19241    let (editor, cx) = cx.add_window_view(|window, cx| {
19242        let multi_buffer = MultiBuffer::build_multi(
19243            [
19244                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19245                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19246                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19247                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19248            ],
19249            cx,
19250        );
19251        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19252
19253        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19254        // fold all but the second buffer, so that we test navigating between two
19255        // adjacent folded buffers, as well as folded buffers at the start and
19256        // end the multibuffer
19257        editor.fold_buffer(buffer_ids[0], cx);
19258        editor.fold_buffer(buffer_ids[2], cx);
19259        editor.fold_buffer(buffer_ids[3], cx);
19260
19261        editor
19262    });
19263    cx.simulate_resize(size(px(1000.), px(1000.)));
19264
19265    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19266    cx.assert_excerpts_with_selections(indoc! {"
19267        [EXCERPT]
19268        ˇ[FOLDED]
19269        [EXCERPT]
19270        a1
19271        b1
19272        [EXCERPT]
19273        [FOLDED]
19274        [EXCERPT]
19275        [FOLDED]
19276        "
19277    });
19278    cx.simulate_keystroke("down");
19279    cx.assert_excerpts_with_selections(indoc! {"
19280        [EXCERPT]
19281        [FOLDED]
19282        [EXCERPT]
19283        ˇa1
19284        b1
19285        [EXCERPT]
19286        [FOLDED]
19287        [EXCERPT]
19288        [FOLDED]
19289        "
19290    });
19291    cx.simulate_keystroke("down");
19292    cx.assert_excerpts_with_selections(indoc! {"
19293        [EXCERPT]
19294        [FOLDED]
19295        [EXCERPT]
19296        a1
19297        ˇb1
19298        [EXCERPT]
19299        [FOLDED]
19300        [EXCERPT]
19301        [FOLDED]
19302        "
19303    });
19304    cx.simulate_keystroke("down");
19305    cx.assert_excerpts_with_selections(indoc! {"
19306        [EXCERPT]
19307        [FOLDED]
19308        [EXCERPT]
19309        a1
19310        b1
19311        ˇ[EXCERPT]
19312        [FOLDED]
19313        [EXCERPT]
19314        [FOLDED]
19315        "
19316    });
19317    cx.simulate_keystroke("down");
19318    cx.assert_excerpts_with_selections(indoc! {"
19319        [EXCERPT]
19320        [FOLDED]
19321        [EXCERPT]
19322        a1
19323        b1
19324        [EXCERPT]
19325        ˇ[FOLDED]
19326        [EXCERPT]
19327        [FOLDED]
19328        "
19329    });
19330    for _ in 0..5 {
19331        cx.simulate_keystroke("down");
19332        cx.assert_excerpts_with_selections(indoc! {"
19333            [EXCERPT]
19334            [FOLDED]
19335            [EXCERPT]
19336            a1
19337            b1
19338            [EXCERPT]
19339            [FOLDED]
19340            [EXCERPT]
19341            ˇ[FOLDED]
19342            "
19343        });
19344    }
19345
19346    cx.simulate_keystroke("up");
19347    cx.assert_excerpts_with_selections(indoc! {"
19348        [EXCERPT]
19349        [FOLDED]
19350        [EXCERPT]
19351        a1
19352        b1
19353        [EXCERPT]
19354        ˇ[FOLDED]
19355        [EXCERPT]
19356        [FOLDED]
19357        "
19358    });
19359    cx.simulate_keystroke("up");
19360    cx.assert_excerpts_with_selections(indoc! {"
19361        [EXCERPT]
19362        [FOLDED]
19363        [EXCERPT]
19364        a1
19365        b1
19366        ˇ[EXCERPT]
19367        [FOLDED]
19368        [EXCERPT]
19369        [FOLDED]
19370        "
19371    });
19372    cx.simulate_keystroke("up");
19373    cx.assert_excerpts_with_selections(indoc! {"
19374        [EXCERPT]
19375        [FOLDED]
19376        [EXCERPT]
19377        a1
19378        ˇb1
19379        [EXCERPT]
19380        [FOLDED]
19381        [EXCERPT]
19382        [FOLDED]
19383        "
19384    });
19385    cx.simulate_keystroke("up");
19386    cx.assert_excerpts_with_selections(indoc! {"
19387        [EXCERPT]
19388        [FOLDED]
19389        [EXCERPT]
19390        ˇa1
19391        b1
19392        [EXCERPT]
19393        [FOLDED]
19394        [EXCERPT]
19395        [FOLDED]
19396        "
19397    });
19398    for _ in 0..5 {
19399        cx.simulate_keystroke("up");
19400        cx.assert_excerpts_with_selections(indoc! {"
19401            [EXCERPT]
19402            ˇ[FOLDED]
19403            [EXCERPT]
19404            a1
19405            b1
19406            [EXCERPT]
19407            [FOLDED]
19408            [EXCERPT]
19409            [FOLDED]
19410            "
19411        });
19412    }
19413}
19414
19415#[gpui::test]
19416async fn test_inline_completion_text(cx: &mut TestAppContext) {
19417    init_test(cx, |_| {});
19418
19419    // Simple insertion
19420    assert_highlighted_edits(
19421        "Hello, world!",
19422        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19423        true,
19424        cx,
19425        |highlighted_edits, cx| {
19426            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19427            assert_eq!(highlighted_edits.highlights.len(), 1);
19428            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19429            assert_eq!(
19430                highlighted_edits.highlights[0].1.background_color,
19431                Some(cx.theme().status().created_background)
19432            );
19433        },
19434    )
19435    .await;
19436
19437    // Replacement
19438    assert_highlighted_edits(
19439        "This is a test.",
19440        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19441        false,
19442        cx,
19443        |highlighted_edits, cx| {
19444            assert_eq!(highlighted_edits.text, "That is a test.");
19445            assert_eq!(highlighted_edits.highlights.len(), 1);
19446            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19447            assert_eq!(
19448                highlighted_edits.highlights[0].1.background_color,
19449                Some(cx.theme().status().created_background)
19450            );
19451        },
19452    )
19453    .await;
19454
19455    // Multiple edits
19456    assert_highlighted_edits(
19457        "Hello, world!",
19458        vec![
19459            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19460            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19461        ],
19462        false,
19463        cx,
19464        |highlighted_edits, cx| {
19465            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19466            assert_eq!(highlighted_edits.highlights.len(), 2);
19467            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19468            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19469            assert_eq!(
19470                highlighted_edits.highlights[0].1.background_color,
19471                Some(cx.theme().status().created_background)
19472            );
19473            assert_eq!(
19474                highlighted_edits.highlights[1].1.background_color,
19475                Some(cx.theme().status().created_background)
19476            );
19477        },
19478    )
19479    .await;
19480
19481    // Multiple lines with edits
19482    assert_highlighted_edits(
19483        "First line\nSecond line\nThird line\nFourth line",
19484        vec![
19485            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19486            (
19487                Point::new(2, 0)..Point::new(2, 10),
19488                "New third line".to_string(),
19489            ),
19490            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19491        ],
19492        false,
19493        cx,
19494        |highlighted_edits, cx| {
19495            assert_eq!(
19496                highlighted_edits.text,
19497                "Second modified\nNew third line\nFourth updated line"
19498            );
19499            assert_eq!(highlighted_edits.highlights.len(), 3);
19500            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19501            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19502            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19503            for highlight in &highlighted_edits.highlights {
19504                assert_eq!(
19505                    highlight.1.background_color,
19506                    Some(cx.theme().status().created_background)
19507                );
19508            }
19509        },
19510    )
19511    .await;
19512}
19513
19514#[gpui::test]
19515async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19516    init_test(cx, |_| {});
19517
19518    // Deletion
19519    assert_highlighted_edits(
19520        "Hello, world!",
19521        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19522        true,
19523        cx,
19524        |highlighted_edits, cx| {
19525            assert_eq!(highlighted_edits.text, "Hello, world!");
19526            assert_eq!(highlighted_edits.highlights.len(), 1);
19527            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19528            assert_eq!(
19529                highlighted_edits.highlights[0].1.background_color,
19530                Some(cx.theme().status().deleted_background)
19531            );
19532        },
19533    )
19534    .await;
19535
19536    // Insertion
19537    assert_highlighted_edits(
19538        "Hello, world!",
19539        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19540        true,
19541        cx,
19542        |highlighted_edits, cx| {
19543            assert_eq!(highlighted_edits.highlights.len(), 1);
19544            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19545            assert_eq!(
19546                highlighted_edits.highlights[0].1.background_color,
19547                Some(cx.theme().status().created_background)
19548            );
19549        },
19550    )
19551    .await;
19552}
19553
19554async fn assert_highlighted_edits(
19555    text: &str,
19556    edits: Vec<(Range<Point>, String)>,
19557    include_deletions: bool,
19558    cx: &mut TestAppContext,
19559    assertion_fn: impl Fn(HighlightedText, &App),
19560) {
19561    let window = cx.add_window(|window, cx| {
19562        let buffer = MultiBuffer::build_simple(text, cx);
19563        Editor::new(EditorMode::full(), buffer, None, window, cx)
19564    });
19565    let cx = &mut VisualTestContext::from_window(*window, cx);
19566
19567    let (buffer, snapshot) = window
19568        .update(cx, |editor, _window, cx| {
19569            (
19570                editor.buffer().clone(),
19571                editor.buffer().read(cx).snapshot(cx),
19572            )
19573        })
19574        .unwrap();
19575
19576    let edits = edits
19577        .into_iter()
19578        .map(|(range, edit)| {
19579            (
19580                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19581                edit,
19582            )
19583        })
19584        .collect::<Vec<_>>();
19585
19586    let text_anchor_edits = edits
19587        .clone()
19588        .into_iter()
19589        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19590        .collect::<Vec<_>>();
19591
19592    let edit_preview = window
19593        .update(cx, |_, _window, cx| {
19594            buffer
19595                .read(cx)
19596                .as_singleton()
19597                .unwrap()
19598                .read(cx)
19599                .preview_edits(text_anchor_edits.into(), cx)
19600        })
19601        .unwrap()
19602        .await;
19603
19604    cx.update(|_window, cx| {
19605        let highlighted_edits = inline_completion_edit_text(
19606            &snapshot.as_singleton().unwrap().2,
19607            &edits,
19608            &edit_preview,
19609            include_deletions,
19610            cx,
19611        );
19612        assertion_fn(highlighted_edits, cx)
19613    });
19614}
19615
19616#[track_caller]
19617fn assert_breakpoint(
19618    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19619    path: &Arc<Path>,
19620    expected: Vec<(u32, Breakpoint)>,
19621) {
19622    if expected.len() == 0usize {
19623        assert!(!breakpoints.contains_key(path), "{}", path.display());
19624    } else {
19625        let mut breakpoint = breakpoints
19626            .get(path)
19627            .unwrap()
19628            .into_iter()
19629            .map(|breakpoint| {
19630                (
19631                    breakpoint.row,
19632                    Breakpoint {
19633                        message: breakpoint.message.clone(),
19634                        state: breakpoint.state,
19635                        condition: breakpoint.condition.clone(),
19636                        hit_condition: breakpoint.hit_condition.clone(),
19637                    },
19638                )
19639            })
19640            .collect::<Vec<_>>();
19641
19642        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19643
19644        assert_eq!(expected, breakpoint);
19645    }
19646}
19647
19648fn add_log_breakpoint_at_cursor(
19649    editor: &mut Editor,
19650    log_message: &str,
19651    window: &mut Window,
19652    cx: &mut Context<Editor>,
19653) {
19654    let (anchor, bp) = editor
19655        .breakpoints_at_cursors(window, cx)
19656        .first()
19657        .and_then(|(anchor, bp)| {
19658            if let Some(bp) = bp {
19659                Some((*anchor, bp.clone()))
19660            } else {
19661                None
19662            }
19663        })
19664        .unwrap_or_else(|| {
19665            let cursor_position: Point = editor.selections.newest(cx).head();
19666
19667            let breakpoint_position = editor
19668                .snapshot(window, cx)
19669                .display_snapshot
19670                .buffer_snapshot
19671                .anchor_before(Point::new(cursor_position.row, 0));
19672
19673            (breakpoint_position, Breakpoint::new_log(&log_message))
19674        });
19675
19676    editor.edit_breakpoint_at_anchor(
19677        anchor,
19678        bp,
19679        BreakpointEditAction::EditLogMessage(log_message.into()),
19680        cx,
19681    );
19682}
19683
19684#[gpui::test]
19685async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19686    init_test(cx, |_| {});
19687
19688    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19689    let fs = FakeFs::new(cx.executor());
19690    fs.insert_tree(
19691        path!("/a"),
19692        json!({
19693            "main.rs": sample_text,
19694        }),
19695    )
19696    .await;
19697    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19698    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19699    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19700
19701    let fs = FakeFs::new(cx.executor());
19702    fs.insert_tree(
19703        path!("/a"),
19704        json!({
19705            "main.rs": sample_text,
19706        }),
19707    )
19708    .await;
19709    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19710    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19711    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19712    let worktree_id = workspace
19713        .update(cx, |workspace, _window, cx| {
19714            workspace.project().update(cx, |project, cx| {
19715                project.worktrees(cx).next().unwrap().read(cx).id()
19716            })
19717        })
19718        .unwrap();
19719
19720    let buffer = project
19721        .update(cx, |project, cx| {
19722            project.open_buffer((worktree_id, "main.rs"), cx)
19723        })
19724        .await
19725        .unwrap();
19726
19727    let (editor, cx) = cx.add_window_view(|window, cx| {
19728        Editor::new(
19729            EditorMode::full(),
19730            MultiBuffer::build_from_buffer(buffer, cx),
19731            Some(project.clone()),
19732            window,
19733            cx,
19734        )
19735    });
19736
19737    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19738    let abs_path = project.read_with(cx, |project, cx| {
19739        project
19740            .absolute_path(&project_path, cx)
19741            .map(|path_buf| Arc::from(path_buf.to_owned()))
19742            .unwrap()
19743    });
19744
19745    // assert we can add breakpoint on the first line
19746    editor.update_in(cx, |editor, window, cx| {
19747        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19748        editor.move_to_end(&MoveToEnd, window, cx);
19749        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19750    });
19751
19752    let breakpoints = editor.update(cx, |editor, cx| {
19753        editor
19754            .breakpoint_store()
19755            .as_ref()
19756            .unwrap()
19757            .read(cx)
19758            .all_source_breakpoints(cx)
19759            .clone()
19760    });
19761
19762    assert_eq!(1, breakpoints.len());
19763    assert_breakpoint(
19764        &breakpoints,
19765        &abs_path,
19766        vec![
19767            (0, Breakpoint::new_standard()),
19768            (3, Breakpoint::new_standard()),
19769        ],
19770    );
19771
19772    editor.update_in(cx, |editor, window, cx| {
19773        editor.move_to_beginning(&MoveToBeginning, window, cx);
19774        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19775    });
19776
19777    let breakpoints = editor.update(cx, |editor, cx| {
19778        editor
19779            .breakpoint_store()
19780            .as_ref()
19781            .unwrap()
19782            .read(cx)
19783            .all_source_breakpoints(cx)
19784            .clone()
19785    });
19786
19787    assert_eq!(1, breakpoints.len());
19788    assert_breakpoint(
19789        &breakpoints,
19790        &abs_path,
19791        vec![(3, Breakpoint::new_standard())],
19792    );
19793
19794    editor.update_in(cx, |editor, window, cx| {
19795        editor.move_to_end(&MoveToEnd, window, cx);
19796        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19797    });
19798
19799    let breakpoints = editor.update(cx, |editor, cx| {
19800        editor
19801            .breakpoint_store()
19802            .as_ref()
19803            .unwrap()
19804            .read(cx)
19805            .all_source_breakpoints(cx)
19806            .clone()
19807    });
19808
19809    assert_eq!(0, breakpoints.len());
19810    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19811}
19812
19813#[gpui::test]
19814async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19815    init_test(cx, |_| {});
19816
19817    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19818
19819    let fs = FakeFs::new(cx.executor());
19820    fs.insert_tree(
19821        path!("/a"),
19822        json!({
19823            "main.rs": sample_text,
19824        }),
19825    )
19826    .await;
19827    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19828    let (workspace, cx) =
19829        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19830
19831    let worktree_id = workspace.update(cx, |workspace, cx| {
19832        workspace.project().update(cx, |project, cx| {
19833            project.worktrees(cx).next().unwrap().read(cx).id()
19834        })
19835    });
19836
19837    let buffer = project
19838        .update(cx, |project, cx| {
19839            project.open_buffer((worktree_id, "main.rs"), cx)
19840        })
19841        .await
19842        .unwrap();
19843
19844    let (editor, cx) = cx.add_window_view(|window, cx| {
19845        Editor::new(
19846            EditorMode::full(),
19847            MultiBuffer::build_from_buffer(buffer, cx),
19848            Some(project.clone()),
19849            window,
19850            cx,
19851        )
19852    });
19853
19854    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19855    let abs_path = project.read_with(cx, |project, cx| {
19856        project
19857            .absolute_path(&project_path, cx)
19858            .map(|path_buf| Arc::from(path_buf.to_owned()))
19859            .unwrap()
19860    });
19861
19862    editor.update_in(cx, |editor, window, cx| {
19863        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19864    });
19865
19866    let breakpoints = editor.update(cx, |editor, cx| {
19867        editor
19868            .breakpoint_store()
19869            .as_ref()
19870            .unwrap()
19871            .read(cx)
19872            .all_source_breakpoints(cx)
19873            .clone()
19874    });
19875
19876    assert_breakpoint(
19877        &breakpoints,
19878        &abs_path,
19879        vec![(0, Breakpoint::new_log("hello world"))],
19880    );
19881
19882    // Removing a log message from a log breakpoint should remove it
19883    editor.update_in(cx, |editor, window, cx| {
19884        add_log_breakpoint_at_cursor(editor, "", window, cx);
19885    });
19886
19887    let breakpoints = editor.update(cx, |editor, cx| {
19888        editor
19889            .breakpoint_store()
19890            .as_ref()
19891            .unwrap()
19892            .read(cx)
19893            .all_source_breakpoints(cx)
19894            .clone()
19895    });
19896
19897    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19898
19899    editor.update_in(cx, |editor, window, cx| {
19900        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19901        editor.move_to_end(&MoveToEnd, window, cx);
19902        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19903        // Not adding a log message to a standard breakpoint shouldn't remove it
19904        add_log_breakpoint_at_cursor(editor, "", window, cx);
19905    });
19906
19907    let breakpoints = editor.update(cx, |editor, cx| {
19908        editor
19909            .breakpoint_store()
19910            .as_ref()
19911            .unwrap()
19912            .read(cx)
19913            .all_source_breakpoints(cx)
19914            .clone()
19915    });
19916
19917    assert_breakpoint(
19918        &breakpoints,
19919        &abs_path,
19920        vec![
19921            (0, Breakpoint::new_standard()),
19922            (3, Breakpoint::new_standard()),
19923        ],
19924    );
19925
19926    editor.update_in(cx, |editor, window, cx| {
19927        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19928    });
19929
19930    let breakpoints = editor.update(cx, |editor, cx| {
19931        editor
19932            .breakpoint_store()
19933            .as_ref()
19934            .unwrap()
19935            .read(cx)
19936            .all_source_breakpoints(cx)
19937            .clone()
19938    });
19939
19940    assert_breakpoint(
19941        &breakpoints,
19942        &abs_path,
19943        vec![
19944            (0, Breakpoint::new_standard()),
19945            (3, Breakpoint::new_log("hello world")),
19946        ],
19947    );
19948
19949    editor.update_in(cx, |editor, window, cx| {
19950        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19951    });
19952
19953    let breakpoints = editor.update(cx, |editor, cx| {
19954        editor
19955            .breakpoint_store()
19956            .as_ref()
19957            .unwrap()
19958            .read(cx)
19959            .all_source_breakpoints(cx)
19960            .clone()
19961    });
19962
19963    assert_breakpoint(
19964        &breakpoints,
19965        &abs_path,
19966        vec![
19967            (0, Breakpoint::new_standard()),
19968            (3, Breakpoint::new_log("hello Earth!!")),
19969        ],
19970    );
19971}
19972
19973/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19974/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19975/// or when breakpoints were placed out of order. This tests for a regression too
19976#[gpui::test]
19977async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19978    init_test(cx, |_| {});
19979
19980    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19981    let fs = FakeFs::new(cx.executor());
19982    fs.insert_tree(
19983        path!("/a"),
19984        json!({
19985            "main.rs": sample_text,
19986        }),
19987    )
19988    .await;
19989    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19990    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19991    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19992
19993    let fs = FakeFs::new(cx.executor());
19994    fs.insert_tree(
19995        path!("/a"),
19996        json!({
19997            "main.rs": sample_text,
19998        }),
19999    )
20000    .await;
20001    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20002    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20003    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20004    let worktree_id = workspace
20005        .update(cx, |workspace, _window, cx| {
20006            workspace.project().update(cx, |project, cx| {
20007                project.worktrees(cx).next().unwrap().read(cx).id()
20008            })
20009        })
20010        .unwrap();
20011
20012    let buffer = project
20013        .update(cx, |project, cx| {
20014            project.open_buffer((worktree_id, "main.rs"), cx)
20015        })
20016        .await
20017        .unwrap();
20018
20019    let (editor, cx) = cx.add_window_view(|window, cx| {
20020        Editor::new(
20021            EditorMode::full(),
20022            MultiBuffer::build_from_buffer(buffer, cx),
20023            Some(project.clone()),
20024            window,
20025            cx,
20026        )
20027    });
20028
20029    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20030    let abs_path = project.read_with(cx, |project, cx| {
20031        project
20032            .absolute_path(&project_path, cx)
20033            .map(|path_buf| Arc::from(path_buf.to_owned()))
20034            .unwrap()
20035    });
20036
20037    // assert we can add breakpoint on the first line
20038    editor.update_in(cx, |editor, window, cx| {
20039        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20040        editor.move_to_end(&MoveToEnd, window, cx);
20041        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20042        editor.move_up(&MoveUp, window, cx);
20043        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20044    });
20045
20046    let breakpoints = editor.update(cx, |editor, cx| {
20047        editor
20048            .breakpoint_store()
20049            .as_ref()
20050            .unwrap()
20051            .read(cx)
20052            .all_source_breakpoints(cx)
20053            .clone()
20054    });
20055
20056    assert_eq!(1, breakpoints.len());
20057    assert_breakpoint(
20058        &breakpoints,
20059        &abs_path,
20060        vec![
20061            (0, Breakpoint::new_standard()),
20062            (2, Breakpoint::new_standard()),
20063            (3, Breakpoint::new_standard()),
20064        ],
20065    );
20066
20067    editor.update_in(cx, |editor, window, cx| {
20068        editor.move_to_beginning(&MoveToBeginning, window, cx);
20069        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20070        editor.move_to_end(&MoveToEnd, window, cx);
20071        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20072        // Disabling a breakpoint that doesn't exist should do nothing
20073        editor.move_up(&MoveUp, window, cx);
20074        editor.move_up(&MoveUp, window, cx);
20075        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20076    });
20077
20078    let breakpoints = editor.update(cx, |editor, cx| {
20079        editor
20080            .breakpoint_store()
20081            .as_ref()
20082            .unwrap()
20083            .read(cx)
20084            .all_source_breakpoints(cx)
20085            .clone()
20086    });
20087
20088    let disable_breakpoint = {
20089        let mut bp = Breakpoint::new_standard();
20090        bp.state = BreakpointState::Disabled;
20091        bp
20092    };
20093
20094    assert_eq!(1, breakpoints.len());
20095    assert_breakpoint(
20096        &breakpoints,
20097        &abs_path,
20098        vec![
20099            (0, disable_breakpoint.clone()),
20100            (2, Breakpoint::new_standard()),
20101            (3, disable_breakpoint.clone()),
20102        ],
20103    );
20104
20105    editor.update_in(cx, |editor, window, cx| {
20106        editor.move_to_beginning(&MoveToBeginning, window, cx);
20107        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20108        editor.move_to_end(&MoveToEnd, window, cx);
20109        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20110        editor.move_up(&MoveUp, window, cx);
20111        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20112    });
20113
20114    let breakpoints = editor.update(cx, |editor, cx| {
20115        editor
20116            .breakpoint_store()
20117            .as_ref()
20118            .unwrap()
20119            .read(cx)
20120            .all_source_breakpoints(cx)
20121            .clone()
20122    });
20123
20124    assert_eq!(1, breakpoints.len());
20125    assert_breakpoint(
20126        &breakpoints,
20127        &abs_path,
20128        vec![
20129            (0, Breakpoint::new_standard()),
20130            (2, disable_breakpoint),
20131            (3, Breakpoint::new_standard()),
20132        ],
20133    );
20134}
20135
20136#[gpui::test]
20137async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20138    init_test(cx, |_| {});
20139    let capabilities = lsp::ServerCapabilities {
20140        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20141            prepare_provider: Some(true),
20142            work_done_progress_options: Default::default(),
20143        })),
20144        ..Default::default()
20145    };
20146    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20147
20148    cx.set_state(indoc! {"
20149        struct Fˇoo {}
20150    "});
20151
20152    cx.update_editor(|editor, _, cx| {
20153        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20154        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20155        editor.highlight_background::<DocumentHighlightRead>(
20156            &[highlight_range],
20157            |c| c.editor_document_highlight_read_background,
20158            cx,
20159        );
20160    });
20161
20162    let mut prepare_rename_handler = cx
20163        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20164            move |_, _, _| async move {
20165                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20166                    start: lsp::Position {
20167                        line: 0,
20168                        character: 7,
20169                    },
20170                    end: lsp::Position {
20171                        line: 0,
20172                        character: 10,
20173                    },
20174                })))
20175            },
20176        );
20177    let prepare_rename_task = cx
20178        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20179        .expect("Prepare rename was not started");
20180    prepare_rename_handler.next().await.unwrap();
20181    prepare_rename_task.await.expect("Prepare rename failed");
20182
20183    let mut rename_handler =
20184        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20185            let edit = lsp::TextEdit {
20186                range: lsp::Range {
20187                    start: lsp::Position {
20188                        line: 0,
20189                        character: 7,
20190                    },
20191                    end: lsp::Position {
20192                        line: 0,
20193                        character: 10,
20194                    },
20195                },
20196                new_text: "FooRenamed".to_string(),
20197            };
20198            Ok(Some(lsp::WorkspaceEdit::new(
20199                // Specify the same edit twice
20200                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20201            )))
20202        });
20203    let rename_task = cx
20204        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20205        .expect("Confirm rename was not started");
20206    rename_handler.next().await.unwrap();
20207    rename_task.await.expect("Confirm rename failed");
20208    cx.run_until_parked();
20209
20210    // Despite two edits, only one is actually applied as those are identical
20211    cx.assert_editor_state(indoc! {"
20212        struct FooRenamedˇ {}
20213    "});
20214}
20215
20216#[gpui::test]
20217async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20218    init_test(cx, |_| {});
20219    // These capabilities indicate that the server does not support prepare rename.
20220    let capabilities = lsp::ServerCapabilities {
20221        rename_provider: Some(lsp::OneOf::Left(true)),
20222        ..Default::default()
20223    };
20224    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20225
20226    cx.set_state(indoc! {"
20227        struct Fˇoo {}
20228    "});
20229
20230    cx.update_editor(|editor, _window, cx| {
20231        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20232        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20233        editor.highlight_background::<DocumentHighlightRead>(
20234            &[highlight_range],
20235            |c| c.editor_document_highlight_read_background,
20236            cx,
20237        );
20238    });
20239
20240    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20241        .expect("Prepare rename was not started")
20242        .await
20243        .expect("Prepare rename failed");
20244
20245    let mut rename_handler =
20246        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20247            let edit = lsp::TextEdit {
20248                range: lsp::Range {
20249                    start: lsp::Position {
20250                        line: 0,
20251                        character: 7,
20252                    },
20253                    end: lsp::Position {
20254                        line: 0,
20255                        character: 10,
20256                    },
20257                },
20258                new_text: "FooRenamed".to_string(),
20259            };
20260            Ok(Some(lsp::WorkspaceEdit::new(
20261                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20262            )))
20263        });
20264    let rename_task = cx
20265        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20266        .expect("Confirm rename was not started");
20267    rename_handler.next().await.unwrap();
20268    rename_task.await.expect("Confirm rename failed");
20269    cx.run_until_parked();
20270
20271    // Correct range is renamed, as `surrounding_word` is used to find it.
20272    cx.assert_editor_state(indoc! {"
20273        struct FooRenamedˇ {}
20274    "});
20275}
20276
20277#[gpui::test]
20278async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20279    init_test(cx, |_| {});
20280    let mut cx = EditorTestContext::new(cx).await;
20281
20282    let language = Arc::new(
20283        Language::new(
20284            LanguageConfig::default(),
20285            Some(tree_sitter_html::LANGUAGE.into()),
20286        )
20287        .with_brackets_query(
20288            r#"
20289            ("<" @open "/>" @close)
20290            ("</" @open ">" @close)
20291            ("<" @open ">" @close)
20292            ("\"" @open "\"" @close)
20293            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20294        "#,
20295        )
20296        .unwrap(),
20297    );
20298    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20299
20300    cx.set_state(indoc! {"
20301        <span>ˇ</span>
20302    "});
20303    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20304    cx.assert_editor_state(indoc! {"
20305        <span>
20306        ˇ
20307        </span>
20308    "});
20309
20310    cx.set_state(indoc! {"
20311        <span><span></span>ˇ</span>
20312    "});
20313    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20314    cx.assert_editor_state(indoc! {"
20315        <span><span></span>
20316        ˇ</span>
20317    "});
20318
20319    cx.set_state(indoc! {"
20320        <span>ˇ
20321        </span>
20322    "});
20323    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20324    cx.assert_editor_state(indoc! {"
20325        <span>
20326        ˇ
20327        </span>
20328    "});
20329}
20330
20331#[gpui::test(iterations = 10)]
20332async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20333    init_test(cx, |_| {});
20334
20335    let fs = FakeFs::new(cx.executor());
20336    fs.insert_tree(
20337        path!("/dir"),
20338        json!({
20339            "a.ts": "a",
20340        }),
20341    )
20342    .await;
20343
20344    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20345    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20346    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20347
20348    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20349    language_registry.add(Arc::new(Language::new(
20350        LanguageConfig {
20351            name: "TypeScript".into(),
20352            matcher: LanguageMatcher {
20353                path_suffixes: vec!["ts".to_string()],
20354                ..Default::default()
20355            },
20356            ..Default::default()
20357        },
20358        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20359    )));
20360    let mut fake_language_servers = language_registry.register_fake_lsp(
20361        "TypeScript",
20362        FakeLspAdapter {
20363            capabilities: lsp::ServerCapabilities {
20364                code_lens_provider: Some(lsp::CodeLensOptions {
20365                    resolve_provider: Some(true),
20366                }),
20367                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20368                    commands: vec!["_the/command".to_string()],
20369                    ..lsp::ExecuteCommandOptions::default()
20370                }),
20371                ..lsp::ServerCapabilities::default()
20372            },
20373            ..FakeLspAdapter::default()
20374        },
20375    );
20376
20377    let (buffer, _handle) = project
20378        .update(cx, |p, cx| {
20379            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20380        })
20381        .await
20382        .unwrap();
20383    cx.executor().run_until_parked();
20384
20385    let fake_server = fake_language_servers.next().await.unwrap();
20386
20387    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20388    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20389    drop(buffer_snapshot);
20390    let actions = cx
20391        .update_window(*workspace, |_, window, cx| {
20392            project.code_actions(&buffer, anchor..anchor, window, cx)
20393        })
20394        .unwrap();
20395
20396    fake_server
20397        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20398            Ok(Some(vec![
20399                lsp::CodeLens {
20400                    range: lsp::Range::default(),
20401                    command: Some(lsp::Command {
20402                        title: "Code lens command".to_owned(),
20403                        command: "_the/command".to_owned(),
20404                        arguments: None,
20405                    }),
20406                    data: None,
20407                },
20408                lsp::CodeLens {
20409                    range: lsp::Range::default(),
20410                    command: Some(lsp::Command {
20411                        title: "Command not in capabilities".to_owned(),
20412                        command: "not in capabilities".to_owned(),
20413                        arguments: None,
20414                    }),
20415                    data: None,
20416                },
20417                lsp::CodeLens {
20418                    range: lsp::Range {
20419                        start: lsp::Position {
20420                            line: 1,
20421                            character: 1,
20422                        },
20423                        end: lsp::Position {
20424                            line: 1,
20425                            character: 1,
20426                        },
20427                    },
20428                    command: Some(lsp::Command {
20429                        title: "Command not in range".to_owned(),
20430                        command: "_the/command".to_owned(),
20431                        arguments: None,
20432                    }),
20433                    data: None,
20434                },
20435            ]))
20436        })
20437        .next()
20438        .await;
20439
20440    let actions = actions.await.unwrap();
20441    assert_eq!(
20442        actions.len(),
20443        1,
20444        "Should have only one valid action for the 0..0 range"
20445    );
20446    let action = actions[0].clone();
20447    let apply = project.update(cx, |project, cx| {
20448        project.apply_code_action(buffer.clone(), action, true, cx)
20449    });
20450
20451    // Resolving the code action does not populate its edits. In absence of
20452    // edits, we must execute the given command.
20453    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20454        |mut lens, _| async move {
20455            let lens_command = lens.command.as_mut().expect("should have a command");
20456            assert_eq!(lens_command.title, "Code lens command");
20457            lens_command.arguments = Some(vec![json!("the-argument")]);
20458            Ok(lens)
20459        },
20460    );
20461
20462    // While executing the command, the language server sends the editor
20463    // a `workspaceEdit` request.
20464    fake_server
20465        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20466            let fake = fake_server.clone();
20467            move |params, _| {
20468                assert_eq!(params.command, "_the/command");
20469                let fake = fake.clone();
20470                async move {
20471                    fake.server
20472                        .request::<lsp::request::ApplyWorkspaceEdit>(
20473                            lsp::ApplyWorkspaceEditParams {
20474                                label: None,
20475                                edit: lsp::WorkspaceEdit {
20476                                    changes: Some(
20477                                        [(
20478                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20479                                            vec![lsp::TextEdit {
20480                                                range: lsp::Range::new(
20481                                                    lsp::Position::new(0, 0),
20482                                                    lsp::Position::new(0, 0),
20483                                                ),
20484                                                new_text: "X".into(),
20485                                            }],
20486                                        )]
20487                                        .into_iter()
20488                                        .collect(),
20489                                    ),
20490                                    ..Default::default()
20491                                },
20492                            },
20493                        )
20494                        .await
20495                        .into_response()
20496                        .unwrap();
20497                    Ok(Some(json!(null)))
20498                }
20499            }
20500        })
20501        .next()
20502        .await;
20503
20504    // Applying the code lens command returns a project transaction containing the edits
20505    // sent by the language server in its `workspaceEdit` request.
20506    let transaction = apply.await.unwrap();
20507    assert!(transaction.0.contains_key(&buffer));
20508    buffer.update(cx, |buffer, cx| {
20509        assert_eq!(buffer.text(), "Xa");
20510        buffer.undo(cx);
20511        assert_eq!(buffer.text(), "a");
20512    });
20513}
20514
20515#[gpui::test]
20516async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20517    init_test(cx, |_| {});
20518
20519    let fs = FakeFs::new(cx.executor());
20520    let main_text = r#"fn main() {
20521println!("1");
20522println!("2");
20523println!("3");
20524println!("4");
20525println!("5");
20526}"#;
20527    let lib_text = "mod foo {}";
20528    fs.insert_tree(
20529        path!("/a"),
20530        json!({
20531            "lib.rs": lib_text,
20532            "main.rs": main_text,
20533        }),
20534    )
20535    .await;
20536
20537    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20538    let (workspace, cx) =
20539        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20540    let worktree_id = workspace.update(cx, |workspace, cx| {
20541        workspace.project().update(cx, |project, cx| {
20542            project.worktrees(cx).next().unwrap().read(cx).id()
20543        })
20544    });
20545
20546    let expected_ranges = vec![
20547        Point::new(0, 0)..Point::new(0, 0),
20548        Point::new(1, 0)..Point::new(1, 1),
20549        Point::new(2, 0)..Point::new(2, 2),
20550        Point::new(3, 0)..Point::new(3, 3),
20551    ];
20552
20553    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20554    let editor_1 = workspace
20555        .update_in(cx, |workspace, window, cx| {
20556            workspace.open_path(
20557                (worktree_id, "main.rs"),
20558                Some(pane_1.downgrade()),
20559                true,
20560                window,
20561                cx,
20562            )
20563        })
20564        .unwrap()
20565        .await
20566        .downcast::<Editor>()
20567        .unwrap();
20568    pane_1.update(cx, |pane, cx| {
20569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20570        open_editor.update(cx, |editor, cx| {
20571            assert_eq!(
20572                editor.display_text(cx),
20573                main_text,
20574                "Original main.rs text on initial open",
20575            );
20576            assert_eq!(
20577                editor
20578                    .selections
20579                    .all::<Point>(cx)
20580                    .into_iter()
20581                    .map(|s| s.range())
20582                    .collect::<Vec<_>>(),
20583                vec![Point::zero()..Point::zero()],
20584                "Default selections on initial open",
20585            );
20586        })
20587    });
20588    editor_1.update_in(cx, |editor, window, cx| {
20589        editor.change_selections(None, window, cx, |s| {
20590            s.select_ranges(expected_ranges.clone());
20591        });
20592    });
20593
20594    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20595        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20596    });
20597    let editor_2 = workspace
20598        .update_in(cx, |workspace, window, cx| {
20599            workspace.open_path(
20600                (worktree_id, "main.rs"),
20601                Some(pane_2.downgrade()),
20602                true,
20603                window,
20604                cx,
20605            )
20606        })
20607        .unwrap()
20608        .await
20609        .downcast::<Editor>()
20610        .unwrap();
20611    pane_2.update(cx, |pane, cx| {
20612        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20613        open_editor.update(cx, |editor, cx| {
20614            assert_eq!(
20615                editor.display_text(cx),
20616                main_text,
20617                "Original main.rs text on initial open in another panel",
20618            );
20619            assert_eq!(
20620                editor
20621                    .selections
20622                    .all::<Point>(cx)
20623                    .into_iter()
20624                    .map(|s| s.range())
20625                    .collect::<Vec<_>>(),
20626                vec![Point::zero()..Point::zero()],
20627                "Default selections on initial open in another panel",
20628            );
20629        })
20630    });
20631
20632    editor_2.update_in(cx, |editor, window, cx| {
20633        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20634    });
20635
20636    let _other_editor_1 = workspace
20637        .update_in(cx, |workspace, window, cx| {
20638            workspace.open_path(
20639                (worktree_id, "lib.rs"),
20640                Some(pane_1.downgrade()),
20641                true,
20642                window,
20643                cx,
20644            )
20645        })
20646        .unwrap()
20647        .await
20648        .downcast::<Editor>()
20649        .unwrap();
20650    pane_1
20651        .update_in(cx, |pane, window, cx| {
20652            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20653        })
20654        .await
20655        .unwrap();
20656    drop(editor_1);
20657    pane_1.update(cx, |pane, cx| {
20658        pane.active_item()
20659            .unwrap()
20660            .downcast::<Editor>()
20661            .unwrap()
20662            .update(cx, |editor, cx| {
20663                assert_eq!(
20664                    editor.display_text(cx),
20665                    lib_text,
20666                    "Other file should be open and active",
20667                );
20668            });
20669        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20670    });
20671
20672    let _other_editor_2 = workspace
20673        .update_in(cx, |workspace, window, cx| {
20674            workspace.open_path(
20675                (worktree_id, "lib.rs"),
20676                Some(pane_2.downgrade()),
20677                true,
20678                window,
20679                cx,
20680            )
20681        })
20682        .unwrap()
20683        .await
20684        .downcast::<Editor>()
20685        .unwrap();
20686    pane_2
20687        .update_in(cx, |pane, window, cx| {
20688            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20689        })
20690        .await
20691        .unwrap();
20692    drop(editor_2);
20693    pane_2.update(cx, |pane, cx| {
20694        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20695        open_editor.update(cx, |editor, cx| {
20696            assert_eq!(
20697                editor.display_text(cx),
20698                lib_text,
20699                "Other file should be open and active in another panel too",
20700            );
20701        });
20702        assert_eq!(
20703            pane.items().count(),
20704            1,
20705            "No other editors should be open in another pane",
20706        );
20707    });
20708
20709    let _editor_1_reopened = workspace
20710        .update_in(cx, |workspace, window, cx| {
20711            workspace.open_path(
20712                (worktree_id, "main.rs"),
20713                Some(pane_1.downgrade()),
20714                true,
20715                window,
20716                cx,
20717            )
20718        })
20719        .unwrap()
20720        .await
20721        .downcast::<Editor>()
20722        .unwrap();
20723    let _editor_2_reopened = workspace
20724        .update_in(cx, |workspace, window, cx| {
20725            workspace.open_path(
20726                (worktree_id, "main.rs"),
20727                Some(pane_2.downgrade()),
20728                true,
20729                window,
20730                cx,
20731            )
20732        })
20733        .unwrap()
20734        .await
20735        .downcast::<Editor>()
20736        .unwrap();
20737    pane_1.update(cx, |pane, cx| {
20738        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20739        open_editor.update(cx, |editor, cx| {
20740            assert_eq!(
20741                editor.display_text(cx),
20742                main_text,
20743                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20744            );
20745            assert_eq!(
20746                editor
20747                    .selections
20748                    .all::<Point>(cx)
20749                    .into_iter()
20750                    .map(|s| s.range())
20751                    .collect::<Vec<_>>(),
20752                expected_ranges,
20753                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20754            );
20755        })
20756    });
20757    pane_2.update(cx, |pane, cx| {
20758        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20759        open_editor.update(cx, |editor, cx| {
20760            assert_eq!(
20761                editor.display_text(cx),
20762                r#"fn main() {
20763⋯rintln!("1");
20764⋯intln!("2");
20765⋯ntln!("3");
20766println!("4");
20767println!("5");
20768}"#,
20769                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20770            );
20771            assert_eq!(
20772                editor
20773                    .selections
20774                    .all::<Point>(cx)
20775                    .into_iter()
20776                    .map(|s| s.range())
20777                    .collect::<Vec<_>>(),
20778                vec![Point::zero()..Point::zero()],
20779                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20780            );
20781        })
20782    });
20783}
20784
20785#[gpui::test]
20786async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20787    init_test(cx, |_| {});
20788
20789    let fs = FakeFs::new(cx.executor());
20790    let main_text = r#"fn main() {
20791println!("1");
20792println!("2");
20793println!("3");
20794println!("4");
20795println!("5");
20796}"#;
20797    let lib_text = "mod foo {}";
20798    fs.insert_tree(
20799        path!("/a"),
20800        json!({
20801            "lib.rs": lib_text,
20802            "main.rs": main_text,
20803        }),
20804    )
20805    .await;
20806
20807    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20808    let (workspace, cx) =
20809        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20810    let worktree_id = workspace.update(cx, |workspace, cx| {
20811        workspace.project().update(cx, |project, cx| {
20812            project.worktrees(cx).next().unwrap().read(cx).id()
20813        })
20814    });
20815
20816    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20817    let editor = workspace
20818        .update_in(cx, |workspace, window, cx| {
20819            workspace.open_path(
20820                (worktree_id, "main.rs"),
20821                Some(pane.downgrade()),
20822                true,
20823                window,
20824                cx,
20825            )
20826        })
20827        .unwrap()
20828        .await
20829        .downcast::<Editor>()
20830        .unwrap();
20831    pane.update(cx, |pane, cx| {
20832        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20833        open_editor.update(cx, |editor, cx| {
20834            assert_eq!(
20835                editor.display_text(cx),
20836                main_text,
20837                "Original main.rs text on initial open",
20838            );
20839        })
20840    });
20841    editor.update_in(cx, |editor, window, cx| {
20842        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20843    });
20844
20845    cx.update_global(|store: &mut SettingsStore, cx| {
20846        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20847            s.restore_on_file_reopen = Some(false);
20848        });
20849    });
20850    editor.update_in(cx, |editor, window, cx| {
20851        editor.fold_ranges(
20852            vec![
20853                Point::new(1, 0)..Point::new(1, 1),
20854                Point::new(2, 0)..Point::new(2, 2),
20855                Point::new(3, 0)..Point::new(3, 3),
20856            ],
20857            false,
20858            window,
20859            cx,
20860        );
20861    });
20862    pane.update_in(cx, |pane, window, cx| {
20863        pane.close_all_items(&CloseAllItems::default(), window, cx)
20864    })
20865    .await
20866    .unwrap();
20867    pane.update(cx, |pane, _| {
20868        assert!(pane.active_item().is_none());
20869    });
20870    cx.update_global(|store: &mut SettingsStore, cx| {
20871        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20872            s.restore_on_file_reopen = Some(true);
20873        });
20874    });
20875
20876    let _editor_reopened = workspace
20877        .update_in(cx, |workspace, window, cx| {
20878            workspace.open_path(
20879                (worktree_id, "main.rs"),
20880                Some(pane.downgrade()),
20881                true,
20882                window,
20883                cx,
20884            )
20885        })
20886        .unwrap()
20887        .await
20888        .downcast::<Editor>()
20889        .unwrap();
20890    pane.update(cx, |pane, cx| {
20891        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20892        open_editor.update(cx, |editor, cx| {
20893            assert_eq!(
20894                editor.display_text(cx),
20895                main_text,
20896                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20897            );
20898        })
20899    });
20900}
20901
20902#[gpui::test]
20903async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20904    struct EmptyModalView {
20905        focus_handle: gpui::FocusHandle,
20906    }
20907    impl EventEmitter<DismissEvent> for EmptyModalView {}
20908    impl Render for EmptyModalView {
20909        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20910            div()
20911        }
20912    }
20913    impl Focusable for EmptyModalView {
20914        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20915            self.focus_handle.clone()
20916        }
20917    }
20918    impl workspace::ModalView for EmptyModalView {}
20919    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20920        EmptyModalView {
20921            focus_handle: cx.focus_handle(),
20922        }
20923    }
20924
20925    init_test(cx, |_| {});
20926
20927    let fs = FakeFs::new(cx.executor());
20928    let project = Project::test(fs, [], cx).await;
20929    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20930    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20931    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20932    let editor = cx.new_window_entity(|window, cx| {
20933        Editor::new(
20934            EditorMode::full(),
20935            buffer,
20936            Some(project.clone()),
20937            window,
20938            cx,
20939        )
20940    });
20941    workspace
20942        .update(cx, |workspace, window, cx| {
20943            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20944        })
20945        .unwrap();
20946    editor.update_in(cx, |editor, window, cx| {
20947        editor.open_context_menu(&OpenContextMenu, window, cx);
20948        assert!(editor.mouse_context_menu.is_some());
20949    });
20950    workspace
20951        .update(cx, |workspace, window, cx| {
20952            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20953        })
20954        .unwrap();
20955    cx.read(|cx| {
20956        assert!(editor.read(cx).mouse_context_menu.is_none());
20957    });
20958}
20959
20960#[gpui::test]
20961async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20962    init_test(cx, |_| {});
20963
20964    let fs = FakeFs::new(cx.executor());
20965    fs.insert_file(path!("/file.html"), Default::default())
20966        .await;
20967
20968    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20969
20970    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20971    let html_language = Arc::new(Language::new(
20972        LanguageConfig {
20973            name: "HTML".into(),
20974            matcher: LanguageMatcher {
20975                path_suffixes: vec!["html".to_string()],
20976                ..LanguageMatcher::default()
20977            },
20978            brackets: BracketPairConfig {
20979                pairs: vec![BracketPair {
20980                    start: "<".into(),
20981                    end: ">".into(),
20982                    close: true,
20983                    ..Default::default()
20984                }],
20985                ..Default::default()
20986            },
20987            ..Default::default()
20988        },
20989        Some(tree_sitter_html::LANGUAGE.into()),
20990    ));
20991    language_registry.add(html_language);
20992    let mut fake_servers = language_registry.register_fake_lsp(
20993        "HTML",
20994        FakeLspAdapter {
20995            capabilities: lsp::ServerCapabilities {
20996                completion_provider: Some(lsp::CompletionOptions {
20997                    resolve_provider: Some(true),
20998                    ..Default::default()
20999                }),
21000                ..Default::default()
21001            },
21002            ..Default::default()
21003        },
21004    );
21005
21006    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21007    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21008
21009    let worktree_id = workspace
21010        .update(cx, |workspace, _window, cx| {
21011            workspace.project().update(cx, |project, cx| {
21012                project.worktrees(cx).next().unwrap().read(cx).id()
21013            })
21014        })
21015        .unwrap();
21016    project
21017        .update(cx, |project, cx| {
21018            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21019        })
21020        .await
21021        .unwrap();
21022    let editor = workspace
21023        .update(cx, |workspace, window, cx| {
21024            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21025        })
21026        .unwrap()
21027        .await
21028        .unwrap()
21029        .downcast::<Editor>()
21030        .unwrap();
21031
21032    let fake_server = fake_servers.next().await.unwrap();
21033    editor.update_in(cx, |editor, window, cx| {
21034        editor.set_text("<ad></ad>", window, cx);
21035        editor.change_selections(None, window, cx, |selections| {
21036            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21037        });
21038        let Some((buffer, _)) = editor
21039            .buffer
21040            .read(cx)
21041            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21042        else {
21043            panic!("Failed to get buffer for selection position");
21044        };
21045        let buffer = buffer.read(cx);
21046        let buffer_id = buffer.remote_id();
21047        let opening_range =
21048            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21049        let closing_range =
21050            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21051        let mut linked_ranges = HashMap::default();
21052        linked_ranges.insert(
21053            buffer_id,
21054            vec![(opening_range.clone(), vec![closing_range.clone()])],
21055        );
21056        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21057    });
21058    let mut completion_handle =
21059        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21060            Ok(Some(lsp::CompletionResponse::Array(vec![
21061                lsp::CompletionItem {
21062                    label: "head".to_string(),
21063                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21064                        lsp::InsertReplaceEdit {
21065                            new_text: "head".to_string(),
21066                            insert: lsp::Range::new(
21067                                lsp::Position::new(0, 1),
21068                                lsp::Position::new(0, 3),
21069                            ),
21070                            replace: lsp::Range::new(
21071                                lsp::Position::new(0, 1),
21072                                lsp::Position::new(0, 3),
21073                            ),
21074                        },
21075                    )),
21076                    ..Default::default()
21077                },
21078            ])))
21079        });
21080    editor.update_in(cx, |editor, window, cx| {
21081        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21082    });
21083    cx.run_until_parked();
21084    completion_handle.next().await.unwrap();
21085    editor.update(cx, |editor, _| {
21086        assert!(
21087            editor.context_menu_visible(),
21088            "Completion menu should be visible"
21089        );
21090    });
21091    editor.update_in(cx, |editor, window, cx| {
21092        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21093    });
21094    cx.executor().run_until_parked();
21095    editor.update(cx, |editor, cx| {
21096        assert_eq!(editor.text(cx), "<head></head>");
21097    });
21098}
21099
21100#[gpui::test]
21101async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21102    init_test(cx, |_| {});
21103
21104    let fs = FakeFs::new(cx.executor());
21105    fs.insert_tree(
21106        path!("/root"),
21107        json!({
21108            "a": {
21109                "main.rs": "fn main() {}",
21110            },
21111            "foo": {
21112                "bar": {
21113                    "external_file.rs": "pub mod external {}",
21114                }
21115            }
21116        }),
21117    )
21118    .await;
21119
21120    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21121    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21122    language_registry.add(rust_lang());
21123    let _fake_servers = language_registry.register_fake_lsp(
21124        "Rust",
21125        FakeLspAdapter {
21126            ..FakeLspAdapter::default()
21127        },
21128    );
21129    let (workspace, cx) =
21130        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21131    let worktree_id = workspace.update(cx, |workspace, cx| {
21132        workspace.project().update(cx, |project, cx| {
21133            project.worktrees(cx).next().unwrap().read(cx).id()
21134        })
21135    });
21136
21137    let assert_language_servers_count =
21138        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21139            project.update(cx, |project, cx| {
21140                let current = project
21141                    .lsp_store()
21142                    .read(cx)
21143                    .as_local()
21144                    .unwrap()
21145                    .language_servers
21146                    .len();
21147                assert_eq!(expected, current, "{context}");
21148            });
21149        };
21150
21151    assert_language_servers_count(
21152        0,
21153        "No servers should be running before any file is open",
21154        cx,
21155    );
21156    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21157    let main_editor = workspace
21158        .update_in(cx, |workspace, window, cx| {
21159            workspace.open_path(
21160                (worktree_id, "main.rs"),
21161                Some(pane.downgrade()),
21162                true,
21163                window,
21164                cx,
21165            )
21166        })
21167        .unwrap()
21168        .await
21169        .downcast::<Editor>()
21170        .unwrap();
21171    pane.update(cx, |pane, cx| {
21172        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21173        open_editor.update(cx, |editor, cx| {
21174            assert_eq!(
21175                editor.display_text(cx),
21176                "fn main() {}",
21177                "Original main.rs text on initial open",
21178            );
21179        });
21180        assert_eq!(open_editor, main_editor);
21181    });
21182    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21183
21184    let external_editor = workspace
21185        .update_in(cx, |workspace, window, cx| {
21186            workspace.open_abs_path(
21187                PathBuf::from("/root/foo/bar/external_file.rs"),
21188                OpenOptions::default(),
21189                window,
21190                cx,
21191            )
21192        })
21193        .await
21194        .expect("opening external file")
21195        .downcast::<Editor>()
21196        .expect("downcasted external file's open element to editor");
21197    pane.update(cx, |pane, cx| {
21198        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21199        open_editor.update(cx, |editor, cx| {
21200            assert_eq!(
21201                editor.display_text(cx),
21202                "pub mod external {}",
21203                "External file is open now",
21204            );
21205        });
21206        assert_eq!(open_editor, external_editor);
21207    });
21208    assert_language_servers_count(
21209        1,
21210        "Second, external, *.rs file should join the existing server",
21211        cx,
21212    );
21213
21214    pane.update_in(cx, |pane, window, cx| {
21215        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21216    })
21217    .await
21218    .unwrap();
21219    pane.update_in(cx, |pane, window, cx| {
21220        pane.navigate_backward(window, cx);
21221    });
21222    cx.run_until_parked();
21223    pane.update(cx, |pane, cx| {
21224        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21225        open_editor.update(cx, |editor, cx| {
21226            assert_eq!(
21227                editor.display_text(cx),
21228                "pub mod external {}",
21229                "External file is open now",
21230            );
21231        });
21232    });
21233    assert_language_servers_count(
21234        1,
21235        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21236        cx,
21237    );
21238
21239    cx.update(|_, cx| {
21240        workspace::reload(&workspace::Reload::default(), cx);
21241    });
21242    assert_language_servers_count(
21243        1,
21244        "After reloading the worktree with local and external files opened, only one project should be started",
21245        cx,
21246    );
21247}
21248
21249#[gpui::test]
21250async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21251    init_test(cx, |_| {});
21252
21253    let mut cx = EditorTestContext::new(cx).await;
21254    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21255    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21256
21257    // test cursor move to start of each line on tab
21258    // for `if`, `elif`, `else`, `while`, `with` and `for`
21259    cx.set_state(indoc! {"
21260        def main():
21261        ˇ    for item in items:
21262        ˇ        while item.active:
21263        ˇ            if item.value > 10:
21264        ˇ                continue
21265        ˇ            elif item.value < 0:
21266        ˇ                break
21267        ˇ            else:
21268        ˇ                with item.context() as ctx:
21269        ˇ                    yield count
21270        ˇ        else:
21271        ˇ            log('while else')
21272        ˇ    else:
21273        ˇ        log('for else')
21274    "});
21275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21276    cx.assert_editor_state(indoc! {"
21277        def main():
21278            ˇfor item in items:
21279                ˇwhile item.active:
21280                    ˇif item.value > 10:
21281                        ˇcontinue
21282                    ˇelif item.value < 0:
21283                        ˇbreak
21284                    ˇelse:
21285                        ˇwith item.context() as ctx:
21286                            ˇyield count
21287                ˇelse:
21288                    ˇlog('while else')
21289            ˇelse:
21290                ˇlog('for else')
21291    "});
21292    // test relative indent is preserved when tab
21293    // for `if`, `elif`, `else`, `while`, `with` and `for`
21294    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21295    cx.assert_editor_state(indoc! {"
21296        def main():
21297                ˇfor item in items:
21298                    ˇwhile item.active:
21299                        ˇif item.value > 10:
21300                            ˇcontinue
21301                        ˇelif item.value < 0:
21302                            ˇbreak
21303                        ˇelse:
21304                            ˇwith item.context() as ctx:
21305                                ˇyield count
21306                    ˇelse:
21307                        ˇlog('while else')
21308                ˇelse:
21309                    ˇlog('for else')
21310    "});
21311
21312    // test cursor move to start of each line on tab
21313    // for `try`, `except`, `else`, `finally`, `match` and `def`
21314    cx.set_state(indoc! {"
21315        def main():
21316        ˇ    try:
21317        ˇ       fetch()
21318        ˇ    except ValueError:
21319        ˇ       handle_error()
21320        ˇ    else:
21321        ˇ        match value:
21322        ˇ            case _:
21323        ˇ    finally:
21324        ˇ        def status():
21325        ˇ            return 0
21326    "});
21327    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21328    cx.assert_editor_state(indoc! {"
21329        def main():
21330            ˇtry:
21331                ˇfetch()
21332            ˇexcept ValueError:
21333                ˇhandle_error()
21334            ˇelse:
21335                ˇmatch value:
21336                    ˇcase _:
21337            ˇfinally:
21338                ˇdef status():
21339                    ˇreturn 0
21340    "});
21341    // test relative indent is preserved when tab
21342    // for `try`, `except`, `else`, `finally`, `match` and `def`
21343    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21344    cx.assert_editor_state(indoc! {"
21345        def main():
21346                ˇtry:
21347                    ˇfetch()
21348                ˇexcept ValueError:
21349                    ˇhandle_error()
21350                ˇelse:
21351                    ˇmatch value:
21352                        ˇcase _:
21353                ˇfinally:
21354                    ˇdef status():
21355                        ˇreturn 0
21356    "});
21357}
21358
21359#[gpui::test]
21360async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21361    init_test(cx, |_| {});
21362
21363    let mut cx = EditorTestContext::new(cx).await;
21364    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21365    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21366
21367    // test `else` auto outdents when typed inside `if` block
21368    cx.set_state(indoc! {"
21369        def main():
21370            if i == 2:
21371                return
21372                ˇ
21373    "});
21374    cx.update_editor(|editor, window, cx| {
21375        editor.handle_input("else:", window, cx);
21376    });
21377    cx.assert_editor_state(indoc! {"
21378        def main():
21379            if i == 2:
21380                return
21381            else:ˇ
21382    "});
21383
21384    // test `except` auto outdents when typed inside `try` block
21385    cx.set_state(indoc! {"
21386        def main():
21387            try:
21388                i = 2
21389                ˇ
21390    "});
21391    cx.update_editor(|editor, window, cx| {
21392        editor.handle_input("except:", window, cx);
21393    });
21394    cx.assert_editor_state(indoc! {"
21395        def main():
21396            try:
21397                i = 2
21398            except:ˇ
21399    "});
21400
21401    // test `else` auto outdents when typed inside `except` block
21402    cx.set_state(indoc! {"
21403        def main():
21404            try:
21405                i = 2
21406            except:
21407                j = 2
21408                ˇ
21409    "});
21410    cx.update_editor(|editor, window, cx| {
21411        editor.handle_input("else:", window, cx);
21412    });
21413    cx.assert_editor_state(indoc! {"
21414        def main():
21415            try:
21416                i = 2
21417            except:
21418                j = 2
21419            else:ˇ
21420    "});
21421
21422    // test `finally` auto outdents when typed inside `else` block
21423    cx.set_state(indoc! {"
21424        def main():
21425            try:
21426                i = 2
21427            except:
21428                j = 2
21429            else:
21430                k = 2
21431                ˇ
21432    "});
21433    cx.update_editor(|editor, window, cx| {
21434        editor.handle_input("finally:", window, cx);
21435    });
21436    cx.assert_editor_state(indoc! {"
21437        def main():
21438            try:
21439                i = 2
21440            except:
21441                j = 2
21442            else:
21443                k = 2
21444            finally:ˇ
21445    "});
21446
21447    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21448    // cx.set_state(indoc! {"
21449    //     def main():
21450    //         try:
21451    //             for i in range(n):
21452    //                 pass
21453    //             ˇ
21454    // "});
21455    // cx.update_editor(|editor, window, cx| {
21456    //     editor.handle_input("except:", window, cx);
21457    // });
21458    // cx.assert_editor_state(indoc! {"
21459    //     def main():
21460    //         try:
21461    //             for i in range(n):
21462    //                 pass
21463    //         except:ˇ
21464    // "});
21465
21466    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21467    // cx.set_state(indoc! {"
21468    //     def main():
21469    //         try:
21470    //             i = 2
21471    //         except:
21472    //             for i in range(n):
21473    //                 pass
21474    //             ˇ
21475    // "});
21476    // cx.update_editor(|editor, window, cx| {
21477    //     editor.handle_input("else:", window, cx);
21478    // });
21479    // cx.assert_editor_state(indoc! {"
21480    //     def main():
21481    //         try:
21482    //             i = 2
21483    //         except:
21484    //             for i in range(n):
21485    //                 pass
21486    //         else:ˇ
21487    // "});
21488
21489    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21490    // cx.set_state(indoc! {"
21491    //     def main():
21492    //         try:
21493    //             i = 2
21494    //         except:
21495    //             j = 2
21496    //         else:
21497    //             for i in range(n):
21498    //                 pass
21499    //             ˇ
21500    // "});
21501    // cx.update_editor(|editor, window, cx| {
21502    //     editor.handle_input("finally:", window, cx);
21503    // });
21504    // cx.assert_editor_state(indoc! {"
21505    //     def main():
21506    //         try:
21507    //             i = 2
21508    //         except:
21509    //             j = 2
21510    //         else:
21511    //             for i in range(n):
21512    //                 pass
21513    //         finally:ˇ
21514    // "});
21515
21516    // test `else` stays at correct indent when typed after `for` block
21517    cx.set_state(indoc! {"
21518        def main():
21519            for i in range(10):
21520                if i == 3:
21521                    break
21522            ˇ
21523    "});
21524    cx.update_editor(|editor, window, cx| {
21525        editor.handle_input("else:", window, cx);
21526    });
21527    cx.assert_editor_state(indoc! {"
21528        def main():
21529            for i in range(10):
21530                if i == 3:
21531                    break
21532            else:ˇ
21533    "});
21534
21535    // test does not outdent on typing after line with square brackets
21536    cx.set_state(indoc! {"
21537        def f() -> list[str]:
21538            ˇ
21539    "});
21540    cx.update_editor(|editor, window, cx| {
21541        editor.handle_input("a", window, cx);
21542    });
21543    cx.assert_editor_state(indoc! {"
21544        def f() -> list[str]:
2154521546    "});
21547}
21548
21549#[gpui::test]
21550async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21551    init_test(cx, |_| {});
21552    update_test_language_settings(cx, |settings| {
21553        settings.defaults.extend_comment_on_newline = Some(false);
21554    });
21555    let mut cx = EditorTestContext::new(cx).await;
21556    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21558
21559    // test correct indent after newline on comment
21560    cx.set_state(indoc! {"
21561        # COMMENT:ˇ
21562    "});
21563    cx.update_editor(|editor, window, cx| {
21564        editor.newline(&Newline, window, cx);
21565    });
21566    cx.assert_editor_state(indoc! {"
21567        # COMMENT:
21568        ˇ
21569    "});
21570
21571    // test correct indent after newline in brackets
21572    cx.set_state(indoc! {"
21573        {ˇ}
21574    "});
21575    cx.update_editor(|editor, window, cx| {
21576        editor.newline(&Newline, window, cx);
21577    });
21578    cx.run_until_parked();
21579    cx.assert_editor_state(indoc! {"
21580        {
21581            ˇ
21582        }
21583    "});
21584
21585    cx.set_state(indoc! {"
21586        (ˇ)
21587    "});
21588    cx.update_editor(|editor, window, cx| {
21589        editor.newline(&Newline, window, cx);
21590    });
21591    cx.run_until_parked();
21592    cx.assert_editor_state(indoc! {"
21593        (
21594            ˇ
21595        )
21596    "});
21597
21598    // do not indent after empty lists or dictionaries
21599    cx.set_state(indoc! {"
21600        a = []ˇ
21601    "});
21602    cx.update_editor(|editor, window, cx| {
21603        editor.newline(&Newline, window, cx);
21604    });
21605    cx.run_until_parked();
21606    cx.assert_editor_state(indoc! {"
21607        a = []
21608        ˇ
21609    "});
21610}
21611
21612fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21613    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21614    point..point
21615}
21616
21617#[track_caller]
21618fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21619    let (text, ranges) = marked_text_ranges(marked_text, true);
21620    assert_eq!(editor.text(cx), text);
21621    assert_eq!(
21622        editor.selections.ranges(cx),
21623        ranges,
21624        "Assert selections are {}",
21625        marked_text
21626    );
21627}
21628
21629pub fn handle_signature_help_request(
21630    cx: &mut EditorLspTestContext,
21631    mocked_response: lsp::SignatureHelp,
21632) -> impl Future<Output = ()> + use<> {
21633    let mut request =
21634        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21635            let mocked_response = mocked_response.clone();
21636            async move { Ok(Some(mocked_response)) }
21637        });
21638
21639    async move {
21640        request.next().await;
21641    }
21642}
21643
21644#[track_caller]
21645pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21646    cx.update_editor(|editor, _, _| {
21647        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21648            let entries = menu.entries.borrow();
21649            let entries = entries
21650                .iter()
21651                .map(|entry| entry.string.as_str())
21652                .collect::<Vec<_>>();
21653            assert_eq!(entries, expected);
21654        } else {
21655            panic!("Expected completions menu");
21656        }
21657    });
21658}
21659
21660/// Handle completion request passing a marked string specifying where the completion
21661/// should be triggered from using '|' character, what range should be replaced, and what completions
21662/// should be returned using '<' and '>' to delimit the range.
21663///
21664/// Also see `handle_completion_request_with_insert_and_replace`.
21665#[track_caller]
21666pub fn handle_completion_request(
21667    marked_string: &str,
21668    completions: Vec<&'static str>,
21669    is_incomplete: bool,
21670    counter: Arc<AtomicUsize>,
21671    cx: &mut EditorLspTestContext,
21672) -> impl Future<Output = ()> {
21673    let complete_from_marker: TextRangeMarker = '|'.into();
21674    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21675    let (_, mut marked_ranges) = marked_text_ranges_by(
21676        marked_string,
21677        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21678    );
21679
21680    let complete_from_position =
21681        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21682    let replace_range =
21683        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21684
21685    let mut request =
21686        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21687            let completions = completions.clone();
21688            counter.fetch_add(1, atomic::Ordering::Release);
21689            async move {
21690                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21691                assert_eq!(
21692                    params.text_document_position.position,
21693                    complete_from_position
21694                );
21695                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21696                    is_incomplete: is_incomplete,
21697                    item_defaults: None,
21698                    items: completions
21699                        .iter()
21700                        .map(|completion_text| lsp::CompletionItem {
21701                            label: completion_text.to_string(),
21702                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21703                                range: replace_range,
21704                                new_text: completion_text.to_string(),
21705                            })),
21706                            ..Default::default()
21707                        })
21708                        .collect(),
21709                })))
21710            }
21711        });
21712
21713    async move {
21714        request.next().await;
21715    }
21716}
21717
21718/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21719/// given instead, which also contains an `insert` range.
21720///
21721/// This function uses markers to define ranges:
21722/// - `|` marks the cursor position
21723/// - `<>` marks the replace range
21724/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21725pub fn handle_completion_request_with_insert_and_replace(
21726    cx: &mut EditorLspTestContext,
21727    marked_string: &str,
21728    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21729    counter: Arc<AtomicUsize>,
21730) -> impl Future<Output = ()> {
21731    let complete_from_marker: TextRangeMarker = '|'.into();
21732    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21733    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21734
21735    let (_, mut marked_ranges) = marked_text_ranges_by(
21736        marked_string,
21737        vec![
21738            complete_from_marker.clone(),
21739            replace_range_marker.clone(),
21740            insert_range_marker.clone(),
21741        ],
21742    );
21743
21744    let complete_from_position =
21745        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21746    let replace_range =
21747        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21748
21749    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21750        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21751        _ => lsp::Range {
21752            start: replace_range.start,
21753            end: complete_from_position,
21754        },
21755    };
21756
21757    let mut request =
21758        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21759            let completions = completions.clone();
21760            counter.fetch_add(1, atomic::Ordering::Release);
21761            async move {
21762                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21763                assert_eq!(
21764                    params.text_document_position.position, complete_from_position,
21765                    "marker `|` position doesn't match",
21766                );
21767                Ok(Some(lsp::CompletionResponse::Array(
21768                    completions
21769                        .iter()
21770                        .map(|(label, new_text)| lsp::CompletionItem {
21771                            label: label.to_string(),
21772                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21773                                lsp::InsertReplaceEdit {
21774                                    insert: insert_range,
21775                                    replace: replace_range,
21776                                    new_text: new_text.to_string(),
21777                                },
21778                            )),
21779                            ..Default::default()
21780                        })
21781                        .collect(),
21782                )))
21783            }
21784        });
21785
21786    async move {
21787        request.next().await;
21788    }
21789}
21790
21791fn handle_resolve_completion_request(
21792    cx: &mut EditorLspTestContext,
21793    edits: Option<Vec<(&'static str, &'static str)>>,
21794) -> impl Future<Output = ()> {
21795    let edits = edits.map(|edits| {
21796        edits
21797            .iter()
21798            .map(|(marked_string, new_text)| {
21799                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21800                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21801                lsp::TextEdit::new(replace_range, new_text.to_string())
21802            })
21803            .collect::<Vec<_>>()
21804    });
21805
21806    let mut request =
21807        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21808            let edits = edits.clone();
21809            async move {
21810                Ok(lsp::CompletionItem {
21811                    additional_text_edits: edits,
21812                    ..Default::default()
21813                })
21814            }
21815        });
21816
21817    async move {
21818        request.next().await;
21819    }
21820}
21821
21822pub(crate) fn update_test_language_settings(
21823    cx: &mut TestAppContext,
21824    f: impl Fn(&mut AllLanguageSettingsContent),
21825) {
21826    cx.update(|cx| {
21827        SettingsStore::update_global(cx, |store, cx| {
21828            store.update_user_settings::<AllLanguageSettings>(cx, f);
21829        });
21830    });
21831}
21832
21833pub(crate) fn update_test_project_settings(
21834    cx: &mut TestAppContext,
21835    f: impl Fn(&mut ProjectSettings),
21836) {
21837    cx.update(|cx| {
21838        SettingsStore::update_global(cx, |store, cx| {
21839            store.update_user_settings::<ProjectSettings>(cx, f);
21840        });
21841    });
21842}
21843
21844pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21845    cx.update(|cx| {
21846        assets::Assets.load_test_fonts(cx);
21847        let store = SettingsStore::test(cx);
21848        cx.set_global(store);
21849        theme::init(theme::LoadThemes::JustBase, cx);
21850        release_channel::init(SemanticVersion::default(), cx);
21851        client::init_settings(cx);
21852        language::init(cx);
21853        Project::init_settings(cx);
21854        workspace::init_settings(cx);
21855        crate::init(cx);
21856    });
21857
21858    update_test_language_settings(cx, f);
21859}
21860
21861#[track_caller]
21862fn assert_hunk_revert(
21863    not_reverted_text_with_selections: &str,
21864    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21865    expected_reverted_text_with_selections: &str,
21866    base_text: &str,
21867    cx: &mut EditorLspTestContext,
21868) {
21869    cx.set_state(not_reverted_text_with_selections);
21870    cx.set_head_text(base_text);
21871    cx.executor().run_until_parked();
21872
21873    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21874        let snapshot = editor.snapshot(window, cx);
21875        let reverted_hunk_statuses = snapshot
21876            .buffer_snapshot
21877            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21878            .map(|hunk| hunk.status().kind)
21879            .collect::<Vec<_>>();
21880
21881        editor.git_restore(&Default::default(), window, cx);
21882        reverted_hunk_statuses
21883    });
21884    cx.executor().run_until_parked();
21885    cx.assert_editor_state(expected_reverted_text_with_selections);
21886    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21887}
21888
21889#[gpui::test(iterations = 10)]
21890async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
21891    init_test(cx, |_| {});
21892
21893    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
21894    let counter = diagnostic_requests.clone();
21895
21896    let fs = FakeFs::new(cx.executor());
21897    fs.insert_tree(
21898        path!("/a"),
21899        json!({
21900            "first.rs": "fn main() { let a = 5; }",
21901            "second.rs": "// Test file",
21902        }),
21903    )
21904    .await;
21905
21906    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21907    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21908    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21909
21910    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21911    language_registry.add(rust_lang());
21912    let mut fake_servers = language_registry.register_fake_lsp(
21913        "Rust",
21914        FakeLspAdapter {
21915            capabilities: lsp::ServerCapabilities {
21916                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
21917                    lsp::DiagnosticOptions {
21918                        identifier: None,
21919                        inter_file_dependencies: true,
21920                        workspace_diagnostics: true,
21921                        work_done_progress_options: Default::default(),
21922                    },
21923                )),
21924                ..Default::default()
21925            },
21926            ..Default::default()
21927        },
21928    );
21929
21930    let editor = workspace
21931        .update(cx, |workspace, window, cx| {
21932            workspace.open_abs_path(
21933                PathBuf::from(path!("/a/first.rs")),
21934                OpenOptions::default(),
21935                window,
21936                cx,
21937            )
21938        })
21939        .unwrap()
21940        .await
21941        .unwrap()
21942        .downcast::<Editor>()
21943        .unwrap();
21944    let fake_server = fake_servers.next().await.unwrap();
21945    let mut first_request = fake_server
21946        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
21947            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
21948            let result_id = Some(new_result_id.to_string());
21949            assert_eq!(
21950                params.text_document.uri,
21951                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
21952            );
21953            async move {
21954                Ok(lsp::DocumentDiagnosticReportResult::Report(
21955                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
21956                        related_documents: None,
21957                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
21958                            items: Vec::new(),
21959                            result_id,
21960                        },
21961                    }),
21962                ))
21963            }
21964        });
21965
21966    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
21967        project.update(cx, |project, cx| {
21968            let buffer_id = editor
21969                .read(cx)
21970                .buffer()
21971                .read(cx)
21972                .as_singleton()
21973                .expect("created a singleton buffer")
21974                .read(cx)
21975                .remote_id();
21976            let buffer_result_id = project.lsp_store().read(cx).result_id(buffer_id, cx);
21977            assert_eq!(expected, buffer_result_id);
21978        });
21979    };
21980
21981    ensure_result_id(None, cx);
21982    cx.executor().advance_clock(Duration::from_millis(60));
21983    cx.executor().run_until_parked();
21984    assert_eq!(
21985        diagnostic_requests.load(atomic::Ordering::Acquire),
21986        1,
21987        "Opening file should trigger diagnostic request"
21988    );
21989    first_request
21990        .next()
21991        .await
21992        .expect("should have sent the first diagnostics pull request");
21993    ensure_result_id(Some("1".to_string()), cx);
21994
21995    // Editing should trigger diagnostics
21996    editor.update_in(cx, |editor, window, cx| {
21997        editor.handle_input("2", window, cx)
21998    });
21999    cx.executor().advance_clock(Duration::from_millis(60));
22000    cx.executor().run_until_parked();
22001    assert_eq!(
22002        diagnostic_requests.load(atomic::Ordering::Acquire),
22003        2,
22004        "Editing should trigger diagnostic request"
22005    );
22006    ensure_result_id(Some("2".to_string()), cx);
22007
22008    // Moving cursor should not trigger diagnostic request
22009    editor.update_in(cx, |editor, window, cx| {
22010        editor.change_selections(None, window, cx, |s| {
22011            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22012        });
22013    });
22014    cx.executor().advance_clock(Duration::from_millis(60));
22015    cx.executor().run_until_parked();
22016    assert_eq!(
22017        diagnostic_requests.load(atomic::Ordering::Acquire),
22018        2,
22019        "Cursor movement should not trigger diagnostic request"
22020    );
22021    ensure_result_id(Some("2".to_string()), cx);
22022    // Multiple rapid edits should be debounced
22023    for _ in 0..5 {
22024        editor.update_in(cx, |editor, window, cx| {
22025            editor.handle_input("x", window, cx)
22026        });
22027    }
22028    cx.executor().advance_clock(Duration::from_millis(60));
22029    cx.executor().run_until_parked();
22030
22031    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22032    assert!(
22033        final_requests <= 4,
22034        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22035    );
22036    ensure_result_id(Some(final_requests.to_string()), cx);
22037}
22038
22039#[gpui::test]
22040async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22041    // Regression test for issue #11671
22042    // Previously, adding a cursor after moving multiple cursors would reset
22043    // the cursor count instead of adding to the existing cursors.
22044    init_test(cx, |_| {});
22045    let mut cx = EditorTestContext::new(cx).await;
22046
22047    // Create a simple buffer with cursor at start
22048    cx.set_state(indoc! {"
22049        ˇaaaa
22050        bbbb
22051        cccc
22052        dddd
22053        eeee
22054        ffff
22055        gggg
22056        hhhh"});
22057
22058    // Add 2 cursors below (so we have 3 total)
22059    cx.update_editor(|editor, window, cx| {
22060        editor.add_selection_below(&Default::default(), window, cx);
22061        editor.add_selection_below(&Default::default(), window, cx);
22062    });
22063
22064    // Verify we have 3 cursors
22065    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22066    assert_eq!(
22067        initial_count, 3,
22068        "Should have 3 cursors after adding 2 below"
22069    );
22070
22071    // Move down one line
22072    cx.update_editor(|editor, window, cx| {
22073        editor.move_down(&MoveDown, window, cx);
22074    });
22075
22076    // Add another cursor below
22077    cx.update_editor(|editor, window, cx| {
22078        editor.add_selection_below(&Default::default(), window, cx);
22079    });
22080
22081    // Should now have 4 cursors (3 original + 1 new)
22082    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22083    assert_eq!(
22084        final_count, 4,
22085        "Should have 4 cursors after moving and adding another"
22086    );
22087}