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, no formatting request should be sent
 9089    {
 9090        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9091
 9092        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 9093            panic!("Should not be invoked on non-dirty buffer");
 9094        });
 9095        let save = editor
 9096            .update_in(cx, |editor, window, cx| {
 9097                editor.save(true, project.clone(), window, cx)
 9098            })
 9099            .unwrap();
 9100        cx.executor().start_waiting();
 9101        save.await;
 9102    }
 9103
 9104    // Set rust language override and assert overridden tabsize is sent to language server
 9105    update_test_language_settings(cx, |settings| {
 9106        settings.languages.insert(
 9107            "Rust".into(),
 9108            LanguageSettingsContent {
 9109                tab_size: NonZeroU32::new(8),
 9110                ..Default::default()
 9111            },
 9112        );
 9113    });
 9114
 9115    {
 9116        editor.update_in(cx, |editor, window, cx| {
 9117            editor.set_text("somehting_new\n", window, cx)
 9118        });
 9119        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9120        let _formatting_request_signal = fake_server
 9121            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9122                assert_eq!(
 9123                    params.text_document.uri,
 9124                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9125                );
 9126                assert_eq!(params.options.tab_size, 8);
 9127                Ok(Some(vec![]))
 9128            });
 9129        let save = editor
 9130            .update_in(cx, |editor, window, cx| {
 9131                editor.save(true, project.clone(), window, cx)
 9132            })
 9133            .unwrap();
 9134        cx.executor().start_waiting();
 9135        save.await;
 9136    }
 9137}
 9138
 9139#[gpui::test]
 9140async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9141    init_test(cx, |_| {});
 9142
 9143    let cols = 4;
 9144    let rows = 10;
 9145    let sample_text_1 = sample_text(rows, cols, 'a');
 9146    assert_eq!(
 9147        sample_text_1,
 9148        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9149    );
 9150    let sample_text_2 = sample_text(rows, cols, 'l');
 9151    assert_eq!(
 9152        sample_text_2,
 9153        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9154    );
 9155    let sample_text_3 = sample_text(rows, cols, 'v');
 9156    assert_eq!(
 9157        sample_text_3,
 9158        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9159    );
 9160
 9161    let fs = FakeFs::new(cx.executor());
 9162    fs.insert_tree(
 9163        path!("/a"),
 9164        json!({
 9165            "main.rs": sample_text_1,
 9166            "other.rs": sample_text_2,
 9167            "lib.rs": sample_text_3,
 9168        }),
 9169    )
 9170    .await;
 9171
 9172    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9173    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9174    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9175
 9176    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9177    language_registry.add(rust_lang());
 9178    let mut fake_servers = language_registry.register_fake_lsp(
 9179        "Rust",
 9180        FakeLspAdapter {
 9181            capabilities: lsp::ServerCapabilities {
 9182                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9183                ..Default::default()
 9184            },
 9185            ..Default::default()
 9186        },
 9187    );
 9188
 9189    let worktree = project.update(cx, |project, cx| {
 9190        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9191        assert_eq!(worktrees.len(), 1);
 9192        worktrees.pop().unwrap()
 9193    });
 9194    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9195
 9196    let buffer_1 = project
 9197        .update(cx, |project, cx| {
 9198            project.open_buffer((worktree_id, "main.rs"), cx)
 9199        })
 9200        .await
 9201        .unwrap();
 9202    let buffer_2 = project
 9203        .update(cx, |project, cx| {
 9204            project.open_buffer((worktree_id, "other.rs"), cx)
 9205        })
 9206        .await
 9207        .unwrap();
 9208    let buffer_3 = project
 9209        .update(cx, |project, cx| {
 9210            project.open_buffer((worktree_id, "lib.rs"), cx)
 9211        })
 9212        .await
 9213        .unwrap();
 9214
 9215    let multi_buffer = cx.new(|cx| {
 9216        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9217        multi_buffer.push_excerpts(
 9218            buffer_1.clone(),
 9219            [
 9220                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9221                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9222                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9223            ],
 9224            cx,
 9225        );
 9226        multi_buffer.push_excerpts(
 9227            buffer_2.clone(),
 9228            [
 9229                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9230                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9231                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9232            ],
 9233            cx,
 9234        );
 9235        multi_buffer.push_excerpts(
 9236            buffer_3.clone(),
 9237            [
 9238                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9239                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9240                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9241            ],
 9242            cx,
 9243        );
 9244        multi_buffer
 9245    });
 9246    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9247        Editor::new(
 9248            EditorMode::full(),
 9249            multi_buffer,
 9250            Some(project.clone()),
 9251            window,
 9252            cx,
 9253        )
 9254    });
 9255
 9256    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9257        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9258            s.select_ranges(Some(1..2))
 9259        });
 9260        editor.insert("|one|two|three|", window, cx);
 9261    });
 9262    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9263    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9264        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9265            s.select_ranges(Some(60..70))
 9266        });
 9267        editor.insert("|four|five|six|", window, cx);
 9268    });
 9269    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9270
 9271    // First two buffers should be edited, but not the third one.
 9272    assert_eq!(
 9273        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9274        "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}",
 9275    );
 9276    buffer_1.update(cx, |buffer, _| {
 9277        assert!(buffer.is_dirty());
 9278        assert_eq!(
 9279            buffer.text(),
 9280            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9281        )
 9282    });
 9283    buffer_2.update(cx, |buffer, _| {
 9284        assert!(buffer.is_dirty());
 9285        assert_eq!(
 9286            buffer.text(),
 9287            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9288        )
 9289    });
 9290    buffer_3.update(cx, |buffer, _| {
 9291        assert!(!buffer.is_dirty());
 9292        assert_eq!(buffer.text(), sample_text_3,)
 9293    });
 9294    cx.executor().run_until_parked();
 9295
 9296    cx.executor().start_waiting();
 9297    let save = multi_buffer_editor
 9298        .update_in(cx, |editor, window, cx| {
 9299            editor.save(true, project.clone(), window, cx)
 9300        })
 9301        .unwrap();
 9302
 9303    let fake_server = fake_servers.next().await.unwrap();
 9304    fake_server
 9305        .server
 9306        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9307            Ok(Some(vec![lsp::TextEdit::new(
 9308                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9309                format!("[{} formatted]", params.text_document.uri),
 9310            )]))
 9311        })
 9312        .detach();
 9313    save.await;
 9314
 9315    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9316    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9317    assert_eq!(
 9318        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9319        uri!(
 9320            "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}"
 9321        ),
 9322    );
 9323    buffer_1.update(cx, |buffer, _| {
 9324        assert!(!buffer.is_dirty());
 9325        assert_eq!(
 9326            buffer.text(),
 9327            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9328        )
 9329    });
 9330    buffer_2.update(cx, |buffer, _| {
 9331        assert!(!buffer.is_dirty());
 9332        assert_eq!(
 9333            buffer.text(),
 9334            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9335        )
 9336    });
 9337    buffer_3.update(cx, |buffer, _| {
 9338        assert!(!buffer.is_dirty());
 9339        assert_eq!(buffer.text(), sample_text_3,)
 9340    });
 9341}
 9342
 9343#[gpui::test]
 9344async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9345    init_test(cx, |_| {});
 9346
 9347    let fs = FakeFs::new(cx.executor());
 9348    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9349
 9350    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9351
 9352    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9353    language_registry.add(rust_lang());
 9354    let mut fake_servers = language_registry.register_fake_lsp(
 9355        "Rust",
 9356        FakeLspAdapter {
 9357            capabilities: lsp::ServerCapabilities {
 9358                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9359                ..Default::default()
 9360            },
 9361            ..Default::default()
 9362        },
 9363    );
 9364
 9365    let buffer = project
 9366        .update(cx, |project, cx| {
 9367            project.open_local_buffer(path!("/file.rs"), cx)
 9368        })
 9369        .await
 9370        .unwrap();
 9371
 9372    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9373    let (editor, cx) = cx.add_window_view(|window, cx| {
 9374        build_editor_with_project(project.clone(), buffer, window, cx)
 9375    });
 9376    editor.update_in(cx, |editor, window, cx| {
 9377        editor.set_text("one\ntwo\nthree\n", window, cx)
 9378    });
 9379    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9380
 9381    cx.executor().start_waiting();
 9382    let fake_server = fake_servers.next().await.unwrap();
 9383
 9384    let save = editor
 9385        .update_in(cx, |editor, window, cx| {
 9386            editor.save(true, project.clone(), window, cx)
 9387        })
 9388        .unwrap();
 9389    fake_server
 9390        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9391            assert_eq!(
 9392                params.text_document.uri,
 9393                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9394            );
 9395            assert_eq!(params.options.tab_size, 4);
 9396            Ok(Some(vec![lsp::TextEdit::new(
 9397                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9398                ", ".to_string(),
 9399            )]))
 9400        })
 9401        .next()
 9402        .await;
 9403    cx.executor().start_waiting();
 9404    save.await;
 9405    assert_eq!(
 9406        editor.update(cx, |editor, cx| editor.text(cx)),
 9407        "one, two\nthree\n"
 9408    );
 9409    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9410
 9411    editor.update_in(cx, |editor, window, cx| {
 9412        editor.set_text("one\ntwo\nthree\n", window, cx)
 9413    });
 9414    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9415
 9416    // Ensure we can still save even if formatting hangs.
 9417    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9418        move |params, _| async move {
 9419            assert_eq!(
 9420                params.text_document.uri,
 9421                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9422            );
 9423            futures::future::pending::<()>().await;
 9424            unreachable!()
 9425        },
 9426    );
 9427    let save = editor
 9428        .update_in(cx, |editor, window, cx| {
 9429            editor.save(true, project.clone(), window, cx)
 9430        })
 9431        .unwrap();
 9432    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9433    cx.executor().start_waiting();
 9434    save.await;
 9435    assert_eq!(
 9436        editor.update(cx, |editor, cx| editor.text(cx)),
 9437        "one\ntwo\nthree\n"
 9438    );
 9439    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9440
 9441    // For non-dirty buffer, no formatting request should be sent
 9442    let save = editor
 9443        .update_in(cx, |editor, window, cx| {
 9444            editor.save(true, project.clone(), window, cx)
 9445        })
 9446        .unwrap();
 9447    let _pending_format_request = fake_server
 9448        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9449            panic!("Should not be invoked on non-dirty buffer");
 9450        })
 9451        .next();
 9452    cx.executor().start_waiting();
 9453    save.await;
 9454
 9455    // Set Rust language override and assert overridden tabsize is sent to language server
 9456    update_test_language_settings(cx, |settings| {
 9457        settings.languages.insert(
 9458            "Rust".into(),
 9459            LanguageSettingsContent {
 9460                tab_size: NonZeroU32::new(8),
 9461                ..Default::default()
 9462            },
 9463        );
 9464    });
 9465
 9466    editor.update_in(cx, |editor, window, cx| {
 9467        editor.set_text("somehting_new\n", window, cx)
 9468    });
 9469    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9470    let save = editor
 9471        .update_in(cx, |editor, window, cx| {
 9472            editor.save(true, project.clone(), window, cx)
 9473        })
 9474        .unwrap();
 9475    fake_server
 9476        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9477            assert_eq!(
 9478                params.text_document.uri,
 9479                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9480            );
 9481            assert_eq!(params.options.tab_size, 8);
 9482            Ok(Some(Vec::new()))
 9483        })
 9484        .next()
 9485        .await;
 9486    save.await;
 9487}
 9488
 9489#[gpui::test]
 9490async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9491    init_test(cx, |settings| {
 9492        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9493            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9494        ))
 9495    });
 9496
 9497    let fs = FakeFs::new(cx.executor());
 9498    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9499
 9500    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9501
 9502    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9503    language_registry.add(Arc::new(Language::new(
 9504        LanguageConfig {
 9505            name: "Rust".into(),
 9506            matcher: LanguageMatcher {
 9507                path_suffixes: vec!["rs".to_string()],
 9508                ..Default::default()
 9509            },
 9510            ..LanguageConfig::default()
 9511        },
 9512        Some(tree_sitter_rust::LANGUAGE.into()),
 9513    )));
 9514    update_test_language_settings(cx, |settings| {
 9515        // Enable Prettier formatting for the same buffer, and ensure
 9516        // LSP is called instead of Prettier.
 9517        settings.defaults.prettier = Some(PrettierSettings {
 9518            allowed: true,
 9519            ..PrettierSettings::default()
 9520        });
 9521    });
 9522    let mut fake_servers = language_registry.register_fake_lsp(
 9523        "Rust",
 9524        FakeLspAdapter {
 9525            capabilities: lsp::ServerCapabilities {
 9526                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9527                ..Default::default()
 9528            },
 9529            ..Default::default()
 9530        },
 9531    );
 9532
 9533    let buffer = project
 9534        .update(cx, |project, cx| {
 9535            project.open_local_buffer(path!("/file.rs"), cx)
 9536        })
 9537        .await
 9538        .unwrap();
 9539
 9540    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9541    let (editor, cx) = cx.add_window_view(|window, cx| {
 9542        build_editor_with_project(project.clone(), buffer, window, cx)
 9543    });
 9544    editor.update_in(cx, |editor, window, cx| {
 9545        editor.set_text("one\ntwo\nthree\n", window, cx)
 9546    });
 9547
 9548    cx.executor().start_waiting();
 9549    let fake_server = fake_servers.next().await.unwrap();
 9550
 9551    let format = editor
 9552        .update_in(cx, |editor, window, cx| {
 9553            editor.perform_format(
 9554                project.clone(),
 9555                FormatTrigger::Manual,
 9556                FormatTarget::Buffers,
 9557                window,
 9558                cx,
 9559            )
 9560        })
 9561        .unwrap();
 9562    fake_server
 9563        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9564            assert_eq!(
 9565                params.text_document.uri,
 9566                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9567            );
 9568            assert_eq!(params.options.tab_size, 4);
 9569            Ok(Some(vec![lsp::TextEdit::new(
 9570                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9571                ", ".to_string(),
 9572            )]))
 9573        })
 9574        .next()
 9575        .await;
 9576    cx.executor().start_waiting();
 9577    format.await;
 9578    assert_eq!(
 9579        editor.update(cx, |editor, cx| editor.text(cx)),
 9580        "one, two\nthree\n"
 9581    );
 9582
 9583    editor.update_in(cx, |editor, window, cx| {
 9584        editor.set_text("one\ntwo\nthree\n", window, cx)
 9585    });
 9586    // Ensure we don't lock if formatting hangs.
 9587    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9588        move |params, _| async move {
 9589            assert_eq!(
 9590                params.text_document.uri,
 9591                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9592            );
 9593            futures::future::pending::<()>().await;
 9594            unreachable!()
 9595        },
 9596    );
 9597    let format = editor
 9598        .update_in(cx, |editor, window, cx| {
 9599            editor.perform_format(
 9600                project,
 9601                FormatTrigger::Manual,
 9602                FormatTarget::Buffers,
 9603                window,
 9604                cx,
 9605            )
 9606        })
 9607        .unwrap();
 9608    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9609    cx.executor().start_waiting();
 9610    format.await;
 9611    assert_eq!(
 9612        editor.update(cx, |editor, cx| editor.text(cx)),
 9613        "one\ntwo\nthree\n"
 9614    );
 9615}
 9616
 9617#[gpui::test]
 9618async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9619    init_test(cx, |settings| {
 9620        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9621        settings.defaults.formatter =
 9622            Some(language_settings::SelectedFormatter::List(FormatterList(
 9623                vec![
 9624                    Formatter::LanguageServer { name: None },
 9625                    Formatter::CodeActions(
 9626                        [
 9627                            ("code-action-1".into(), true),
 9628                            ("code-action-2".into(), true),
 9629                        ]
 9630                        .into_iter()
 9631                        .collect(),
 9632                    ),
 9633                ]
 9634                .into(),
 9635            )))
 9636    });
 9637
 9638    let fs = FakeFs::new(cx.executor());
 9639    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9640        .await;
 9641
 9642    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9643    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9644    language_registry.add(rust_lang());
 9645
 9646    let mut fake_servers = language_registry.register_fake_lsp(
 9647        "Rust",
 9648        FakeLspAdapter {
 9649            capabilities: lsp::ServerCapabilities {
 9650                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9651                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9652                    commands: vec!["the-command-for-code-action-1".into()],
 9653                    ..Default::default()
 9654                }),
 9655                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9656                ..Default::default()
 9657            },
 9658            ..Default::default()
 9659        },
 9660    );
 9661
 9662    let buffer = project
 9663        .update(cx, |project, cx| {
 9664            project.open_local_buffer(path!("/file.rs"), cx)
 9665        })
 9666        .await
 9667        .unwrap();
 9668
 9669    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9670    let (editor, cx) = cx.add_window_view(|window, cx| {
 9671        build_editor_with_project(project.clone(), buffer, window, cx)
 9672    });
 9673
 9674    cx.executor().start_waiting();
 9675
 9676    let fake_server = fake_servers.next().await.unwrap();
 9677    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9678        move |_params, _| async move {
 9679            Ok(Some(vec![lsp::TextEdit::new(
 9680                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9681                "applied-formatting\n".to_string(),
 9682            )]))
 9683        },
 9684    );
 9685    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9686        move |params, _| async move {
 9687            assert_eq!(
 9688                params.context.only,
 9689                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9690            );
 9691            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9692            Ok(Some(vec![
 9693                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9694                    kind: Some("code-action-1".into()),
 9695                    edit: Some(lsp::WorkspaceEdit::new(
 9696                        [(
 9697                            uri.clone(),
 9698                            vec![lsp::TextEdit::new(
 9699                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9700                                "applied-code-action-1-edit\n".to_string(),
 9701                            )],
 9702                        )]
 9703                        .into_iter()
 9704                        .collect(),
 9705                    )),
 9706                    command: Some(lsp::Command {
 9707                        command: "the-command-for-code-action-1".into(),
 9708                        ..Default::default()
 9709                    }),
 9710                    ..Default::default()
 9711                }),
 9712                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9713                    kind: Some("code-action-2".into()),
 9714                    edit: Some(lsp::WorkspaceEdit::new(
 9715                        [(
 9716                            uri.clone(),
 9717                            vec![lsp::TextEdit::new(
 9718                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9719                                "applied-code-action-2-edit\n".to_string(),
 9720                            )],
 9721                        )]
 9722                        .into_iter()
 9723                        .collect(),
 9724                    )),
 9725                    ..Default::default()
 9726                }),
 9727            ]))
 9728        },
 9729    );
 9730
 9731    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9732        move |params, _| async move { Ok(params) }
 9733    });
 9734
 9735    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9736    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9737        let fake = fake_server.clone();
 9738        let lock = command_lock.clone();
 9739        move |params, _| {
 9740            assert_eq!(params.command, "the-command-for-code-action-1");
 9741            let fake = fake.clone();
 9742            let lock = lock.clone();
 9743            async move {
 9744                lock.lock().await;
 9745                fake.server
 9746                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9747                        label: None,
 9748                        edit: lsp::WorkspaceEdit {
 9749                            changes: Some(
 9750                                [(
 9751                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9752                                    vec![lsp::TextEdit {
 9753                                        range: lsp::Range::new(
 9754                                            lsp::Position::new(0, 0),
 9755                                            lsp::Position::new(0, 0),
 9756                                        ),
 9757                                        new_text: "applied-code-action-1-command\n".into(),
 9758                                    }],
 9759                                )]
 9760                                .into_iter()
 9761                                .collect(),
 9762                            ),
 9763                            ..Default::default()
 9764                        },
 9765                    })
 9766                    .await
 9767                    .into_response()
 9768                    .unwrap();
 9769                Ok(Some(json!(null)))
 9770            }
 9771        }
 9772    });
 9773
 9774    cx.executor().start_waiting();
 9775    editor
 9776        .update_in(cx, |editor, window, cx| {
 9777            editor.perform_format(
 9778                project.clone(),
 9779                FormatTrigger::Manual,
 9780                FormatTarget::Buffers,
 9781                window,
 9782                cx,
 9783            )
 9784        })
 9785        .unwrap()
 9786        .await;
 9787    editor.update(cx, |editor, cx| {
 9788        assert_eq!(
 9789            editor.text(cx),
 9790            r#"
 9791                applied-code-action-2-edit
 9792                applied-code-action-1-command
 9793                applied-code-action-1-edit
 9794                applied-formatting
 9795                one
 9796                two
 9797                three
 9798            "#
 9799            .unindent()
 9800        );
 9801    });
 9802
 9803    editor.update_in(cx, |editor, window, cx| {
 9804        editor.undo(&Default::default(), window, cx);
 9805        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9806    });
 9807
 9808    // Perform a manual edit while waiting for an LSP command
 9809    // that's being run as part of a formatting code action.
 9810    let lock_guard = command_lock.lock().await;
 9811    let format = editor
 9812        .update_in(cx, |editor, window, cx| {
 9813            editor.perform_format(
 9814                project.clone(),
 9815                FormatTrigger::Manual,
 9816                FormatTarget::Buffers,
 9817                window,
 9818                cx,
 9819            )
 9820        })
 9821        .unwrap();
 9822    cx.run_until_parked();
 9823    editor.update(cx, |editor, cx| {
 9824        assert_eq!(
 9825            editor.text(cx),
 9826            r#"
 9827                applied-code-action-1-edit
 9828                applied-formatting
 9829                one
 9830                two
 9831                three
 9832            "#
 9833            .unindent()
 9834        );
 9835
 9836        editor.buffer.update(cx, |buffer, cx| {
 9837            let ix = buffer.len(cx);
 9838            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9839        });
 9840    });
 9841
 9842    // Allow the LSP command to proceed. Because the buffer was edited,
 9843    // the second code action will not be run.
 9844    drop(lock_guard);
 9845    format.await;
 9846    editor.update_in(cx, |editor, window, cx| {
 9847        assert_eq!(
 9848            editor.text(cx),
 9849            r#"
 9850                applied-code-action-1-command
 9851                applied-code-action-1-edit
 9852                applied-formatting
 9853                one
 9854                two
 9855                three
 9856                edited
 9857            "#
 9858            .unindent()
 9859        );
 9860
 9861        // The manual edit is undone first, because it is the last thing the user did
 9862        // (even though the command completed afterwards).
 9863        editor.undo(&Default::default(), window, cx);
 9864        assert_eq!(
 9865            editor.text(cx),
 9866            r#"
 9867                applied-code-action-1-command
 9868                applied-code-action-1-edit
 9869                applied-formatting
 9870                one
 9871                two
 9872                three
 9873            "#
 9874            .unindent()
 9875        );
 9876
 9877        // All the formatting (including the command, which completed after the manual edit)
 9878        // is undone together.
 9879        editor.undo(&Default::default(), window, cx);
 9880        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9881    });
 9882}
 9883
 9884#[gpui::test]
 9885async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9886    init_test(cx, |settings| {
 9887        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9888            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9889        ))
 9890    });
 9891
 9892    let fs = FakeFs::new(cx.executor());
 9893    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9894
 9895    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9896
 9897    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9898    language_registry.add(Arc::new(Language::new(
 9899        LanguageConfig {
 9900            name: "TypeScript".into(),
 9901            matcher: LanguageMatcher {
 9902                path_suffixes: vec!["ts".to_string()],
 9903                ..Default::default()
 9904            },
 9905            ..LanguageConfig::default()
 9906        },
 9907        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9908    )));
 9909    update_test_language_settings(cx, |settings| {
 9910        settings.defaults.prettier = Some(PrettierSettings {
 9911            allowed: true,
 9912            ..PrettierSettings::default()
 9913        });
 9914    });
 9915    let mut fake_servers = language_registry.register_fake_lsp(
 9916        "TypeScript",
 9917        FakeLspAdapter {
 9918            capabilities: lsp::ServerCapabilities {
 9919                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9920                ..Default::default()
 9921            },
 9922            ..Default::default()
 9923        },
 9924    );
 9925
 9926    let buffer = project
 9927        .update(cx, |project, cx| {
 9928            project.open_local_buffer(path!("/file.ts"), cx)
 9929        })
 9930        .await
 9931        .unwrap();
 9932
 9933    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9934    let (editor, cx) = cx.add_window_view(|window, cx| {
 9935        build_editor_with_project(project.clone(), buffer, window, cx)
 9936    });
 9937    editor.update_in(cx, |editor, window, cx| {
 9938        editor.set_text(
 9939            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9940            window,
 9941            cx,
 9942        )
 9943    });
 9944
 9945    cx.executor().start_waiting();
 9946    let fake_server = fake_servers.next().await.unwrap();
 9947
 9948    let format = editor
 9949        .update_in(cx, |editor, window, cx| {
 9950            editor.perform_code_action_kind(
 9951                project.clone(),
 9952                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9953                window,
 9954                cx,
 9955            )
 9956        })
 9957        .unwrap();
 9958    fake_server
 9959        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9960            assert_eq!(
 9961                params.text_document.uri,
 9962                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9963            );
 9964            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9965                lsp::CodeAction {
 9966                    title: "Organize Imports".to_string(),
 9967                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9968                    edit: Some(lsp::WorkspaceEdit {
 9969                        changes: Some(
 9970                            [(
 9971                                params.text_document.uri.clone(),
 9972                                vec![lsp::TextEdit::new(
 9973                                    lsp::Range::new(
 9974                                        lsp::Position::new(1, 0),
 9975                                        lsp::Position::new(2, 0),
 9976                                    ),
 9977                                    "".to_string(),
 9978                                )],
 9979                            )]
 9980                            .into_iter()
 9981                            .collect(),
 9982                        ),
 9983                        ..Default::default()
 9984                    }),
 9985                    ..Default::default()
 9986                },
 9987            )]))
 9988        })
 9989        .next()
 9990        .await;
 9991    cx.executor().start_waiting();
 9992    format.await;
 9993    assert_eq!(
 9994        editor.update(cx, |editor, cx| editor.text(cx)),
 9995        "import { a } from 'module';\n\nconst x = a;\n"
 9996    );
 9997
 9998    editor.update_in(cx, |editor, window, cx| {
 9999        editor.set_text(
10000            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10001            window,
10002            cx,
10003        )
10004    });
10005    // Ensure we don't lock if code action hangs.
10006    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10007        move |params, _| async move {
10008            assert_eq!(
10009                params.text_document.uri,
10010                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10011            );
10012            futures::future::pending::<()>().await;
10013            unreachable!()
10014        },
10015    );
10016    let format = editor
10017        .update_in(cx, |editor, window, cx| {
10018            editor.perform_code_action_kind(
10019                project,
10020                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10021                window,
10022                cx,
10023            )
10024        })
10025        .unwrap();
10026    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10027    cx.executor().start_waiting();
10028    format.await;
10029    assert_eq!(
10030        editor.update(cx, |editor, cx| editor.text(cx)),
10031        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10032    );
10033}
10034
10035#[gpui::test]
10036async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10037    init_test(cx, |_| {});
10038
10039    let mut cx = EditorLspTestContext::new_rust(
10040        lsp::ServerCapabilities {
10041            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10042            ..Default::default()
10043        },
10044        cx,
10045    )
10046    .await;
10047
10048    cx.set_state(indoc! {"
10049        one.twoˇ
10050    "});
10051
10052    // The format request takes a long time. When it completes, it inserts
10053    // a newline and an indent before the `.`
10054    cx.lsp
10055        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10056            let executor = cx.background_executor().clone();
10057            async move {
10058                executor.timer(Duration::from_millis(100)).await;
10059                Ok(Some(vec![lsp::TextEdit {
10060                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10061                    new_text: "\n    ".into(),
10062                }]))
10063            }
10064        });
10065
10066    // Submit a format request.
10067    let format_1 = cx
10068        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10069        .unwrap();
10070    cx.executor().run_until_parked();
10071
10072    // Submit a second format request.
10073    let format_2 = cx
10074        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10075        .unwrap();
10076    cx.executor().run_until_parked();
10077
10078    // Wait for both format requests to complete
10079    cx.executor().advance_clock(Duration::from_millis(200));
10080    cx.executor().start_waiting();
10081    format_1.await.unwrap();
10082    cx.executor().start_waiting();
10083    format_2.await.unwrap();
10084
10085    // The formatting edits only happens once.
10086    cx.assert_editor_state(indoc! {"
10087        one
10088            .twoˇ
10089    "});
10090}
10091
10092#[gpui::test]
10093async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10094    init_test(cx, |settings| {
10095        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10096    });
10097
10098    let mut cx = EditorLspTestContext::new_rust(
10099        lsp::ServerCapabilities {
10100            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10101            ..Default::default()
10102        },
10103        cx,
10104    )
10105    .await;
10106
10107    // Set up a buffer white some trailing whitespace and no trailing newline.
10108    cx.set_state(
10109        &[
10110            "one ",   //
10111            "twoˇ",   //
10112            "three ", //
10113            "four",   //
10114        ]
10115        .join("\n"),
10116    );
10117
10118    // Submit a format request.
10119    let format = cx
10120        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10121        .unwrap();
10122
10123    // Record which buffer changes have been sent to the language server
10124    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10125    cx.lsp
10126        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10127            let buffer_changes = buffer_changes.clone();
10128            move |params, _| {
10129                buffer_changes.lock().extend(
10130                    params
10131                        .content_changes
10132                        .into_iter()
10133                        .map(|e| (e.range.unwrap(), e.text)),
10134                );
10135            }
10136        });
10137
10138    // Handle formatting requests to the language server.
10139    cx.lsp
10140        .set_request_handler::<lsp::request::Formatting, _, _>({
10141            let buffer_changes = buffer_changes.clone();
10142            move |_, _| {
10143                // When formatting is requested, trailing whitespace has already been stripped,
10144                // and the trailing newline has already been added.
10145                assert_eq!(
10146                    &buffer_changes.lock()[1..],
10147                    &[
10148                        (
10149                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10150                            "".into()
10151                        ),
10152                        (
10153                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10154                            "".into()
10155                        ),
10156                        (
10157                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10158                            "\n".into()
10159                        ),
10160                    ]
10161                );
10162
10163                // Insert blank lines between each line of the buffer.
10164                async move {
10165                    Ok(Some(vec![
10166                        lsp::TextEdit {
10167                            range: lsp::Range::new(
10168                                lsp::Position::new(1, 0),
10169                                lsp::Position::new(1, 0),
10170                            ),
10171                            new_text: "\n".into(),
10172                        },
10173                        lsp::TextEdit {
10174                            range: lsp::Range::new(
10175                                lsp::Position::new(2, 0),
10176                                lsp::Position::new(2, 0),
10177                            ),
10178                            new_text: "\n".into(),
10179                        },
10180                    ]))
10181                }
10182            }
10183        });
10184
10185    // After formatting the buffer, the trailing whitespace is stripped,
10186    // a newline is appended, and the edits provided by the language server
10187    // have been applied.
10188    format.await.unwrap();
10189    cx.assert_editor_state(
10190        &[
10191            "one",   //
10192            "",      //
10193            "twoˇ",  //
10194            "",      //
10195            "three", //
10196            "four",  //
10197            "",      //
10198        ]
10199        .join("\n"),
10200    );
10201
10202    // Undoing the formatting undoes the trailing whitespace removal, the
10203    // trailing newline, and the LSP edits.
10204    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10205    cx.assert_editor_state(
10206        &[
10207            "one ",   //
10208            "twoˇ",   //
10209            "three ", //
10210            "four",   //
10211        ]
10212        .join("\n"),
10213    );
10214}
10215
10216#[gpui::test]
10217async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10218    cx: &mut TestAppContext,
10219) {
10220    init_test(cx, |_| {});
10221
10222    cx.update(|cx| {
10223        cx.update_global::<SettingsStore, _>(|settings, cx| {
10224            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10225                settings.auto_signature_help = Some(true);
10226            });
10227        });
10228    });
10229
10230    let mut cx = EditorLspTestContext::new_rust(
10231        lsp::ServerCapabilities {
10232            signature_help_provider: Some(lsp::SignatureHelpOptions {
10233                ..Default::default()
10234            }),
10235            ..Default::default()
10236        },
10237        cx,
10238    )
10239    .await;
10240
10241    let language = Language::new(
10242        LanguageConfig {
10243            name: "Rust".into(),
10244            brackets: BracketPairConfig {
10245                pairs: vec![
10246                    BracketPair {
10247                        start: "{".to_string(),
10248                        end: "}".to_string(),
10249                        close: true,
10250                        surround: true,
10251                        newline: true,
10252                    },
10253                    BracketPair {
10254                        start: "(".to_string(),
10255                        end: ")".to_string(),
10256                        close: true,
10257                        surround: true,
10258                        newline: true,
10259                    },
10260                    BracketPair {
10261                        start: "/*".to_string(),
10262                        end: " */".to_string(),
10263                        close: true,
10264                        surround: true,
10265                        newline: true,
10266                    },
10267                    BracketPair {
10268                        start: "[".to_string(),
10269                        end: "]".to_string(),
10270                        close: false,
10271                        surround: false,
10272                        newline: true,
10273                    },
10274                    BracketPair {
10275                        start: "\"".to_string(),
10276                        end: "\"".to_string(),
10277                        close: true,
10278                        surround: true,
10279                        newline: false,
10280                    },
10281                    BracketPair {
10282                        start: "<".to_string(),
10283                        end: ">".to_string(),
10284                        close: false,
10285                        surround: true,
10286                        newline: true,
10287                    },
10288                ],
10289                ..Default::default()
10290            },
10291            autoclose_before: "})]".to_string(),
10292            ..Default::default()
10293        },
10294        Some(tree_sitter_rust::LANGUAGE.into()),
10295    );
10296    let language = Arc::new(language);
10297
10298    cx.language_registry().add(language.clone());
10299    cx.update_buffer(|buffer, cx| {
10300        buffer.set_language(Some(language), cx);
10301    });
10302
10303    cx.set_state(
10304        &r#"
10305            fn main() {
10306                sampleˇ
10307            }
10308        "#
10309        .unindent(),
10310    );
10311
10312    cx.update_editor(|editor, window, cx| {
10313        editor.handle_input("(", window, cx);
10314    });
10315    cx.assert_editor_state(
10316        &"
10317            fn main() {
10318                sample(ˇ)
10319            }
10320        "
10321        .unindent(),
10322    );
10323
10324    let mocked_response = lsp::SignatureHelp {
10325        signatures: vec![lsp::SignatureInformation {
10326            label: "fn sample(param1: u8, param2: u8)".to_string(),
10327            documentation: None,
10328            parameters: Some(vec![
10329                lsp::ParameterInformation {
10330                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10331                    documentation: None,
10332                },
10333                lsp::ParameterInformation {
10334                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10335                    documentation: None,
10336                },
10337            ]),
10338            active_parameter: None,
10339        }],
10340        active_signature: Some(0),
10341        active_parameter: Some(0),
10342    };
10343    handle_signature_help_request(&mut cx, mocked_response).await;
10344
10345    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10346        .await;
10347
10348    cx.editor(|editor, _, _| {
10349        let signature_help_state = editor.signature_help_state.popover().cloned();
10350        assert_eq!(
10351            signature_help_state.unwrap().label,
10352            "param1: u8, param2: u8"
10353        );
10354    });
10355}
10356
10357#[gpui::test]
10358async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10359    init_test(cx, |_| {});
10360
10361    cx.update(|cx| {
10362        cx.update_global::<SettingsStore, _>(|settings, cx| {
10363            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10364                settings.auto_signature_help = Some(false);
10365                settings.show_signature_help_after_edits = Some(false);
10366            });
10367        });
10368    });
10369
10370    let mut cx = EditorLspTestContext::new_rust(
10371        lsp::ServerCapabilities {
10372            signature_help_provider: Some(lsp::SignatureHelpOptions {
10373                ..Default::default()
10374            }),
10375            ..Default::default()
10376        },
10377        cx,
10378    )
10379    .await;
10380
10381    let language = Language::new(
10382        LanguageConfig {
10383            name: "Rust".into(),
10384            brackets: BracketPairConfig {
10385                pairs: vec![
10386                    BracketPair {
10387                        start: "{".to_string(),
10388                        end: "}".to_string(),
10389                        close: true,
10390                        surround: true,
10391                        newline: true,
10392                    },
10393                    BracketPair {
10394                        start: "(".to_string(),
10395                        end: ")".to_string(),
10396                        close: true,
10397                        surround: true,
10398                        newline: true,
10399                    },
10400                    BracketPair {
10401                        start: "/*".to_string(),
10402                        end: " */".to_string(),
10403                        close: true,
10404                        surround: true,
10405                        newline: true,
10406                    },
10407                    BracketPair {
10408                        start: "[".to_string(),
10409                        end: "]".to_string(),
10410                        close: false,
10411                        surround: false,
10412                        newline: true,
10413                    },
10414                    BracketPair {
10415                        start: "\"".to_string(),
10416                        end: "\"".to_string(),
10417                        close: true,
10418                        surround: true,
10419                        newline: false,
10420                    },
10421                    BracketPair {
10422                        start: "<".to_string(),
10423                        end: ">".to_string(),
10424                        close: false,
10425                        surround: true,
10426                        newline: true,
10427                    },
10428                ],
10429                ..Default::default()
10430            },
10431            autoclose_before: "})]".to_string(),
10432            ..Default::default()
10433        },
10434        Some(tree_sitter_rust::LANGUAGE.into()),
10435    );
10436    let language = Arc::new(language);
10437
10438    cx.language_registry().add(language.clone());
10439    cx.update_buffer(|buffer, cx| {
10440        buffer.set_language(Some(language), cx);
10441    });
10442
10443    // Ensure that signature_help is not called when no signature help is enabled.
10444    cx.set_state(
10445        &r#"
10446            fn main() {
10447                sampleˇ
10448            }
10449        "#
10450        .unindent(),
10451    );
10452    cx.update_editor(|editor, window, cx| {
10453        editor.handle_input("(", window, cx);
10454    });
10455    cx.assert_editor_state(
10456        &"
10457            fn main() {
10458                sample(ˇ)
10459            }
10460        "
10461        .unindent(),
10462    );
10463    cx.editor(|editor, _, _| {
10464        assert!(editor.signature_help_state.task().is_none());
10465    });
10466
10467    let mocked_response = lsp::SignatureHelp {
10468        signatures: vec![lsp::SignatureInformation {
10469            label: "fn sample(param1: u8, param2: u8)".to_string(),
10470            documentation: None,
10471            parameters: Some(vec![
10472                lsp::ParameterInformation {
10473                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10474                    documentation: None,
10475                },
10476                lsp::ParameterInformation {
10477                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10478                    documentation: None,
10479                },
10480            ]),
10481            active_parameter: None,
10482        }],
10483        active_signature: Some(0),
10484        active_parameter: Some(0),
10485    };
10486
10487    // Ensure that signature_help is called when enabled afte edits
10488    cx.update(|_, cx| {
10489        cx.update_global::<SettingsStore, _>(|settings, cx| {
10490            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10491                settings.auto_signature_help = Some(false);
10492                settings.show_signature_help_after_edits = Some(true);
10493            });
10494        });
10495    });
10496    cx.set_state(
10497        &r#"
10498            fn main() {
10499                sampleˇ
10500            }
10501        "#
10502        .unindent(),
10503    );
10504    cx.update_editor(|editor, window, cx| {
10505        editor.handle_input("(", window, cx);
10506    });
10507    cx.assert_editor_state(
10508        &"
10509            fn main() {
10510                sample(ˇ)
10511            }
10512        "
10513        .unindent(),
10514    );
10515    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10516    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10517        .await;
10518    cx.update_editor(|editor, _, _| {
10519        let signature_help_state = editor.signature_help_state.popover().cloned();
10520        assert!(signature_help_state.is_some());
10521        assert_eq!(
10522            signature_help_state.unwrap().label,
10523            "param1: u8, param2: u8"
10524        );
10525        editor.signature_help_state = SignatureHelpState::default();
10526    });
10527
10528    // Ensure that signature_help is called when auto signature help override is enabled
10529    cx.update(|_, cx| {
10530        cx.update_global::<SettingsStore, _>(|settings, cx| {
10531            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10532                settings.auto_signature_help = Some(true);
10533                settings.show_signature_help_after_edits = Some(false);
10534            });
10535        });
10536    });
10537    cx.set_state(
10538        &r#"
10539            fn main() {
10540                sampleˇ
10541            }
10542        "#
10543        .unindent(),
10544    );
10545    cx.update_editor(|editor, window, cx| {
10546        editor.handle_input("(", window, cx);
10547    });
10548    cx.assert_editor_state(
10549        &"
10550            fn main() {
10551                sample(ˇ)
10552            }
10553        "
10554        .unindent(),
10555    );
10556    handle_signature_help_request(&mut cx, mocked_response).await;
10557    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10558        .await;
10559    cx.editor(|editor, _, _| {
10560        let signature_help_state = editor.signature_help_state.popover().cloned();
10561        assert!(signature_help_state.is_some());
10562        assert_eq!(
10563            signature_help_state.unwrap().label,
10564            "param1: u8, param2: u8"
10565        );
10566    });
10567}
10568
10569#[gpui::test]
10570async fn test_signature_help(cx: &mut TestAppContext) {
10571    init_test(cx, |_| {});
10572    cx.update(|cx| {
10573        cx.update_global::<SettingsStore, _>(|settings, cx| {
10574            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10575                settings.auto_signature_help = Some(true);
10576            });
10577        });
10578    });
10579
10580    let mut cx = EditorLspTestContext::new_rust(
10581        lsp::ServerCapabilities {
10582            signature_help_provider: Some(lsp::SignatureHelpOptions {
10583                ..Default::default()
10584            }),
10585            ..Default::default()
10586        },
10587        cx,
10588    )
10589    .await;
10590
10591    // A test that directly calls `show_signature_help`
10592    cx.update_editor(|editor, window, cx| {
10593        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10594    });
10595
10596    let mocked_response = lsp::SignatureHelp {
10597        signatures: vec![lsp::SignatureInformation {
10598            label: "fn sample(param1: u8, param2: u8)".to_string(),
10599            documentation: None,
10600            parameters: Some(vec![
10601                lsp::ParameterInformation {
10602                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10603                    documentation: None,
10604                },
10605                lsp::ParameterInformation {
10606                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10607                    documentation: None,
10608                },
10609            ]),
10610            active_parameter: None,
10611        }],
10612        active_signature: Some(0),
10613        active_parameter: Some(0),
10614    };
10615    handle_signature_help_request(&mut cx, mocked_response).await;
10616
10617    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10618        .await;
10619
10620    cx.editor(|editor, _, _| {
10621        let signature_help_state = editor.signature_help_state.popover().cloned();
10622        assert!(signature_help_state.is_some());
10623        assert_eq!(
10624            signature_help_state.unwrap().label,
10625            "param1: u8, param2: u8"
10626        );
10627    });
10628
10629    // When exiting outside from inside the brackets, `signature_help` is closed.
10630    cx.set_state(indoc! {"
10631        fn main() {
10632            sample(ˇ);
10633        }
10634
10635        fn sample(param1: u8, param2: u8) {}
10636    "});
10637
10638    cx.update_editor(|editor, window, cx| {
10639        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10640    });
10641
10642    let mocked_response = lsp::SignatureHelp {
10643        signatures: Vec::new(),
10644        active_signature: None,
10645        active_parameter: None,
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        assert!(!editor.signature_help_state.is_shown());
10654    });
10655
10656    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10657    cx.set_state(indoc! {"
10658        fn main() {
10659            sample(ˇ);
10660        }
10661
10662        fn sample(param1: u8, param2: u8) {}
10663    "});
10664
10665    let mocked_response = lsp::SignatureHelp {
10666        signatures: vec![lsp::SignatureInformation {
10667            label: "fn sample(param1: u8, param2: u8)".to_string(),
10668            documentation: None,
10669            parameters: Some(vec![
10670                lsp::ParameterInformation {
10671                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10672                    documentation: None,
10673                },
10674                lsp::ParameterInformation {
10675                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10676                    documentation: None,
10677                },
10678            ]),
10679            active_parameter: None,
10680        }],
10681        active_signature: Some(0),
10682        active_parameter: Some(0),
10683    };
10684    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10685    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10686        .await;
10687    cx.editor(|editor, _, _| {
10688        assert!(editor.signature_help_state.is_shown());
10689    });
10690
10691    // Restore the popover with more parameter input
10692    cx.set_state(indoc! {"
10693        fn main() {
10694            sample(param1, param2ˇ);
10695        }
10696
10697        fn sample(param1: u8, param2: u8) {}
10698    "});
10699
10700    let mocked_response = lsp::SignatureHelp {
10701        signatures: vec![lsp::SignatureInformation {
10702            label: "fn sample(param1: u8, param2: u8)".to_string(),
10703            documentation: None,
10704            parameters: Some(vec![
10705                lsp::ParameterInformation {
10706                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10707                    documentation: None,
10708                },
10709                lsp::ParameterInformation {
10710                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10711                    documentation: None,
10712                },
10713            ]),
10714            active_parameter: None,
10715        }],
10716        active_signature: Some(0),
10717        active_parameter: Some(1),
10718    };
10719    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10720    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10721        .await;
10722
10723    // When selecting a range, the popover is gone.
10724    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10725    cx.update_editor(|editor, window, cx| {
10726        editor.change_selections(None, window, cx, |s| {
10727            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10728        })
10729    });
10730    cx.assert_editor_state(indoc! {"
10731        fn main() {
10732            sample(param1, «ˇparam2»);
10733        }
10734
10735        fn sample(param1: u8, param2: u8) {}
10736    "});
10737    cx.editor(|editor, _, _| {
10738        assert!(!editor.signature_help_state.is_shown());
10739    });
10740
10741    // When unselecting again, the popover is back if within the brackets.
10742    cx.update_editor(|editor, window, cx| {
10743        editor.change_selections(None, window, cx, |s| {
10744            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10745        })
10746    });
10747    cx.assert_editor_state(indoc! {"
10748        fn main() {
10749            sample(param1, ˇparam2);
10750        }
10751
10752        fn sample(param1: u8, param2: u8) {}
10753    "});
10754    handle_signature_help_request(&mut cx, mocked_response).await;
10755    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10756        .await;
10757    cx.editor(|editor, _, _| {
10758        assert!(editor.signature_help_state.is_shown());
10759    });
10760
10761    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10762    cx.update_editor(|editor, window, cx| {
10763        editor.change_selections(None, window, cx, |s| {
10764            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10765            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10766        })
10767    });
10768    cx.assert_editor_state(indoc! {"
10769        fn main() {
10770            sample(param1, ˇparam2);
10771        }
10772
10773        fn sample(param1: u8, param2: u8) {}
10774    "});
10775
10776    let mocked_response = lsp::SignatureHelp {
10777        signatures: vec![lsp::SignatureInformation {
10778            label: "fn sample(param1: u8, param2: u8)".to_string(),
10779            documentation: None,
10780            parameters: Some(vec![
10781                lsp::ParameterInformation {
10782                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10783                    documentation: None,
10784                },
10785                lsp::ParameterInformation {
10786                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10787                    documentation: None,
10788                },
10789            ]),
10790            active_parameter: None,
10791        }],
10792        active_signature: Some(0),
10793        active_parameter: Some(1),
10794    };
10795    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10796    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10797        .await;
10798    cx.update_editor(|editor, _, cx| {
10799        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10800    });
10801    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10802        .await;
10803    cx.update_editor(|editor, window, cx| {
10804        editor.change_selections(None, window, cx, |s| {
10805            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10806        })
10807    });
10808    cx.assert_editor_state(indoc! {"
10809        fn main() {
10810            sample(param1, «ˇparam2»);
10811        }
10812
10813        fn sample(param1: u8, param2: u8) {}
10814    "});
10815    cx.update_editor(|editor, window, cx| {
10816        editor.change_selections(None, window, cx, |s| {
10817            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10818        })
10819    });
10820    cx.assert_editor_state(indoc! {"
10821        fn main() {
10822            sample(param1, ˇparam2);
10823        }
10824
10825        fn sample(param1: u8, param2: u8) {}
10826    "});
10827    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10828        .await;
10829}
10830
10831#[gpui::test]
10832async fn test_completion_mode(cx: &mut TestAppContext) {
10833    init_test(cx, |_| {});
10834    let mut cx = EditorLspTestContext::new_rust(
10835        lsp::ServerCapabilities {
10836            completion_provider: Some(lsp::CompletionOptions {
10837                resolve_provider: Some(true),
10838                ..Default::default()
10839            }),
10840            ..Default::default()
10841        },
10842        cx,
10843    )
10844    .await;
10845
10846    struct Run {
10847        run_description: &'static str,
10848        initial_state: String,
10849        buffer_marked_text: String,
10850        completion_label: &'static str,
10851        completion_text: &'static str,
10852        expected_with_insert_mode: String,
10853        expected_with_replace_mode: String,
10854        expected_with_replace_subsequence_mode: String,
10855        expected_with_replace_suffix_mode: String,
10856    }
10857
10858    let runs = [
10859        Run {
10860            run_description: "Start of word matches completion text",
10861            initial_state: "before ediˇ after".into(),
10862            buffer_marked_text: "before <edi|> after".into(),
10863            completion_label: "editor",
10864            completion_text: "editor",
10865            expected_with_insert_mode: "before editorˇ after".into(),
10866            expected_with_replace_mode: "before editorˇ after".into(),
10867            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10868            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10869        },
10870        Run {
10871            run_description: "Accept same text at the middle of the word",
10872            initial_state: "before ediˇtor after".into(),
10873            buffer_marked_text: "before <edi|tor> after".into(),
10874            completion_label: "editor",
10875            completion_text: "editor",
10876            expected_with_insert_mode: "before editorˇtor after".into(),
10877            expected_with_replace_mode: "before editorˇ after".into(),
10878            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10879            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10880        },
10881        Run {
10882            run_description: "End of word matches completion text -- cursor at end",
10883            initial_state: "before torˇ after".into(),
10884            buffer_marked_text: "before <tor|> after".into(),
10885            completion_label: "editor",
10886            completion_text: "editor",
10887            expected_with_insert_mode: "before editorˇ after".into(),
10888            expected_with_replace_mode: "before editorˇ after".into(),
10889            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10890            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10891        },
10892        Run {
10893            run_description: "End of word matches completion text -- cursor at start",
10894            initial_state: "before ˇtor after".into(),
10895            buffer_marked_text: "before <|tor> after".into(),
10896            completion_label: "editor",
10897            completion_text: "editor",
10898            expected_with_insert_mode: "before editorˇtor after".into(),
10899            expected_with_replace_mode: "before editorˇ after".into(),
10900            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10901            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10902        },
10903        Run {
10904            run_description: "Prepend text containing whitespace",
10905            initial_state: "pˇfield: bool".into(),
10906            buffer_marked_text: "<p|field>: bool".into(),
10907            completion_label: "pub ",
10908            completion_text: "pub ",
10909            expected_with_insert_mode: "pub ˇfield: bool".into(),
10910            expected_with_replace_mode: "pub ˇ: bool".into(),
10911            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10912            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10913        },
10914        Run {
10915            run_description: "Add element to start of list",
10916            initial_state: "[element_ˇelement_2]".into(),
10917            buffer_marked_text: "[<element_|element_2>]".into(),
10918            completion_label: "element_1",
10919            completion_text: "element_1",
10920            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10921            expected_with_replace_mode: "[element_1ˇ]".into(),
10922            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10923            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10924        },
10925        Run {
10926            run_description: "Add element to start of list -- first and second elements are equal",
10927            initial_state: "[elˇelement]".into(),
10928            buffer_marked_text: "[<el|element>]".into(),
10929            completion_label: "element",
10930            completion_text: "element",
10931            expected_with_insert_mode: "[elementˇelement]".into(),
10932            expected_with_replace_mode: "[elementˇ]".into(),
10933            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10934            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10935        },
10936        Run {
10937            run_description: "Ends with matching suffix",
10938            initial_state: "SubˇError".into(),
10939            buffer_marked_text: "<Sub|Error>".into(),
10940            completion_label: "SubscriptionError",
10941            completion_text: "SubscriptionError",
10942            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10943            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10944            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10945            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10946        },
10947        Run {
10948            run_description: "Suffix is a subsequence -- contiguous",
10949            initial_state: "SubˇErr".into(),
10950            buffer_marked_text: "<Sub|Err>".into(),
10951            completion_label: "SubscriptionError",
10952            completion_text: "SubscriptionError",
10953            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10954            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10955            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10956            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10957        },
10958        Run {
10959            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10960            initial_state: "Suˇscrirr".into(),
10961            buffer_marked_text: "<Su|scrirr>".into(),
10962            completion_label: "SubscriptionError",
10963            completion_text: "SubscriptionError",
10964            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10965            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10966            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10967            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10968        },
10969        Run {
10970            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10971            initial_state: "foo(indˇix)".into(),
10972            buffer_marked_text: "foo(<ind|ix>)".into(),
10973            completion_label: "node_index",
10974            completion_text: "node_index",
10975            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10976            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10977            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10978            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10979        },
10980        Run {
10981            run_description: "Replace range ends before cursor - should extend to cursor",
10982            initial_state: "before editˇo after".into(),
10983            buffer_marked_text: "before <{ed}>it|o after".into(),
10984            completion_label: "editor",
10985            completion_text: "editor",
10986            expected_with_insert_mode: "before editorˇo after".into(),
10987            expected_with_replace_mode: "before editorˇo after".into(),
10988            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
10989            expected_with_replace_suffix_mode: "before editorˇo after".into(),
10990        },
10991        Run {
10992            run_description: "Uses label for suffix matching",
10993            initial_state: "before ediˇtor after".into(),
10994            buffer_marked_text: "before <edi|tor> after".into(),
10995            completion_label: "editor",
10996            completion_text: "editor()",
10997            expected_with_insert_mode: "before editor()ˇtor after".into(),
10998            expected_with_replace_mode: "before editor()ˇ after".into(),
10999            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11000            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11001        },
11002        Run {
11003            run_description: "Case insensitive subsequence and suffix matching",
11004            initial_state: "before EDiˇtoR after".into(),
11005            buffer_marked_text: "before <EDi|toR> after".into(),
11006            completion_label: "editor",
11007            completion_text: "editor",
11008            expected_with_insert_mode: "before editorˇtoR after".into(),
11009            expected_with_replace_mode: "before editorˇ after".into(),
11010            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11011            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11012        },
11013    ];
11014
11015    for run in runs {
11016        let run_variations = [
11017            (LspInsertMode::Insert, run.expected_with_insert_mode),
11018            (LspInsertMode::Replace, run.expected_with_replace_mode),
11019            (
11020                LspInsertMode::ReplaceSubsequence,
11021                run.expected_with_replace_subsequence_mode,
11022            ),
11023            (
11024                LspInsertMode::ReplaceSuffix,
11025                run.expected_with_replace_suffix_mode,
11026            ),
11027        ];
11028
11029        for (lsp_insert_mode, expected_text) in run_variations {
11030            eprintln!(
11031                "run = {:?}, mode = {lsp_insert_mode:.?}",
11032                run.run_description,
11033            );
11034
11035            update_test_language_settings(&mut cx, |settings| {
11036                settings.defaults.completions = Some(CompletionSettings {
11037                    lsp_insert_mode,
11038                    words: WordsCompletionMode::Disabled,
11039                    lsp: true,
11040                    lsp_fetch_timeout_ms: 0,
11041                });
11042            });
11043
11044            cx.set_state(&run.initial_state);
11045            cx.update_editor(|editor, window, cx| {
11046                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11047            });
11048
11049            let counter = Arc::new(AtomicUsize::new(0));
11050            handle_completion_request_with_insert_and_replace(
11051                &mut cx,
11052                &run.buffer_marked_text,
11053                vec![(run.completion_label, run.completion_text)],
11054                counter.clone(),
11055            )
11056            .await;
11057            cx.condition(|editor, _| editor.context_menu_visible())
11058                .await;
11059            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11060
11061            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11062                editor
11063                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11064                    .unwrap()
11065            });
11066            cx.assert_editor_state(&expected_text);
11067            handle_resolve_completion_request(&mut cx, None).await;
11068            apply_additional_edits.await.unwrap();
11069        }
11070    }
11071}
11072
11073#[gpui::test]
11074async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11075    init_test(cx, |_| {});
11076    let mut cx = EditorLspTestContext::new_rust(
11077        lsp::ServerCapabilities {
11078            completion_provider: Some(lsp::CompletionOptions {
11079                resolve_provider: Some(true),
11080                ..Default::default()
11081            }),
11082            ..Default::default()
11083        },
11084        cx,
11085    )
11086    .await;
11087
11088    let initial_state = "SubˇError";
11089    let buffer_marked_text = "<Sub|Error>";
11090    let completion_text = "SubscriptionError";
11091    let expected_with_insert_mode = "SubscriptionErrorˇError";
11092    let expected_with_replace_mode = "SubscriptionErrorˇ";
11093
11094    update_test_language_settings(&mut cx, |settings| {
11095        settings.defaults.completions = Some(CompletionSettings {
11096            words: WordsCompletionMode::Disabled,
11097            // set the opposite here to ensure that the action is overriding the default behavior
11098            lsp_insert_mode: LspInsertMode::Insert,
11099            lsp: true,
11100            lsp_fetch_timeout_ms: 0,
11101        });
11102    });
11103
11104    cx.set_state(initial_state);
11105    cx.update_editor(|editor, window, cx| {
11106        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11107    });
11108
11109    let counter = Arc::new(AtomicUsize::new(0));
11110    handle_completion_request_with_insert_and_replace(
11111        &mut cx,
11112        &buffer_marked_text,
11113        vec![(completion_text, completion_text)],
11114        counter.clone(),
11115    )
11116    .await;
11117    cx.condition(|editor, _| editor.context_menu_visible())
11118        .await;
11119    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11120
11121    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11122        editor
11123            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11124            .unwrap()
11125    });
11126    cx.assert_editor_state(&expected_with_replace_mode);
11127    handle_resolve_completion_request(&mut cx, None).await;
11128    apply_additional_edits.await.unwrap();
11129
11130    update_test_language_settings(&mut cx, |settings| {
11131        settings.defaults.completions = Some(CompletionSettings {
11132            words: WordsCompletionMode::Disabled,
11133            // set the opposite here to ensure that the action is overriding the default behavior
11134            lsp_insert_mode: LspInsertMode::Replace,
11135            lsp: true,
11136            lsp_fetch_timeout_ms: 0,
11137        });
11138    });
11139
11140    cx.set_state(initial_state);
11141    cx.update_editor(|editor, window, cx| {
11142        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11143    });
11144    handle_completion_request_with_insert_and_replace(
11145        &mut cx,
11146        &buffer_marked_text,
11147        vec![(completion_text, completion_text)],
11148        counter.clone(),
11149    )
11150    .await;
11151    cx.condition(|editor, _| editor.context_menu_visible())
11152        .await;
11153    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11154
11155    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11156        editor
11157            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11158            .unwrap()
11159    });
11160    cx.assert_editor_state(&expected_with_insert_mode);
11161    handle_resolve_completion_request(&mut cx, None).await;
11162    apply_additional_edits.await.unwrap();
11163}
11164
11165#[gpui::test]
11166async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11167    init_test(cx, |_| {});
11168    let mut cx = EditorLspTestContext::new_rust(
11169        lsp::ServerCapabilities {
11170            completion_provider: Some(lsp::CompletionOptions {
11171                resolve_provider: Some(true),
11172                ..Default::default()
11173            }),
11174            ..Default::default()
11175        },
11176        cx,
11177    )
11178    .await;
11179
11180    // scenario: surrounding text matches completion text
11181    let completion_text = "to_offset";
11182    let initial_state = indoc! {"
11183        1. buf.to_offˇsuffix
11184        2. buf.to_offˇsuf
11185        3. buf.to_offˇfix
11186        4. buf.to_offˇ
11187        5. into_offˇensive
11188        6. ˇsuffix
11189        7. let ˇ //
11190        8. aaˇzz
11191        9. buf.to_off«zzzzzˇ»suffix
11192        10. buf.«ˇzzzzz»suffix
11193        11. to_off«ˇzzzzz»
11194
11195        buf.to_offˇsuffix  // newest cursor
11196    "};
11197    let completion_marked_buffer = indoc! {"
11198        1. buf.to_offsuffix
11199        2. buf.to_offsuf
11200        3. buf.to_offfix
11201        4. buf.to_off
11202        5. into_offensive
11203        6. suffix
11204        7. let  //
11205        8. aazz
11206        9. buf.to_offzzzzzsuffix
11207        10. buf.zzzzzsuffix
11208        11. to_offzzzzz
11209
11210        buf.<to_off|suffix>  // newest cursor
11211    "};
11212    let expected = indoc! {"
11213        1. buf.to_offsetˇ
11214        2. buf.to_offsetˇsuf
11215        3. buf.to_offsetˇfix
11216        4. buf.to_offsetˇ
11217        5. into_offsetˇensive
11218        6. to_offsetˇsuffix
11219        7. let to_offsetˇ //
11220        8. aato_offsetˇzz
11221        9. buf.to_offsetˇ
11222        10. buf.to_offsetˇsuffix
11223        11. to_offsetˇ
11224
11225        buf.to_offsetˇ  // newest cursor
11226    "};
11227    cx.set_state(initial_state);
11228    cx.update_editor(|editor, window, cx| {
11229        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11230    });
11231    handle_completion_request_with_insert_and_replace(
11232        &mut cx,
11233        completion_marked_buffer,
11234        vec![(completion_text, completion_text)],
11235        Arc::new(AtomicUsize::new(0)),
11236    )
11237    .await;
11238    cx.condition(|editor, _| editor.context_menu_visible())
11239        .await;
11240    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11241        editor
11242            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11243            .unwrap()
11244    });
11245    cx.assert_editor_state(expected);
11246    handle_resolve_completion_request(&mut cx, None).await;
11247    apply_additional_edits.await.unwrap();
11248
11249    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11250    let completion_text = "foo_and_bar";
11251    let initial_state = indoc! {"
11252        1. ooanbˇ
11253        2. zooanbˇ
11254        3. ooanbˇz
11255        4. zooanbˇz
11256        5. ooanˇ
11257        6. oanbˇ
11258
11259        ooanbˇ
11260    "};
11261    let completion_marked_buffer = indoc! {"
11262        1. ooanb
11263        2. zooanb
11264        3. ooanbz
11265        4. zooanbz
11266        5. ooan
11267        6. oanb
11268
11269        <ooanb|>
11270    "};
11271    let expected = indoc! {"
11272        1. foo_and_barˇ
11273        2. zfoo_and_barˇ
11274        3. foo_and_barˇz
11275        4. zfoo_and_barˇz
11276        5. ooanfoo_and_barˇ
11277        6. oanbfoo_and_barˇ
11278
11279        foo_and_barˇ
11280    "};
11281    cx.set_state(initial_state);
11282    cx.update_editor(|editor, window, cx| {
11283        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11284    });
11285    handle_completion_request_with_insert_and_replace(
11286        &mut cx,
11287        completion_marked_buffer,
11288        vec![(completion_text, completion_text)],
11289        Arc::new(AtomicUsize::new(0)),
11290    )
11291    .await;
11292    cx.condition(|editor, _| editor.context_menu_visible())
11293        .await;
11294    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11295        editor
11296            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11297            .unwrap()
11298    });
11299    cx.assert_editor_state(expected);
11300    handle_resolve_completion_request(&mut cx, None).await;
11301    apply_additional_edits.await.unwrap();
11302
11303    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11304    // (expects the same as if it was inserted at the end)
11305    let completion_text = "foo_and_bar";
11306    let initial_state = indoc! {"
11307        1. ooˇanb
11308        2. zooˇanb
11309        3. ooˇanbz
11310        4. zooˇanbz
11311
11312        ooˇanb
11313    "};
11314    let completion_marked_buffer = indoc! {"
11315        1. ooanb
11316        2. zooanb
11317        3. ooanbz
11318        4. zooanbz
11319
11320        <oo|anb>
11321    "};
11322    let expected = indoc! {"
11323        1. foo_and_barˇ
11324        2. zfoo_and_barˇ
11325        3. foo_and_barˇz
11326        4. zfoo_and_barˇz
11327
11328        foo_and_barˇ
11329    "};
11330    cx.set_state(initial_state);
11331    cx.update_editor(|editor, window, cx| {
11332        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11333    });
11334    handle_completion_request_with_insert_and_replace(
11335        &mut cx,
11336        completion_marked_buffer,
11337        vec![(completion_text, completion_text)],
11338        Arc::new(AtomicUsize::new(0)),
11339    )
11340    .await;
11341    cx.condition(|editor, _| editor.context_menu_visible())
11342        .await;
11343    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11344        editor
11345            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11346            .unwrap()
11347    });
11348    cx.assert_editor_state(expected);
11349    handle_resolve_completion_request(&mut cx, None).await;
11350    apply_additional_edits.await.unwrap();
11351}
11352
11353// This used to crash
11354#[gpui::test]
11355async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11356    init_test(cx, |_| {});
11357
11358    let buffer_text = indoc! {"
11359        fn main() {
11360            10.satu;
11361
11362            //
11363            // separate cursors so they open in different excerpts (manually reproducible)
11364            //
11365
11366            10.satu20;
11367        }
11368    "};
11369    let multibuffer_text_with_selections = indoc! {"
11370        fn main() {
11371            10.satuˇ;
11372
11373            //
11374
11375            //
11376
11377            10.satuˇ20;
11378        }
11379    "};
11380    let expected_multibuffer = indoc! {"
11381        fn main() {
11382            10.saturating_sub()ˇ;
11383
11384            //
11385
11386            //
11387
11388            10.saturating_sub()ˇ;
11389        }
11390    "};
11391
11392    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11393    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11394
11395    let fs = FakeFs::new(cx.executor());
11396    fs.insert_tree(
11397        path!("/a"),
11398        json!({
11399            "main.rs": buffer_text,
11400        }),
11401    )
11402    .await;
11403
11404    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11405    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11406    language_registry.add(rust_lang());
11407    let mut fake_servers = language_registry.register_fake_lsp(
11408        "Rust",
11409        FakeLspAdapter {
11410            capabilities: lsp::ServerCapabilities {
11411                completion_provider: Some(lsp::CompletionOptions {
11412                    resolve_provider: None,
11413                    ..lsp::CompletionOptions::default()
11414                }),
11415                ..lsp::ServerCapabilities::default()
11416            },
11417            ..FakeLspAdapter::default()
11418        },
11419    );
11420    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11421    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11422    let buffer = project
11423        .update(cx, |project, cx| {
11424            project.open_local_buffer(path!("/a/main.rs"), cx)
11425        })
11426        .await
11427        .unwrap();
11428
11429    let multi_buffer = cx.new(|cx| {
11430        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11431        multi_buffer.push_excerpts(
11432            buffer.clone(),
11433            [ExcerptRange::new(0..first_excerpt_end)],
11434            cx,
11435        );
11436        multi_buffer.push_excerpts(
11437            buffer.clone(),
11438            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11439            cx,
11440        );
11441        multi_buffer
11442    });
11443
11444    let editor = workspace
11445        .update(cx, |_, window, cx| {
11446            cx.new(|cx| {
11447                Editor::new(
11448                    EditorMode::Full {
11449                        scale_ui_elements_with_buffer_font_size: false,
11450                        show_active_line_background: false,
11451                        sized_by_content: false,
11452                    },
11453                    multi_buffer.clone(),
11454                    Some(project.clone()),
11455                    window,
11456                    cx,
11457                )
11458            })
11459        })
11460        .unwrap();
11461
11462    let pane = workspace
11463        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11464        .unwrap();
11465    pane.update_in(cx, |pane, window, cx| {
11466        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11467    });
11468
11469    let fake_server = fake_servers.next().await.unwrap();
11470
11471    editor.update_in(cx, |editor, window, cx| {
11472        editor.change_selections(None, window, cx, |s| {
11473            s.select_ranges([
11474                Point::new(1, 11)..Point::new(1, 11),
11475                Point::new(7, 11)..Point::new(7, 11),
11476            ])
11477        });
11478
11479        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11480    });
11481
11482    editor.update_in(cx, |editor, window, cx| {
11483        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11484    });
11485
11486    fake_server
11487        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11488            let completion_item = lsp::CompletionItem {
11489                label: "saturating_sub()".into(),
11490                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11491                    lsp::InsertReplaceEdit {
11492                        new_text: "saturating_sub()".to_owned(),
11493                        insert: lsp::Range::new(
11494                            lsp::Position::new(7, 7),
11495                            lsp::Position::new(7, 11),
11496                        ),
11497                        replace: lsp::Range::new(
11498                            lsp::Position::new(7, 7),
11499                            lsp::Position::new(7, 13),
11500                        ),
11501                    },
11502                )),
11503                ..lsp::CompletionItem::default()
11504            };
11505
11506            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11507        })
11508        .next()
11509        .await
11510        .unwrap();
11511
11512    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11513        .await;
11514
11515    editor
11516        .update_in(cx, |editor, window, cx| {
11517            editor
11518                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11519                .unwrap()
11520        })
11521        .await
11522        .unwrap();
11523
11524    editor.update(cx, |editor, cx| {
11525        assert_text_with_selections(editor, expected_multibuffer, cx);
11526    })
11527}
11528
11529#[gpui::test]
11530async fn test_completion(cx: &mut TestAppContext) {
11531    init_test(cx, |_| {});
11532
11533    let mut cx = EditorLspTestContext::new_rust(
11534        lsp::ServerCapabilities {
11535            completion_provider: Some(lsp::CompletionOptions {
11536                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11537                resolve_provider: Some(true),
11538                ..Default::default()
11539            }),
11540            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11541            ..Default::default()
11542        },
11543        cx,
11544    )
11545    .await;
11546    let counter = Arc::new(AtomicUsize::new(0));
11547
11548    cx.set_state(indoc! {"
11549        oneˇ
11550        two
11551        three
11552    "});
11553    cx.simulate_keystroke(".");
11554    handle_completion_request(
11555        indoc! {"
11556            one.|<>
11557            two
11558            three
11559        "},
11560        vec!["first_completion", "second_completion"],
11561        true,
11562        counter.clone(),
11563        &mut cx,
11564    )
11565    .await;
11566    cx.condition(|editor, _| editor.context_menu_visible())
11567        .await;
11568    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11569
11570    let _handler = handle_signature_help_request(
11571        &mut cx,
11572        lsp::SignatureHelp {
11573            signatures: vec![lsp::SignatureInformation {
11574                label: "test signature".to_string(),
11575                documentation: None,
11576                parameters: Some(vec![lsp::ParameterInformation {
11577                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11578                    documentation: None,
11579                }]),
11580                active_parameter: None,
11581            }],
11582            active_signature: None,
11583            active_parameter: None,
11584        },
11585    );
11586    cx.update_editor(|editor, window, cx| {
11587        assert!(
11588            !editor.signature_help_state.is_shown(),
11589            "No signature help was called for"
11590        );
11591        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11592    });
11593    cx.run_until_parked();
11594    cx.update_editor(|editor, _, _| {
11595        assert!(
11596            !editor.signature_help_state.is_shown(),
11597            "No signature help should be shown when completions menu is open"
11598        );
11599    });
11600
11601    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11602        editor.context_menu_next(&Default::default(), window, cx);
11603        editor
11604            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11605            .unwrap()
11606    });
11607    cx.assert_editor_state(indoc! {"
11608        one.second_completionˇ
11609        two
11610        three
11611    "});
11612
11613    handle_resolve_completion_request(
11614        &mut cx,
11615        Some(vec![
11616            (
11617                //This overlaps with the primary completion edit which is
11618                //misbehavior from the LSP spec, test that we filter it out
11619                indoc! {"
11620                    one.second_ˇcompletion
11621                    two
11622                    threeˇ
11623                "},
11624                "overlapping additional edit",
11625            ),
11626            (
11627                indoc! {"
11628                    one.second_completion
11629                    two
11630                    threeˇ
11631                "},
11632                "\nadditional edit",
11633            ),
11634        ]),
11635    )
11636    .await;
11637    apply_additional_edits.await.unwrap();
11638    cx.assert_editor_state(indoc! {"
11639        one.second_completionˇ
11640        two
11641        three
11642        additional edit
11643    "});
11644
11645    cx.set_state(indoc! {"
11646        one.second_completion
11647        twoˇ
11648        threeˇ
11649        additional edit
11650    "});
11651    cx.simulate_keystroke(" ");
11652    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11653    cx.simulate_keystroke("s");
11654    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11655
11656    cx.assert_editor_state(indoc! {"
11657        one.second_completion
11658        two sˇ
11659        three sˇ
11660        additional edit
11661    "});
11662    handle_completion_request(
11663        indoc! {"
11664            one.second_completion
11665            two s
11666            three <s|>
11667            additional edit
11668        "},
11669        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11670        true,
11671        counter.clone(),
11672        &mut cx,
11673    )
11674    .await;
11675    cx.condition(|editor, _| editor.context_menu_visible())
11676        .await;
11677    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11678
11679    cx.simulate_keystroke("i");
11680
11681    handle_completion_request(
11682        indoc! {"
11683            one.second_completion
11684            two si
11685            three <si|>
11686            additional edit
11687        "},
11688        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11689        true,
11690        counter.clone(),
11691        &mut cx,
11692    )
11693    .await;
11694    cx.condition(|editor, _| editor.context_menu_visible())
11695        .await;
11696    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11697
11698    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11699        editor
11700            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11701            .unwrap()
11702    });
11703    cx.assert_editor_state(indoc! {"
11704        one.second_completion
11705        two sixth_completionˇ
11706        three sixth_completionˇ
11707        additional edit
11708    "});
11709
11710    apply_additional_edits.await.unwrap();
11711
11712    update_test_language_settings(&mut cx, |settings| {
11713        settings.defaults.show_completions_on_input = Some(false);
11714    });
11715    cx.set_state("editorˇ");
11716    cx.simulate_keystroke(".");
11717    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11718    cx.simulate_keystrokes("c l o");
11719    cx.assert_editor_state("editor.cloˇ");
11720    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11721    cx.update_editor(|editor, window, cx| {
11722        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11723    });
11724    handle_completion_request(
11725        "editor.<clo|>",
11726        vec!["close", "clobber"],
11727        true,
11728        counter.clone(),
11729        &mut cx,
11730    )
11731    .await;
11732    cx.condition(|editor, _| editor.context_menu_visible())
11733        .await;
11734    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11735
11736    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11737        editor
11738            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11739            .unwrap()
11740    });
11741    cx.assert_editor_state("editor.closeˇ");
11742    handle_resolve_completion_request(&mut cx, None).await;
11743    apply_additional_edits.await.unwrap();
11744}
11745
11746#[gpui::test]
11747async fn test_completion_reuse(cx: &mut TestAppContext) {
11748    init_test(cx, |_| {});
11749
11750    let mut cx = EditorLspTestContext::new_rust(
11751        lsp::ServerCapabilities {
11752            completion_provider: Some(lsp::CompletionOptions {
11753                trigger_characters: Some(vec![".".to_string()]),
11754                ..Default::default()
11755            }),
11756            ..Default::default()
11757        },
11758        cx,
11759    )
11760    .await;
11761
11762    let counter = Arc::new(AtomicUsize::new(0));
11763    cx.set_state("objˇ");
11764    cx.simulate_keystroke(".");
11765
11766    // Initial completion request returns complete results
11767    let is_incomplete = false;
11768    handle_completion_request(
11769        "obj.|<>",
11770        vec!["a", "ab", "abc"],
11771        is_incomplete,
11772        counter.clone(),
11773        &mut cx,
11774    )
11775    .await;
11776    cx.run_until_parked();
11777    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11778    cx.assert_editor_state("obj.ˇ");
11779    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11780
11781    // Type "a" - filters existing completions
11782    cx.simulate_keystroke("a");
11783    cx.run_until_parked();
11784    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11785    cx.assert_editor_state("obj.aˇ");
11786    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11787
11788    // Type "b" - filters existing completions
11789    cx.simulate_keystroke("b");
11790    cx.run_until_parked();
11791    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11792    cx.assert_editor_state("obj.abˇ");
11793    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11794
11795    // Type "c" - filters existing completions
11796    cx.simulate_keystroke("c");
11797    cx.run_until_parked();
11798    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11799    cx.assert_editor_state("obj.abcˇ");
11800    check_displayed_completions(vec!["abc"], &mut cx);
11801
11802    // Backspace to delete "c" - filters existing completions
11803    cx.update_editor(|editor, window, cx| {
11804        editor.backspace(&Backspace, window, cx);
11805    });
11806    cx.run_until_parked();
11807    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11808    cx.assert_editor_state("obj.abˇ");
11809    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11810
11811    // Moving cursor to the left dismisses menu.
11812    cx.update_editor(|editor, window, cx| {
11813        editor.move_left(&MoveLeft, window, cx);
11814    });
11815    cx.run_until_parked();
11816    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11817    cx.assert_editor_state("obj.aˇb");
11818    cx.update_editor(|editor, _, _| {
11819        assert_eq!(editor.context_menu_visible(), false);
11820    });
11821
11822    // Type "b" - new request
11823    cx.simulate_keystroke("b");
11824    let is_incomplete = false;
11825    handle_completion_request(
11826        "obj.<ab|>a",
11827        vec!["ab", "abc"],
11828        is_incomplete,
11829        counter.clone(),
11830        &mut cx,
11831    )
11832    .await;
11833    cx.run_until_parked();
11834    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11835    cx.assert_editor_state("obj.abˇb");
11836    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11837
11838    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
11839    cx.update_editor(|editor, window, cx| {
11840        editor.backspace(&Backspace, window, cx);
11841    });
11842    let is_incomplete = false;
11843    handle_completion_request(
11844        "obj.<a|>b",
11845        vec!["a", "ab", "abc"],
11846        is_incomplete,
11847        counter.clone(),
11848        &mut cx,
11849    )
11850    .await;
11851    cx.run_until_parked();
11852    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11853    cx.assert_editor_state("obj.aˇb");
11854    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11855
11856    // Backspace to delete "a" - dismisses menu.
11857    cx.update_editor(|editor, window, cx| {
11858        editor.backspace(&Backspace, window, cx);
11859    });
11860    cx.run_until_parked();
11861    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11862    cx.assert_editor_state("obj.ˇb");
11863    cx.update_editor(|editor, _, _| {
11864        assert_eq!(editor.context_menu_visible(), false);
11865    });
11866}
11867
11868#[gpui::test]
11869async fn test_word_completion(cx: &mut TestAppContext) {
11870    let lsp_fetch_timeout_ms = 10;
11871    init_test(cx, |language_settings| {
11872        language_settings.defaults.completions = Some(CompletionSettings {
11873            words: WordsCompletionMode::Fallback,
11874            lsp: true,
11875            lsp_fetch_timeout_ms: 10,
11876            lsp_insert_mode: LspInsertMode::Insert,
11877        });
11878    });
11879
11880    let mut cx = EditorLspTestContext::new_rust(
11881        lsp::ServerCapabilities {
11882            completion_provider: Some(lsp::CompletionOptions {
11883                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11884                ..lsp::CompletionOptions::default()
11885            }),
11886            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11887            ..lsp::ServerCapabilities::default()
11888        },
11889        cx,
11890    )
11891    .await;
11892
11893    let throttle_completions = Arc::new(AtomicBool::new(false));
11894
11895    let lsp_throttle_completions = throttle_completions.clone();
11896    let _completion_requests_handler =
11897        cx.lsp
11898            .server
11899            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11900                let lsp_throttle_completions = lsp_throttle_completions.clone();
11901                let cx = cx.clone();
11902                async move {
11903                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11904                        cx.background_executor()
11905                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11906                            .await;
11907                    }
11908                    Ok(Some(lsp::CompletionResponse::Array(vec![
11909                        lsp::CompletionItem {
11910                            label: "first".into(),
11911                            ..lsp::CompletionItem::default()
11912                        },
11913                        lsp::CompletionItem {
11914                            label: "last".into(),
11915                            ..lsp::CompletionItem::default()
11916                        },
11917                    ])))
11918                }
11919            });
11920
11921    cx.set_state(indoc! {"
11922        oneˇ
11923        two
11924        three
11925    "});
11926    cx.simulate_keystroke(".");
11927    cx.executor().run_until_parked();
11928    cx.condition(|editor, _| editor.context_menu_visible())
11929        .await;
11930    cx.update_editor(|editor, window, cx| {
11931        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11932        {
11933            assert_eq!(
11934                completion_menu_entries(&menu),
11935                &["first", "last"],
11936                "When LSP server is fast to reply, no fallback word completions are used"
11937            );
11938        } else {
11939            panic!("expected completion menu to be open");
11940        }
11941        editor.cancel(&Cancel, window, cx);
11942    });
11943    cx.executor().run_until_parked();
11944    cx.condition(|editor, _| !editor.context_menu_visible())
11945        .await;
11946
11947    throttle_completions.store(true, atomic::Ordering::Release);
11948    cx.simulate_keystroke(".");
11949    cx.executor()
11950        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11951    cx.executor().run_until_parked();
11952    cx.condition(|editor, _| editor.context_menu_visible())
11953        .await;
11954    cx.update_editor(|editor, _, _| {
11955        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11956        {
11957            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11958                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11959        } else {
11960            panic!("expected completion menu to be open");
11961        }
11962    });
11963}
11964
11965#[gpui::test]
11966async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11967    init_test(cx, |language_settings| {
11968        language_settings.defaults.completions = Some(CompletionSettings {
11969            words: WordsCompletionMode::Enabled,
11970            lsp: true,
11971            lsp_fetch_timeout_ms: 0,
11972            lsp_insert_mode: LspInsertMode::Insert,
11973        });
11974    });
11975
11976    let mut cx = EditorLspTestContext::new_rust(
11977        lsp::ServerCapabilities {
11978            completion_provider: Some(lsp::CompletionOptions {
11979                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11980                ..lsp::CompletionOptions::default()
11981            }),
11982            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11983            ..lsp::ServerCapabilities::default()
11984        },
11985        cx,
11986    )
11987    .await;
11988
11989    let _completion_requests_handler =
11990        cx.lsp
11991            .server
11992            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11993                Ok(Some(lsp::CompletionResponse::Array(vec![
11994                    lsp::CompletionItem {
11995                        label: "first".into(),
11996                        ..lsp::CompletionItem::default()
11997                    },
11998                    lsp::CompletionItem {
11999                        label: "last".into(),
12000                        ..lsp::CompletionItem::default()
12001                    },
12002                ])))
12003            });
12004
12005    cx.set_state(indoc! {"ˇ
12006        first
12007        last
12008        second
12009    "});
12010    cx.simulate_keystroke(".");
12011    cx.executor().run_until_parked();
12012    cx.condition(|editor, _| editor.context_menu_visible())
12013        .await;
12014    cx.update_editor(|editor, _, _| {
12015        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12016        {
12017            assert_eq!(
12018                completion_menu_entries(&menu),
12019                &["first", "last", "second"],
12020                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12021            );
12022        } else {
12023            panic!("expected completion menu to be open");
12024        }
12025    });
12026}
12027
12028#[gpui::test]
12029async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12030    init_test(cx, |language_settings| {
12031        language_settings.defaults.completions = Some(CompletionSettings {
12032            words: WordsCompletionMode::Disabled,
12033            lsp: true,
12034            lsp_fetch_timeout_ms: 0,
12035            lsp_insert_mode: LspInsertMode::Insert,
12036        });
12037    });
12038
12039    let mut cx = EditorLspTestContext::new_rust(
12040        lsp::ServerCapabilities {
12041            completion_provider: Some(lsp::CompletionOptions {
12042                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12043                ..lsp::CompletionOptions::default()
12044            }),
12045            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12046            ..lsp::ServerCapabilities::default()
12047        },
12048        cx,
12049    )
12050    .await;
12051
12052    let _completion_requests_handler =
12053        cx.lsp
12054            .server
12055            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12056                panic!("LSP completions should not be queried when dealing with word completions")
12057            });
12058
12059    cx.set_state(indoc! {"ˇ
12060        first
12061        last
12062        second
12063    "});
12064    cx.update_editor(|editor, window, cx| {
12065        editor.show_word_completions(&ShowWordCompletions, window, cx);
12066    });
12067    cx.executor().run_until_parked();
12068    cx.condition(|editor, _| editor.context_menu_visible())
12069        .await;
12070    cx.update_editor(|editor, _, _| {
12071        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12072        {
12073            assert_eq!(
12074                completion_menu_entries(&menu),
12075                &["first", "last", "second"],
12076                "`ShowWordCompletions` action should show word completions"
12077            );
12078        } else {
12079            panic!("expected completion menu to be open");
12080        }
12081    });
12082
12083    cx.simulate_keystroke("l");
12084    cx.executor().run_until_parked();
12085    cx.condition(|editor, _| editor.context_menu_visible())
12086        .await;
12087    cx.update_editor(|editor, _, _| {
12088        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12089        {
12090            assert_eq!(
12091                completion_menu_entries(&menu),
12092                &["last"],
12093                "After showing word completions, further editing should filter them and not query the LSP"
12094            );
12095        } else {
12096            panic!("expected completion menu to be open");
12097        }
12098    });
12099}
12100
12101#[gpui::test]
12102async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12103    init_test(cx, |language_settings| {
12104        language_settings.defaults.completions = Some(CompletionSettings {
12105            words: WordsCompletionMode::Fallback,
12106            lsp: false,
12107            lsp_fetch_timeout_ms: 0,
12108            lsp_insert_mode: LspInsertMode::Insert,
12109        });
12110    });
12111
12112    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12113
12114    cx.set_state(indoc! {"ˇ
12115        0_usize
12116        let
12117        33
12118        4.5f32
12119    "});
12120    cx.update_editor(|editor, window, cx| {
12121        editor.show_completions(&ShowCompletions::default(), window, cx);
12122    });
12123    cx.executor().run_until_parked();
12124    cx.condition(|editor, _| editor.context_menu_visible())
12125        .await;
12126    cx.update_editor(|editor, window, cx| {
12127        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12128        {
12129            assert_eq!(
12130                completion_menu_entries(&menu),
12131                &["let"],
12132                "With no digits in the completion query, no digits should be in the word completions"
12133            );
12134        } else {
12135            panic!("expected completion menu to be open");
12136        }
12137        editor.cancel(&Cancel, window, cx);
12138    });
12139
12140    cx.set_state(indoc! {"12141        0_usize
12142        let
12143        3
12144        33.35f32
12145    "});
12146    cx.update_editor(|editor, window, cx| {
12147        editor.show_completions(&ShowCompletions::default(), window, cx);
12148    });
12149    cx.executor().run_until_parked();
12150    cx.condition(|editor, _| editor.context_menu_visible())
12151        .await;
12152    cx.update_editor(|editor, _, _| {
12153        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12154        {
12155            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12156                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12157        } else {
12158            panic!("expected completion menu to be open");
12159        }
12160    });
12161}
12162
12163fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12164    let position = || lsp::Position {
12165        line: params.text_document_position.position.line,
12166        character: params.text_document_position.position.character,
12167    };
12168    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12169        range: lsp::Range {
12170            start: position(),
12171            end: position(),
12172        },
12173        new_text: text.to_string(),
12174    }))
12175}
12176
12177#[gpui::test]
12178async fn test_multiline_completion(cx: &mut TestAppContext) {
12179    init_test(cx, |_| {});
12180
12181    let fs = FakeFs::new(cx.executor());
12182    fs.insert_tree(
12183        path!("/a"),
12184        json!({
12185            "main.ts": "a",
12186        }),
12187    )
12188    .await;
12189
12190    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12191    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12192    let typescript_language = Arc::new(Language::new(
12193        LanguageConfig {
12194            name: "TypeScript".into(),
12195            matcher: LanguageMatcher {
12196                path_suffixes: vec!["ts".to_string()],
12197                ..LanguageMatcher::default()
12198            },
12199            line_comments: vec!["// ".into()],
12200            ..LanguageConfig::default()
12201        },
12202        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12203    ));
12204    language_registry.add(typescript_language.clone());
12205    let mut fake_servers = language_registry.register_fake_lsp(
12206        "TypeScript",
12207        FakeLspAdapter {
12208            capabilities: lsp::ServerCapabilities {
12209                completion_provider: Some(lsp::CompletionOptions {
12210                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12211                    ..lsp::CompletionOptions::default()
12212                }),
12213                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12214                ..lsp::ServerCapabilities::default()
12215            },
12216            // Emulate vtsls label generation
12217            label_for_completion: Some(Box::new(|item, _| {
12218                let text = if let Some(description) = item
12219                    .label_details
12220                    .as_ref()
12221                    .and_then(|label_details| label_details.description.as_ref())
12222                {
12223                    format!("{} {}", item.label, description)
12224                } else if let Some(detail) = &item.detail {
12225                    format!("{} {}", item.label, detail)
12226                } else {
12227                    item.label.clone()
12228                };
12229                let len = text.len();
12230                Some(language::CodeLabel {
12231                    text,
12232                    runs: Vec::new(),
12233                    filter_range: 0..len,
12234                })
12235            })),
12236            ..FakeLspAdapter::default()
12237        },
12238    );
12239    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12240    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12241    let worktree_id = workspace
12242        .update(cx, |workspace, _window, cx| {
12243            workspace.project().update(cx, |project, cx| {
12244                project.worktrees(cx).next().unwrap().read(cx).id()
12245            })
12246        })
12247        .unwrap();
12248    let _buffer = project
12249        .update(cx, |project, cx| {
12250            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12251        })
12252        .await
12253        .unwrap();
12254    let editor = workspace
12255        .update(cx, |workspace, window, cx| {
12256            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12257        })
12258        .unwrap()
12259        .await
12260        .unwrap()
12261        .downcast::<Editor>()
12262        .unwrap();
12263    let fake_server = fake_servers.next().await.unwrap();
12264
12265    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12266    let multiline_label_2 = "a\nb\nc\n";
12267    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12268    let multiline_description = "d\ne\nf\n";
12269    let multiline_detail_2 = "g\nh\ni\n";
12270
12271    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12272        move |params, _| async move {
12273            Ok(Some(lsp::CompletionResponse::Array(vec![
12274                lsp::CompletionItem {
12275                    label: multiline_label.to_string(),
12276                    text_edit: gen_text_edit(&params, "new_text_1"),
12277                    ..lsp::CompletionItem::default()
12278                },
12279                lsp::CompletionItem {
12280                    label: "single line label 1".to_string(),
12281                    detail: Some(multiline_detail.to_string()),
12282                    text_edit: gen_text_edit(&params, "new_text_2"),
12283                    ..lsp::CompletionItem::default()
12284                },
12285                lsp::CompletionItem {
12286                    label: "single line label 2".to_string(),
12287                    label_details: Some(lsp::CompletionItemLabelDetails {
12288                        description: Some(multiline_description.to_string()),
12289                        detail: None,
12290                    }),
12291                    text_edit: gen_text_edit(&params, "new_text_2"),
12292                    ..lsp::CompletionItem::default()
12293                },
12294                lsp::CompletionItem {
12295                    label: multiline_label_2.to_string(),
12296                    detail: Some(multiline_detail_2.to_string()),
12297                    text_edit: gen_text_edit(&params, "new_text_3"),
12298                    ..lsp::CompletionItem::default()
12299                },
12300                lsp::CompletionItem {
12301                    label: "Label with many     spaces and \t but without newlines".to_string(),
12302                    detail: Some(
12303                        "Details with many     spaces and \t but without newlines".to_string(),
12304                    ),
12305                    text_edit: gen_text_edit(&params, "new_text_4"),
12306                    ..lsp::CompletionItem::default()
12307                },
12308            ])))
12309        },
12310    );
12311
12312    editor.update_in(cx, |editor, window, cx| {
12313        cx.focus_self(window);
12314        editor.move_to_end(&MoveToEnd, window, cx);
12315        editor.handle_input(".", window, cx);
12316    });
12317    cx.run_until_parked();
12318    completion_handle.next().await.unwrap();
12319
12320    editor.update(cx, |editor, _| {
12321        assert!(editor.context_menu_visible());
12322        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12323        {
12324            let completion_labels = menu
12325                .completions
12326                .borrow()
12327                .iter()
12328                .map(|c| c.label.text.clone())
12329                .collect::<Vec<_>>();
12330            assert_eq!(
12331                completion_labels,
12332                &[
12333                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12334                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12335                    "single line label 2 d e f ",
12336                    "a b c g h i ",
12337                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12338                ],
12339                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12340            );
12341
12342            for completion in menu
12343                .completions
12344                .borrow()
12345                .iter() {
12346                    assert_eq!(
12347                        completion.label.filter_range,
12348                        0..completion.label.text.len(),
12349                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12350                    );
12351                }
12352        } else {
12353            panic!("expected completion menu to be open");
12354        }
12355    });
12356}
12357
12358#[gpui::test]
12359async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12360    init_test(cx, |_| {});
12361    let mut cx = EditorLspTestContext::new_rust(
12362        lsp::ServerCapabilities {
12363            completion_provider: Some(lsp::CompletionOptions {
12364                trigger_characters: Some(vec![".".to_string()]),
12365                ..Default::default()
12366            }),
12367            ..Default::default()
12368        },
12369        cx,
12370    )
12371    .await;
12372    cx.lsp
12373        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12374            Ok(Some(lsp::CompletionResponse::Array(vec![
12375                lsp::CompletionItem {
12376                    label: "first".into(),
12377                    ..Default::default()
12378                },
12379                lsp::CompletionItem {
12380                    label: "last".into(),
12381                    ..Default::default()
12382                },
12383            ])))
12384        });
12385    cx.set_state("variableˇ");
12386    cx.simulate_keystroke(".");
12387    cx.executor().run_until_parked();
12388
12389    cx.update_editor(|editor, _, _| {
12390        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12391        {
12392            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12393        } else {
12394            panic!("expected completion menu to be open");
12395        }
12396    });
12397
12398    cx.update_editor(|editor, window, cx| {
12399        editor.move_page_down(&MovePageDown::default(), window, cx);
12400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12401        {
12402            assert!(
12403                menu.selected_item == 1,
12404                "expected PageDown to select the last item from the context menu"
12405            );
12406        } else {
12407            panic!("expected completion menu to stay open after PageDown");
12408        }
12409    });
12410
12411    cx.update_editor(|editor, window, cx| {
12412        editor.move_page_up(&MovePageUp::default(), window, cx);
12413        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12414        {
12415            assert!(
12416                menu.selected_item == 0,
12417                "expected PageUp to select the first item from the context menu"
12418            );
12419        } else {
12420            panic!("expected completion menu to stay open after PageUp");
12421        }
12422    });
12423}
12424
12425#[gpui::test]
12426async fn test_as_is_completions(cx: &mut TestAppContext) {
12427    init_test(cx, |_| {});
12428    let mut cx = EditorLspTestContext::new_rust(
12429        lsp::ServerCapabilities {
12430            completion_provider: Some(lsp::CompletionOptions {
12431                ..Default::default()
12432            }),
12433            ..Default::default()
12434        },
12435        cx,
12436    )
12437    .await;
12438    cx.lsp
12439        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12440            Ok(Some(lsp::CompletionResponse::Array(vec![
12441                lsp::CompletionItem {
12442                    label: "unsafe".into(),
12443                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12444                        range: lsp::Range {
12445                            start: lsp::Position {
12446                                line: 1,
12447                                character: 2,
12448                            },
12449                            end: lsp::Position {
12450                                line: 1,
12451                                character: 3,
12452                            },
12453                        },
12454                        new_text: "unsafe".to_string(),
12455                    })),
12456                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12457                    ..Default::default()
12458                },
12459            ])))
12460        });
12461    cx.set_state("fn a() {}\n");
12462    cx.executor().run_until_parked();
12463    cx.update_editor(|editor, window, cx| {
12464        editor.show_completions(
12465            &ShowCompletions {
12466                trigger: Some("\n".into()),
12467            },
12468            window,
12469            cx,
12470        );
12471    });
12472    cx.executor().run_until_parked();
12473
12474    cx.update_editor(|editor, window, cx| {
12475        editor.confirm_completion(&Default::default(), window, cx)
12476    });
12477    cx.executor().run_until_parked();
12478    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12479}
12480
12481#[gpui::test]
12482async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12483    init_test(cx, |_| {});
12484
12485    let mut cx = EditorLspTestContext::new_rust(
12486        lsp::ServerCapabilities {
12487            completion_provider: Some(lsp::CompletionOptions {
12488                trigger_characters: Some(vec![".".to_string()]),
12489                resolve_provider: Some(true),
12490                ..Default::default()
12491            }),
12492            ..Default::default()
12493        },
12494        cx,
12495    )
12496    .await;
12497
12498    cx.set_state("fn main() { let a = 2ˇ; }");
12499    cx.simulate_keystroke(".");
12500    let completion_item = lsp::CompletionItem {
12501        label: "Some".into(),
12502        kind: Some(lsp::CompletionItemKind::SNIPPET),
12503        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12504        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12505            kind: lsp::MarkupKind::Markdown,
12506            value: "```rust\nSome(2)\n```".to_string(),
12507        })),
12508        deprecated: Some(false),
12509        sort_text: Some("Some".to_string()),
12510        filter_text: Some("Some".to_string()),
12511        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12512        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12513            range: lsp::Range {
12514                start: lsp::Position {
12515                    line: 0,
12516                    character: 22,
12517                },
12518                end: lsp::Position {
12519                    line: 0,
12520                    character: 22,
12521                },
12522            },
12523            new_text: "Some(2)".to_string(),
12524        })),
12525        additional_text_edits: Some(vec![lsp::TextEdit {
12526            range: lsp::Range {
12527                start: lsp::Position {
12528                    line: 0,
12529                    character: 20,
12530                },
12531                end: lsp::Position {
12532                    line: 0,
12533                    character: 22,
12534                },
12535            },
12536            new_text: "".to_string(),
12537        }]),
12538        ..Default::default()
12539    };
12540
12541    let closure_completion_item = completion_item.clone();
12542    let counter = Arc::new(AtomicUsize::new(0));
12543    let counter_clone = counter.clone();
12544    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12545        let task_completion_item = closure_completion_item.clone();
12546        counter_clone.fetch_add(1, atomic::Ordering::Release);
12547        async move {
12548            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12549                is_incomplete: true,
12550                item_defaults: None,
12551                items: vec![task_completion_item],
12552            })))
12553        }
12554    });
12555
12556    cx.condition(|editor, _| editor.context_menu_visible())
12557        .await;
12558    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12559    assert!(request.next().await.is_some());
12560    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12561
12562    cx.simulate_keystrokes("S o m");
12563    cx.condition(|editor, _| editor.context_menu_visible())
12564        .await;
12565    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12566    assert!(request.next().await.is_some());
12567    assert!(request.next().await.is_some());
12568    assert!(request.next().await.is_some());
12569    request.close();
12570    assert!(request.next().await.is_none());
12571    assert_eq!(
12572        counter.load(atomic::Ordering::Acquire),
12573        4,
12574        "With the completions menu open, only one LSP request should happen per input"
12575    );
12576}
12577
12578#[gpui::test]
12579async fn test_toggle_comment(cx: &mut TestAppContext) {
12580    init_test(cx, |_| {});
12581    let mut cx = EditorTestContext::new(cx).await;
12582    let language = Arc::new(Language::new(
12583        LanguageConfig {
12584            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12585            ..Default::default()
12586        },
12587        Some(tree_sitter_rust::LANGUAGE.into()),
12588    ));
12589    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12590
12591    // If multiple selections intersect a line, the line is only toggled once.
12592    cx.set_state(indoc! {"
12593        fn a() {
12594            «//b();
12595            ˇ»// «c();
12596            //ˇ»  d();
12597        }
12598    "});
12599
12600    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12601
12602    cx.assert_editor_state(indoc! {"
12603        fn a() {
12604            «b();
12605            c();
12606            ˇ» d();
12607        }
12608    "});
12609
12610    // The comment prefix is inserted at the same column for every line in a
12611    // selection.
12612    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12613
12614    cx.assert_editor_state(indoc! {"
12615        fn a() {
12616            // «b();
12617            // c();
12618            ˇ»//  d();
12619        }
12620    "});
12621
12622    // If a selection ends at the beginning of a line, that line is not toggled.
12623    cx.set_selections_state(indoc! {"
12624        fn a() {
12625            // b();
12626            «// c();
12627        ˇ»    //  d();
12628        }
12629    "});
12630
12631    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12632
12633    cx.assert_editor_state(indoc! {"
12634        fn a() {
12635            // b();
12636            «c();
12637        ˇ»    //  d();
12638        }
12639    "});
12640
12641    // If a selection span a single line and is empty, the line is toggled.
12642    cx.set_state(indoc! {"
12643        fn a() {
12644            a();
12645            b();
12646        ˇ
12647        }
12648    "});
12649
12650    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12651
12652    cx.assert_editor_state(indoc! {"
12653        fn a() {
12654            a();
12655            b();
12656        //•ˇ
12657        }
12658    "});
12659
12660    // If a selection span multiple lines, empty lines are not toggled.
12661    cx.set_state(indoc! {"
12662        fn a() {
12663            «a();
12664
12665            c();ˇ»
12666        }
12667    "});
12668
12669    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12670
12671    cx.assert_editor_state(indoc! {"
12672        fn a() {
12673            // «a();
12674
12675            // c();ˇ»
12676        }
12677    "});
12678
12679    // If a selection includes multiple comment prefixes, all lines are uncommented.
12680    cx.set_state(indoc! {"
12681        fn a() {
12682            «// a();
12683            /// b();
12684            //! c();ˇ»
12685        }
12686    "});
12687
12688    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12689
12690    cx.assert_editor_state(indoc! {"
12691        fn a() {
12692            «a();
12693            b();
12694            c();ˇ»
12695        }
12696    "});
12697}
12698
12699#[gpui::test]
12700async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12701    init_test(cx, |_| {});
12702    let mut cx = EditorTestContext::new(cx).await;
12703    let language = Arc::new(Language::new(
12704        LanguageConfig {
12705            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12706            ..Default::default()
12707        },
12708        Some(tree_sitter_rust::LANGUAGE.into()),
12709    ));
12710    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12711
12712    let toggle_comments = &ToggleComments {
12713        advance_downwards: false,
12714        ignore_indent: true,
12715    };
12716
12717    // If multiple selections intersect a line, the line is only toggled once.
12718    cx.set_state(indoc! {"
12719        fn a() {
12720        //    «b();
12721        //    c();
12722        //    ˇ» d();
12723        }
12724    "});
12725
12726    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12727
12728    cx.assert_editor_state(indoc! {"
12729        fn a() {
12730            «b();
12731            c();
12732            ˇ» d();
12733        }
12734    "});
12735
12736    // The comment prefix is inserted at the beginning of each line
12737    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12738
12739    cx.assert_editor_state(indoc! {"
12740        fn a() {
12741        //    «b();
12742        //    c();
12743        //    ˇ» d();
12744        }
12745    "});
12746
12747    // If a selection ends at the beginning of a line, that line is not toggled.
12748    cx.set_selections_state(indoc! {"
12749        fn a() {
12750        //    b();
12751        //    «c();
12752        ˇ»//     d();
12753        }
12754    "});
12755
12756    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12757
12758    cx.assert_editor_state(indoc! {"
12759        fn a() {
12760        //    b();
12761            «c();
12762        ˇ»//     d();
12763        }
12764    "});
12765
12766    // If a selection span a single line and is empty, the line is toggled.
12767    cx.set_state(indoc! {"
12768        fn a() {
12769            a();
12770            b();
12771        ˇ
12772        }
12773    "});
12774
12775    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12776
12777    cx.assert_editor_state(indoc! {"
12778        fn a() {
12779            a();
12780            b();
12781        //ˇ
12782        }
12783    "});
12784
12785    // If a selection span multiple lines, empty lines are not toggled.
12786    cx.set_state(indoc! {"
12787        fn a() {
12788            «a();
12789
12790            c();ˇ»
12791        }
12792    "});
12793
12794    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12795
12796    cx.assert_editor_state(indoc! {"
12797        fn a() {
12798        //    «a();
12799
12800        //    c();ˇ»
12801        }
12802    "});
12803
12804    // If a selection includes multiple comment prefixes, all lines are uncommented.
12805    cx.set_state(indoc! {"
12806        fn a() {
12807        //    «a();
12808        ///    b();
12809        //!    c();ˇ»
12810        }
12811    "});
12812
12813    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12814
12815    cx.assert_editor_state(indoc! {"
12816        fn a() {
12817            «a();
12818            b();
12819            c();ˇ»
12820        }
12821    "});
12822}
12823
12824#[gpui::test]
12825async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12826    init_test(cx, |_| {});
12827
12828    let language = Arc::new(Language::new(
12829        LanguageConfig {
12830            line_comments: vec!["// ".into()],
12831            ..Default::default()
12832        },
12833        Some(tree_sitter_rust::LANGUAGE.into()),
12834    ));
12835
12836    let mut cx = EditorTestContext::new(cx).await;
12837
12838    cx.language_registry().add(language.clone());
12839    cx.update_buffer(|buffer, cx| {
12840        buffer.set_language(Some(language), cx);
12841    });
12842
12843    let toggle_comments = &ToggleComments {
12844        advance_downwards: true,
12845        ignore_indent: false,
12846    };
12847
12848    // Single cursor on one line -> advance
12849    // Cursor moves horizontally 3 characters as well on non-blank line
12850    cx.set_state(indoc!(
12851        "fn a() {
12852             ˇdog();
12853             cat();
12854        }"
12855    ));
12856    cx.update_editor(|editor, window, cx| {
12857        editor.toggle_comments(toggle_comments, window, cx);
12858    });
12859    cx.assert_editor_state(indoc!(
12860        "fn a() {
12861             // dog();
12862             catˇ();
12863        }"
12864    ));
12865
12866    // Single selection on one line -> don't advance
12867    cx.set_state(indoc!(
12868        "fn a() {
12869             «dog()ˇ»;
12870             cat();
12871        }"
12872    ));
12873    cx.update_editor(|editor, window, cx| {
12874        editor.toggle_comments(toggle_comments, window, cx);
12875    });
12876    cx.assert_editor_state(indoc!(
12877        "fn a() {
12878             // «dog()ˇ»;
12879             cat();
12880        }"
12881    ));
12882
12883    // Multiple cursors on one line -> advance
12884    cx.set_state(indoc!(
12885        "fn a() {
12886             ˇdˇog();
12887             cat();
12888        }"
12889    ));
12890    cx.update_editor(|editor, window, cx| {
12891        editor.toggle_comments(toggle_comments, window, cx);
12892    });
12893    cx.assert_editor_state(indoc!(
12894        "fn a() {
12895             // dog();
12896             catˇ(ˇ);
12897        }"
12898    ));
12899
12900    // Multiple cursors on one line, with selection -> don't advance
12901    cx.set_state(indoc!(
12902        "fn a() {
12903             ˇdˇog«()ˇ»;
12904             cat();
12905        }"
12906    ));
12907    cx.update_editor(|editor, window, cx| {
12908        editor.toggle_comments(toggle_comments, window, cx);
12909    });
12910    cx.assert_editor_state(indoc!(
12911        "fn a() {
12912             // ˇdˇog«()ˇ»;
12913             cat();
12914        }"
12915    ));
12916
12917    // Single cursor on one line -> advance
12918    // Cursor moves to column 0 on blank line
12919    cx.set_state(indoc!(
12920        "fn a() {
12921             ˇdog();
12922
12923             cat();
12924        }"
12925    ));
12926    cx.update_editor(|editor, window, cx| {
12927        editor.toggle_comments(toggle_comments, window, cx);
12928    });
12929    cx.assert_editor_state(indoc!(
12930        "fn a() {
12931             // dog();
12932        ˇ
12933             cat();
12934        }"
12935    ));
12936
12937    // Single cursor on one line -> advance
12938    // Cursor starts and ends at column 0
12939    cx.set_state(indoc!(
12940        "fn a() {
12941         ˇ    dog();
12942             cat();
12943        }"
12944    ));
12945    cx.update_editor(|editor, window, cx| {
12946        editor.toggle_comments(toggle_comments, window, cx);
12947    });
12948    cx.assert_editor_state(indoc!(
12949        "fn a() {
12950             // dog();
12951         ˇ    cat();
12952        }"
12953    ));
12954}
12955
12956#[gpui::test]
12957async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12958    init_test(cx, |_| {});
12959
12960    let mut cx = EditorTestContext::new(cx).await;
12961
12962    let html_language = Arc::new(
12963        Language::new(
12964            LanguageConfig {
12965                name: "HTML".into(),
12966                block_comment: Some(("<!-- ".into(), " -->".into())),
12967                ..Default::default()
12968            },
12969            Some(tree_sitter_html::LANGUAGE.into()),
12970        )
12971        .with_injection_query(
12972            r#"
12973            (script_element
12974                (raw_text) @injection.content
12975                (#set! injection.language "javascript"))
12976            "#,
12977        )
12978        .unwrap(),
12979    );
12980
12981    let javascript_language = Arc::new(Language::new(
12982        LanguageConfig {
12983            name: "JavaScript".into(),
12984            line_comments: vec!["// ".into()],
12985            ..Default::default()
12986        },
12987        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12988    ));
12989
12990    cx.language_registry().add(html_language.clone());
12991    cx.language_registry().add(javascript_language.clone());
12992    cx.update_buffer(|buffer, cx| {
12993        buffer.set_language(Some(html_language), cx);
12994    });
12995
12996    // Toggle comments for empty selections
12997    cx.set_state(
12998        &r#"
12999            <p>A</p>ˇ
13000            <p>B</p>ˇ
13001            <p>C</p>ˇ
13002        "#
13003        .unindent(),
13004    );
13005    cx.update_editor(|editor, window, cx| {
13006        editor.toggle_comments(&ToggleComments::default(), window, cx)
13007    });
13008    cx.assert_editor_state(
13009        &r#"
13010            <!-- <p>A</p>ˇ -->
13011            <!-- <p>B</p>ˇ -->
13012            <!-- <p>C</p>ˇ -->
13013        "#
13014        .unindent(),
13015    );
13016    cx.update_editor(|editor, window, cx| {
13017        editor.toggle_comments(&ToggleComments::default(), window, cx)
13018    });
13019    cx.assert_editor_state(
13020        &r#"
13021            <p>A</p>ˇ
13022            <p>B</p>ˇ
13023            <p>C</p>ˇ
13024        "#
13025        .unindent(),
13026    );
13027
13028    // Toggle comments for mixture of empty and non-empty selections, where
13029    // multiple selections occupy a given line.
13030    cx.set_state(
13031        &r#"
13032            <p>A«</p>
13033            <p>ˇ»B</p>ˇ
13034            <p>C«</p>
13035            <p>ˇ»D</p>ˇ
13036        "#
13037        .unindent(),
13038    );
13039
13040    cx.update_editor(|editor, window, cx| {
13041        editor.toggle_comments(&ToggleComments::default(), window, cx)
13042    });
13043    cx.assert_editor_state(
13044        &r#"
13045            <!-- <p>A«</p>
13046            <p>ˇ»B</p>ˇ -->
13047            <!-- <p>C«</p>
13048            <p>ˇ»D</p>ˇ -->
13049        "#
13050        .unindent(),
13051    );
13052    cx.update_editor(|editor, window, cx| {
13053        editor.toggle_comments(&ToggleComments::default(), window, cx)
13054    });
13055    cx.assert_editor_state(
13056        &r#"
13057            <p>A«</p>
13058            <p>ˇ»B</p>ˇ
13059            <p>C«</p>
13060            <p>ˇ»D</p>ˇ
13061        "#
13062        .unindent(),
13063    );
13064
13065    // Toggle comments when different languages are active for different
13066    // selections.
13067    cx.set_state(
13068        &r#"
13069            ˇ<script>
13070                ˇvar x = new Y();
13071            ˇ</script>
13072        "#
13073        .unindent(),
13074    );
13075    cx.executor().run_until_parked();
13076    cx.update_editor(|editor, window, cx| {
13077        editor.toggle_comments(&ToggleComments::default(), window, cx)
13078    });
13079    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13080    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13081    cx.assert_editor_state(
13082        &r#"
13083            <!-- ˇ<script> -->
13084                // ˇvar x = new Y();
13085            <!-- ˇ</script> -->
13086        "#
13087        .unindent(),
13088    );
13089}
13090
13091#[gpui::test]
13092fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13093    init_test(cx, |_| {});
13094
13095    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13096    let multibuffer = cx.new(|cx| {
13097        let mut multibuffer = MultiBuffer::new(ReadWrite);
13098        multibuffer.push_excerpts(
13099            buffer.clone(),
13100            [
13101                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13102                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13103            ],
13104            cx,
13105        );
13106        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13107        multibuffer
13108    });
13109
13110    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13111    editor.update_in(cx, |editor, window, cx| {
13112        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13113        editor.change_selections(None, window, cx, |s| {
13114            s.select_ranges([
13115                Point::new(0, 0)..Point::new(0, 0),
13116                Point::new(1, 0)..Point::new(1, 0),
13117            ])
13118        });
13119
13120        editor.handle_input("X", window, cx);
13121        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13122        assert_eq!(
13123            editor.selections.ranges(cx),
13124            [
13125                Point::new(0, 1)..Point::new(0, 1),
13126                Point::new(1, 1)..Point::new(1, 1),
13127            ]
13128        );
13129
13130        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13131        editor.change_selections(None, window, cx, |s| {
13132            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13133        });
13134        editor.backspace(&Default::default(), window, cx);
13135        assert_eq!(editor.text(cx), "Xa\nbbb");
13136        assert_eq!(
13137            editor.selections.ranges(cx),
13138            [Point::new(1, 0)..Point::new(1, 0)]
13139        );
13140
13141        editor.change_selections(None, window, cx, |s| {
13142            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13143        });
13144        editor.backspace(&Default::default(), window, cx);
13145        assert_eq!(editor.text(cx), "X\nbb");
13146        assert_eq!(
13147            editor.selections.ranges(cx),
13148            [Point::new(0, 1)..Point::new(0, 1)]
13149        );
13150    });
13151}
13152
13153#[gpui::test]
13154fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13155    init_test(cx, |_| {});
13156
13157    let markers = vec![('[', ']').into(), ('(', ')').into()];
13158    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13159        indoc! {"
13160            [aaaa
13161            (bbbb]
13162            cccc)",
13163        },
13164        markers.clone(),
13165    );
13166    let excerpt_ranges = markers.into_iter().map(|marker| {
13167        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13168        ExcerptRange::new(context.clone())
13169    });
13170    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13171    let multibuffer = cx.new(|cx| {
13172        let mut multibuffer = MultiBuffer::new(ReadWrite);
13173        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13174        multibuffer
13175    });
13176
13177    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13178    editor.update_in(cx, |editor, window, cx| {
13179        let (expected_text, selection_ranges) = marked_text_ranges(
13180            indoc! {"
13181                aaaa
13182                bˇbbb
13183                bˇbbˇb
13184                cccc"
13185            },
13186            true,
13187        );
13188        assert_eq!(editor.text(cx), expected_text);
13189        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13190
13191        editor.handle_input("X", window, cx);
13192
13193        let (expected_text, expected_selections) = marked_text_ranges(
13194            indoc! {"
13195                aaaa
13196                bXˇbbXb
13197                bXˇbbXˇb
13198                cccc"
13199            },
13200            false,
13201        );
13202        assert_eq!(editor.text(cx), expected_text);
13203        assert_eq!(editor.selections.ranges(cx), expected_selections);
13204
13205        editor.newline(&Newline, window, cx);
13206        let (expected_text, expected_selections) = marked_text_ranges(
13207            indoc! {"
13208                aaaa
13209                bX
13210                ˇbbX
13211                b
13212                bX
13213                ˇbbX
13214                ˇb
13215                cccc"
13216            },
13217            false,
13218        );
13219        assert_eq!(editor.text(cx), expected_text);
13220        assert_eq!(editor.selections.ranges(cx), expected_selections);
13221    });
13222}
13223
13224#[gpui::test]
13225fn test_refresh_selections(cx: &mut TestAppContext) {
13226    init_test(cx, |_| {});
13227
13228    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13229    let mut excerpt1_id = None;
13230    let multibuffer = cx.new(|cx| {
13231        let mut multibuffer = MultiBuffer::new(ReadWrite);
13232        excerpt1_id = multibuffer
13233            .push_excerpts(
13234                buffer.clone(),
13235                [
13236                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13237                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13238                ],
13239                cx,
13240            )
13241            .into_iter()
13242            .next();
13243        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13244        multibuffer
13245    });
13246
13247    let editor = cx.add_window(|window, cx| {
13248        let mut editor = build_editor(multibuffer.clone(), window, cx);
13249        let snapshot = editor.snapshot(window, cx);
13250        editor.change_selections(None, window, cx, |s| {
13251            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13252        });
13253        editor.begin_selection(
13254            Point::new(2, 1).to_display_point(&snapshot),
13255            true,
13256            1,
13257            window,
13258            cx,
13259        );
13260        assert_eq!(
13261            editor.selections.ranges(cx),
13262            [
13263                Point::new(1, 3)..Point::new(1, 3),
13264                Point::new(2, 1)..Point::new(2, 1),
13265            ]
13266        );
13267        editor
13268    });
13269
13270    // Refreshing selections is a no-op when excerpts haven't changed.
13271    _ = editor.update(cx, |editor, window, cx| {
13272        editor.change_selections(None, window, cx, |s| s.refresh());
13273        assert_eq!(
13274            editor.selections.ranges(cx),
13275            [
13276                Point::new(1, 3)..Point::new(1, 3),
13277                Point::new(2, 1)..Point::new(2, 1),
13278            ]
13279        );
13280    });
13281
13282    multibuffer.update(cx, |multibuffer, cx| {
13283        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13284    });
13285    _ = editor.update(cx, |editor, window, cx| {
13286        // Removing an excerpt causes the first selection to become degenerate.
13287        assert_eq!(
13288            editor.selections.ranges(cx),
13289            [
13290                Point::new(0, 0)..Point::new(0, 0),
13291                Point::new(0, 1)..Point::new(0, 1)
13292            ]
13293        );
13294
13295        // Refreshing selections will relocate the first selection to the original buffer
13296        // location.
13297        editor.change_selections(None, window, cx, |s| s.refresh());
13298        assert_eq!(
13299            editor.selections.ranges(cx),
13300            [
13301                Point::new(0, 1)..Point::new(0, 1),
13302                Point::new(0, 3)..Point::new(0, 3)
13303            ]
13304        );
13305        assert!(editor.selections.pending_anchor().is_some());
13306    });
13307}
13308
13309#[gpui::test]
13310fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13311    init_test(cx, |_| {});
13312
13313    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13314    let mut excerpt1_id = None;
13315    let multibuffer = cx.new(|cx| {
13316        let mut multibuffer = MultiBuffer::new(ReadWrite);
13317        excerpt1_id = multibuffer
13318            .push_excerpts(
13319                buffer.clone(),
13320                [
13321                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13322                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13323                ],
13324                cx,
13325            )
13326            .into_iter()
13327            .next();
13328        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13329        multibuffer
13330    });
13331
13332    let editor = cx.add_window(|window, cx| {
13333        let mut editor = build_editor(multibuffer.clone(), window, cx);
13334        let snapshot = editor.snapshot(window, cx);
13335        editor.begin_selection(
13336            Point::new(1, 3).to_display_point(&snapshot),
13337            false,
13338            1,
13339            window,
13340            cx,
13341        );
13342        assert_eq!(
13343            editor.selections.ranges(cx),
13344            [Point::new(1, 3)..Point::new(1, 3)]
13345        );
13346        editor
13347    });
13348
13349    multibuffer.update(cx, |multibuffer, cx| {
13350        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13351    });
13352    _ = editor.update(cx, |editor, window, cx| {
13353        assert_eq!(
13354            editor.selections.ranges(cx),
13355            [Point::new(0, 0)..Point::new(0, 0)]
13356        );
13357
13358        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13359        editor.change_selections(None, window, cx, |s| s.refresh());
13360        assert_eq!(
13361            editor.selections.ranges(cx),
13362            [Point::new(0, 3)..Point::new(0, 3)]
13363        );
13364        assert!(editor.selections.pending_anchor().is_some());
13365    });
13366}
13367
13368#[gpui::test]
13369async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13370    init_test(cx, |_| {});
13371
13372    let language = Arc::new(
13373        Language::new(
13374            LanguageConfig {
13375                brackets: BracketPairConfig {
13376                    pairs: vec![
13377                        BracketPair {
13378                            start: "{".to_string(),
13379                            end: "}".to_string(),
13380                            close: true,
13381                            surround: true,
13382                            newline: true,
13383                        },
13384                        BracketPair {
13385                            start: "/* ".to_string(),
13386                            end: " */".to_string(),
13387                            close: true,
13388                            surround: true,
13389                            newline: true,
13390                        },
13391                    ],
13392                    ..Default::default()
13393                },
13394                ..Default::default()
13395            },
13396            Some(tree_sitter_rust::LANGUAGE.into()),
13397        )
13398        .with_indents_query("")
13399        .unwrap(),
13400    );
13401
13402    let text = concat!(
13403        "{   }\n",     //
13404        "  x\n",       //
13405        "  /*   */\n", //
13406        "x\n",         //
13407        "{{} }\n",     //
13408    );
13409
13410    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13411    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13412    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13413    editor
13414        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13415        .await;
13416
13417    editor.update_in(cx, |editor, window, cx| {
13418        editor.change_selections(None, window, cx, |s| {
13419            s.select_display_ranges([
13420                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13421                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13422                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13423            ])
13424        });
13425        editor.newline(&Newline, window, cx);
13426
13427        assert_eq!(
13428            editor.buffer().read(cx).read(cx).text(),
13429            concat!(
13430                "{ \n",    // Suppress rustfmt
13431                "\n",      //
13432                "}\n",     //
13433                "  x\n",   //
13434                "  /* \n", //
13435                "  \n",    //
13436                "  */\n",  //
13437                "x\n",     //
13438                "{{} \n",  //
13439                "}\n",     //
13440            )
13441        );
13442    });
13443}
13444
13445#[gpui::test]
13446fn test_highlighted_ranges(cx: &mut TestAppContext) {
13447    init_test(cx, |_| {});
13448
13449    let editor = cx.add_window(|window, cx| {
13450        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13451        build_editor(buffer.clone(), window, cx)
13452    });
13453
13454    _ = editor.update(cx, |editor, window, cx| {
13455        struct Type1;
13456        struct Type2;
13457
13458        let buffer = editor.buffer.read(cx).snapshot(cx);
13459
13460        let anchor_range =
13461            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13462
13463        editor.highlight_background::<Type1>(
13464            &[
13465                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13466                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13467                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13468                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13469            ],
13470            |_| Hsla::red(),
13471            cx,
13472        );
13473        editor.highlight_background::<Type2>(
13474            &[
13475                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13476                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13477                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13478                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13479            ],
13480            |_| Hsla::green(),
13481            cx,
13482        );
13483
13484        let snapshot = editor.snapshot(window, cx);
13485        let mut highlighted_ranges = editor.background_highlights_in_range(
13486            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13487            &snapshot,
13488            cx.theme().colors(),
13489        );
13490        // Enforce a consistent ordering based on color without relying on the ordering of the
13491        // highlight's `TypeId` which is non-executor.
13492        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13493        assert_eq!(
13494            highlighted_ranges,
13495            &[
13496                (
13497                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13498                    Hsla::red(),
13499                ),
13500                (
13501                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13502                    Hsla::red(),
13503                ),
13504                (
13505                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13506                    Hsla::green(),
13507                ),
13508                (
13509                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13510                    Hsla::green(),
13511                ),
13512            ]
13513        );
13514        assert_eq!(
13515            editor.background_highlights_in_range(
13516                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13517                &snapshot,
13518                cx.theme().colors(),
13519            ),
13520            &[(
13521                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13522                Hsla::red(),
13523            )]
13524        );
13525    });
13526}
13527
13528#[gpui::test]
13529async fn test_following(cx: &mut TestAppContext) {
13530    init_test(cx, |_| {});
13531
13532    let fs = FakeFs::new(cx.executor());
13533    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13534
13535    let buffer = project.update(cx, |project, cx| {
13536        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13537        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13538    });
13539    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13540    let follower = cx.update(|cx| {
13541        cx.open_window(
13542            WindowOptions {
13543                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13544                    gpui::Point::new(px(0.), px(0.)),
13545                    gpui::Point::new(px(10.), px(80.)),
13546                ))),
13547                ..Default::default()
13548            },
13549            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13550        )
13551        .unwrap()
13552    });
13553
13554    let is_still_following = Rc::new(RefCell::new(true));
13555    let follower_edit_event_count = Rc::new(RefCell::new(0));
13556    let pending_update = Rc::new(RefCell::new(None));
13557    let leader_entity = leader.root(cx).unwrap();
13558    let follower_entity = follower.root(cx).unwrap();
13559    _ = follower.update(cx, {
13560        let update = pending_update.clone();
13561        let is_still_following = is_still_following.clone();
13562        let follower_edit_event_count = follower_edit_event_count.clone();
13563        |_, window, cx| {
13564            cx.subscribe_in(
13565                &leader_entity,
13566                window,
13567                move |_, leader, event, window, cx| {
13568                    leader.read(cx).add_event_to_update_proto(
13569                        event,
13570                        &mut update.borrow_mut(),
13571                        window,
13572                        cx,
13573                    );
13574                },
13575            )
13576            .detach();
13577
13578            cx.subscribe_in(
13579                &follower_entity,
13580                window,
13581                move |_, _, event: &EditorEvent, _window, _cx| {
13582                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13583                        *is_still_following.borrow_mut() = false;
13584                    }
13585
13586                    if let EditorEvent::BufferEdited = event {
13587                        *follower_edit_event_count.borrow_mut() += 1;
13588                    }
13589                },
13590            )
13591            .detach();
13592        }
13593    });
13594
13595    // Update the selections only
13596    _ = leader.update(cx, |leader, window, cx| {
13597        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13598    });
13599    follower
13600        .update(cx, |follower, window, cx| {
13601            follower.apply_update_proto(
13602                &project,
13603                pending_update.borrow_mut().take().unwrap(),
13604                window,
13605                cx,
13606            )
13607        })
13608        .unwrap()
13609        .await
13610        .unwrap();
13611    _ = follower.update(cx, |follower, _, cx| {
13612        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13613    });
13614    assert!(*is_still_following.borrow());
13615    assert_eq!(*follower_edit_event_count.borrow(), 0);
13616
13617    // Update the scroll position only
13618    _ = leader.update(cx, |leader, window, cx| {
13619        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13620    });
13621    follower
13622        .update(cx, |follower, window, cx| {
13623            follower.apply_update_proto(
13624                &project,
13625                pending_update.borrow_mut().take().unwrap(),
13626                window,
13627                cx,
13628            )
13629        })
13630        .unwrap()
13631        .await
13632        .unwrap();
13633    assert_eq!(
13634        follower
13635            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13636            .unwrap(),
13637        gpui::Point::new(1.5, 3.5)
13638    );
13639    assert!(*is_still_following.borrow());
13640    assert_eq!(*follower_edit_event_count.borrow(), 0);
13641
13642    // Update the selections and scroll position. The follower's scroll position is updated
13643    // via autoscroll, not via the leader's exact scroll position.
13644    _ = leader.update(cx, |leader, window, cx| {
13645        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13646        leader.request_autoscroll(Autoscroll::newest(), cx);
13647        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13648    });
13649    follower
13650        .update(cx, |follower, window, cx| {
13651            follower.apply_update_proto(
13652                &project,
13653                pending_update.borrow_mut().take().unwrap(),
13654                window,
13655                cx,
13656            )
13657        })
13658        .unwrap()
13659        .await
13660        .unwrap();
13661    _ = follower.update(cx, |follower, _, cx| {
13662        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13663        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13664    });
13665    assert!(*is_still_following.borrow());
13666
13667    // Creating a pending selection that precedes another selection
13668    _ = leader.update(cx, |leader, window, cx| {
13669        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13670        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13671    });
13672    follower
13673        .update(cx, |follower, window, cx| {
13674            follower.apply_update_proto(
13675                &project,
13676                pending_update.borrow_mut().take().unwrap(),
13677                window,
13678                cx,
13679            )
13680        })
13681        .unwrap()
13682        .await
13683        .unwrap();
13684    _ = follower.update(cx, |follower, _, cx| {
13685        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13686    });
13687    assert!(*is_still_following.borrow());
13688
13689    // Extend the pending selection so that it surrounds another selection
13690    _ = leader.update(cx, |leader, window, cx| {
13691        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13692    });
13693    follower
13694        .update(cx, |follower, window, cx| {
13695            follower.apply_update_proto(
13696                &project,
13697                pending_update.borrow_mut().take().unwrap(),
13698                window,
13699                cx,
13700            )
13701        })
13702        .unwrap()
13703        .await
13704        .unwrap();
13705    _ = follower.update(cx, |follower, _, cx| {
13706        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13707    });
13708
13709    // Scrolling locally breaks the follow
13710    _ = follower.update(cx, |follower, window, cx| {
13711        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13712        follower.set_scroll_anchor(
13713            ScrollAnchor {
13714                anchor: top_anchor,
13715                offset: gpui::Point::new(0.0, 0.5),
13716            },
13717            window,
13718            cx,
13719        );
13720    });
13721    assert!(!(*is_still_following.borrow()));
13722}
13723
13724#[gpui::test]
13725async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13726    init_test(cx, |_| {});
13727
13728    let fs = FakeFs::new(cx.executor());
13729    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13730    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13731    let pane = workspace
13732        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13733        .unwrap();
13734
13735    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13736
13737    let leader = pane.update_in(cx, |_, window, cx| {
13738        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13739        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13740    });
13741
13742    // Start following the editor when it has no excerpts.
13743    let mut state_message =
13744        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13745    let workspace_entity = workspace.root(cx).unwrap();
13746    let follower_1 = cx
13747        .update_window(*workspace.deref(), |_, window, cx| {
13748            Editor::from_state_proto(
13749                workspace_entity,
13750                ViewId {
13751                    creator: CollaboratorId::PeerId(PeerId::default()),
13752                    id: 0,
13753                },
13754                &mut state_message,
13755                window,
13756                cx,
13757            )
13758        })
13759        .unwrap()
13760        .unwrap()
13761        .await
13762        .unwrap();
13763
13764    let update_message = Rc::new(RefCell::new(None));
13765    follower_1.update_in(cx, {
13766        let update = update_message.clone();
13767        |_, window, cx| {
13768            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13769                leader.read(cx).add_event_to_update_proto(
13770                    event,
13771                    &mut update.borrow_mut(),
13772                    window,
13773                    cx,
13774                );
13775            })
13776            .detach();
13777        }
13778    });
13779
13780    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13781        (
13782            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13783            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13784        )
13785    });
13786
13787    // Insert some excerpts.
13788    leader.update(cx, |leader, cx| {
13789        leader.buffer.update(cx, |multibuffer, cx| {
13790            multibuffer.set_excerpts_for_path(
13791                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13792                buffer_1.clone(),
13793                vec![
13794                    Point::row_range(0..3),
13795                    Point::row_range(1..6),
13796                    Point::row_range(12..15),
13797                ],
13798                0,
13799                cx,
13800            );
13801            multibuffer.set_excerpts_for_path(
13802                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13803                buffer_2.clone(),
13804                vec![Point::row_range(0..6), Point::row_range(8..12)],
13805                0,
13806                cx,
13807            );
13808        });
13809    });
13810
13811    // Apply the update of adding the excerpts.
13812    follower_1
13813        .update_in(cx, |follower, window, cx| {
13814            follower.apply_update_proto(
13815                &project,
13816                update_message.borrow().clone().unwrap(),
13817                window,
13818                cx,
13819            )
13820        })
13821        .await
13822        .unwrap();
13823    assert_eq!(
13824        follower_1.update(cx, |editor, cx| editor.text(cx)),
13825        leader.update(cx, |editor, cx| editor.text(cx))
13826    );
13827    update_message.borrow_mut().take();
13828
13829    // Start following separately after it already has excerpts.
13830    let mut state_message =
13831        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13832    let workspace_entity = workspace.root(cx).unwrap();
13833    let follower_2 = cx
13834        .update_window(*workspace.deref(), |_, window, cx| {
13835            Editor::from_state_proto(
13836                workspace_entity,
13837                ViewId {
13838                    creator: CollaboratorId::PeerId(PeerId::default()),
13839                    id: 0,
13840                },
13841                &mut state_message,
13842                window,
13843                cx,
13844            )
13845        })
13846        .unwrap()
13847        .unwrap()
13848        .await
13849        .unwrap();
13850    assert_eq!(
13851        follower_2.update(cx, |editor, cx| editor.text(cx)),
13852        leader.update(cx, |editor, cx| editor.text(cx))
13853    );
13854
13855    // Remove some excerpts.
13856    leader.update(cx, |leader, cx| {
13857        leader.buffer.update(cx, |multibuffer, cx| {
13858            let excerpt_ids = multibuffer.excerpt_ids();
13859            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13860            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13861        });
13862    });
13863
13864    // Apply the update of removing the excerpts.
13865    follower_1
13866        .update_in(cx, |follower, window, cx| {
13867            follower.apply_update_proto(
13868                &project,
13869                update_message.borrow().clone().unwrap(),
13870                window,
13871                cx,
13872            )
13873        })
13874        .await
13875        .unwrap();
13876    follower_2
13877        .update_in(cx, |follower, window, cx| {
13878            follower.apply_update_proto(
13879                &project,
13880                update_message.borrow().clone().unwrap(),
13881                window,
13882                cx,
13883            )
13884        })
13885        .await
13886        .unwrap();
13887    update_message.borrow_mut().take();
13888    assert_eq!(
13889        follower_1.update(cx, |editor, cx| editor.text(cx)),
13890        leader.update(cx, |editor, cx| editor.text(cx))
13891    );
13892}
13893
13894#[gpui::test]
13895async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13896    init_test(cx, |_| {});
13897
13898    let mut cx = EditorTestContext::new(cx).await;
13899    let lsp_store =
13900        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13901
13902    cx.set_state(indoc! {"
13903        ˇfn func(abc def: i32) -> u32 {
13904        }
13905    "});
13906
13907    cx.update(|_, cx| {
13908        lsp_store.update(cx, |lsp_store, cx| {
13909            lsp_store
13910                .update_diagnostics(
13911                    LanguageServerId(0),
13912                    lsp::PublishDiagnosticsParams {
13913                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13914                        version: None,
13915                        diagnostics: vec![
13916                            lsp::Diagnostic {
13917                                range: lsp::Range::new(
13918                                    lsp::Position::new(0, 11),
13919                                    lsp::Position::new(0, 12),
13920                                ),
13921                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13922                                ..Default::default()
13923                            },
13924                            lsp::Diagnostic {
13925                                range: lsp::Range::new(
13926                                    lsp::Position::new(0, 12),
13927                                    lsp::Position::new(0, 15),
13928                                ),
13929                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13930                                ..Default::default()
13931                            },
13932                            lsp::Diagnostic {
13933                                range: lsp::Range::new(
13934                                    lsp::Position::new(0, 25),
13935                                    lsp::Position::new(0, 28),
13936                                ),
13937                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13938                                ..Default::default()
13939                            },
13940                        ],
13941                    },
13942                    None,
13943                    DiagnosticSourceKind::Pushed,
13944                    &[],
13945                    cx,
13946                )
13947                .unwrap()
13948        });
13949    });
13950
13951    executor.run_until_parked();
13952
13953    cx.update_editor(|editor, window, cx| {
13954        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13955    });
13956
13957    cx.assert_editor_state(indoc! {"
13958        fn func(abc def: i32) -> ˇu32 {
13959        }
13960    "});
13961
13962    cx.update_editor(|editor, window, cx| {
13963        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13964    });
13965
13966    cx.assert_editor_state(indoc! {"
13967        fn func(abc ˇdef: i32) -> u32 {
13968        }
13969    "});
13970
13971    cx.update_editor(|editor, window, cx| {
13972        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13973    });
13974
13975    cx.assert_editor_state(indoc! {"
13976        fn func(abcˇ def: i32) -> u32 {
13977        }
13978    "});
13979
13980    cx.update_editor(|editor, window, cx| {
13981        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13982    });
13983
13984    cx.assert_editor_state(indoc! {"
13985        fn func(abc def: i32) -> ˇu32 {
13986        }
13987    "});
13988}
13989
13990#[gpui::test]
13991async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13992    init_test(cx, |_| {});
13993
13994    let mut cx = EditorTestContext::new(cx).await;
13995
13996    let diff_base = r#"
13997        use some::mod;
13998
13999        const A: u32 = 42;
14000
14001        fn main() {
14002            println!("hello");
14003
14004            println!("world");
14005        }
14006        "#
14007    .unindent();
14008
14009    // Edits are modified, removed, modified, added
14010    cx.set_state(
14011        &r#"
14012        use some::modified;
14013
14014        ˇ
14015        fn main() {
14016            println!("hello there");
14017
14018            println!("around the");
14019            println!("world");
14020        }
14021        "#
14022        .unindent(),
14023    );
14024
14025    cx.set_head_text(&diff_base);
14026    executor.run_until_parked();
14027
14028    cx.update_editor(|editor, window, cx| {
14029        //Wrap around the bottom of the buffer
14030        for _ in 0..3 {
14031            editor.go_to_next_hunk(&GoToHunk, window, cx);
14032        }
14033    });
14034
14035    cx.assert_editor_state(
14036        &r#"
14037        ˇuse some::modified;
14038
14039
14040        fn main() {
14041            println!("hello there");
14042
14043            println!("around the");
14044            println!("world");
14045        }
14046        "#
14047        .unindent(),
14048    );
14049
14050    cx.update_editor(|editor, window, cx| {
14051        //Wrap around the top of the buffer
14052        for _ in 0..2 {
14053            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14054        }
14055    });
14056
14057    cx.assert_editor_state(
14058        &r#"
14059        use some::modified;
14060
14061
14062        fn main() {
14063        ˇ    println!("hello there");
14064
14065            println!("around the");
14066            println!("world");
14067        }
14068        "#
14069        .unindent(),
14070    );
14071
14072    cx.update_editor(|editor, window, cx| {
14073        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14074    });
14075
14076    cx.assert_editor_state(
14077        &r#"
14078        use some::modified;
14079
14080        ˇ
14081        fn main() {
14082            println!("hello there");
14083
14084            println!("around the");
14085            println!("world");
14086        }
14087        "#
14088        .unindent(),
14089    );
14090
14091    cx.update_editor(|editor, window, cx| {
14092        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14093    });
14094
14095    cx.assert_editor_state(
14096        &r#"
14097        ˇuse some::modified;
14098
14099
14100        fn main() {
14101            println!("hello there");
14102
14103            println!("around the");
14104            println!("world");
14105        }
14106        "#
14107        .unindent(),
14108    );
14109
14110    cx.update_editor(|editor, window, cx| {
14111        for _ in 0..2 {
14112            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14113        }
14114    });
14115
14116    cx.assert_editor_state(
14117        &r#"
14118        use some::modified;
14119
14120
14121        fn main() {
14122        ˇ    println!("hello there");
14123
14124            println!("around the");
14125            println!("world");
14126        }
14127        "#
14128        .unindent(),
14129    );
14130
14131    cx.update_editor(|editor, window, cx| {
14132        editor.fold(&Fold, window, cx);
14133    });
14134
14135    cx.update_editor(|editor, window, cx| {
14136        editor.go_to_next_hunk(&GoToHunk, window, cx);
14137    });
14138
14139    cx.assert_editor_state(
14140        &r#"
14141        ˇuse some::modified;
14142
14143
14144        fn main() {
14145            println!("hello there");
14146
14147            println!("around the");
14148            println!("world");
14149        }
14150        "#
14151        .unindent(),
14152    );
14153}
14154
14155#[test]
14156fn test_split_words() {
14157    fn split(text: &str) -> Vec<&str> {
14158        split_words(text).collect()
14159    }
14160
14161    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14162    assert_eq!(split("hello_world"), &["hello_", "world"]);
14163    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14164    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14165    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14166    assert_eq!(split("helloworld"), &["helloworld"]);
14167
14168    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14169}
14170
14171#[gpui::test]
14172async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14173    init_test(cx, |_| {});
14174
14175    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14176    let mut assert = |before, after| {
14177        let _state_context = cx.set_state(before);
14178        cx.run_until_parked();
14179        cx.update_editor(|editor, window, cx| {
14180            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14181        });
14182        cx.run_until_parked();
14183        cx.assert_editor_state(after);
14184    };
14185
14186    // Outside bracket jumps to outside of matching bracket
14187    assert("console.logˇ(var);", "console.log(var)ˇ;");
14188    assert("console.log(var)ˇ;", "console.logˇ(var);");
14189
14190    // Inside bracket jumps to inside of matching bracket
14191    assert("console.log(ˇvar);", "console.log(varˇ);");
14192    assert("console.log(varˇ);", "console.log(ˇvar);");
14193
14194    // When outside a bracket and inside, favor jumping to the inside bracket
14195    assert(
14196        "console.log('foo', [1, 2, 3]ˇ);",
14197        "console.log(ˇ'foo', [1, 2, 3]);",
14198    );
14199    assert(
14200        "console.log(ˇ'foo', [1, 2, 3]);",
14201        "console.log('foo', [1, 2, 3]ˇ);",
14202    );
14203
14204    // Bias forward if two options are equally likely
14205    assert(
14206        "let result = curried_fun()ˇ();",
14207        "let result = curried_fun()()ˇ;",
14208    );
14209
14210    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14211    assert(
14212        indoc! {"
14213            function test() {
14214                console.log('test')ˇ
14215            }"},
14216        indoc! {"
14217            function test() {
14218                console.logˇ('test')
14219            }"},
14220    );
14221}
14222
14223#[gpui::test]
14224async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14225    init_test(cx, |_| {});
14226
14227    let fs = FakeFs::new(cx.executor());
14228    fs.insert_tree(
14229        path!("/a"),
14230        json!({
14231            "main.rs": "fn main() { let a = 5; }",
14232            "other.rs": "// Test file",
14233        }),
14234    )
14235    .await;
14236    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14237
14238    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14239    language_registry.add(Arc::new(Language::new(
14240        LanguageConfig {
14241            name: "Rust".into(),
14242            matcher: LanguageMatcher {
14243                path_suffixes: vec!["rs".to_string()],
14244                ..Default::default()
14245            },
14246            brackets: BracketPairConfig {
14247                pairs: vec![BracketPair {
14248                    start: "{".to_string(),
14249                    end: "}".to_string(),
14250                    close: true,
14251                    surround: true,
14252                    newline: true,
14253                }],
14254                disabled_scopes_by_bracket_ix: Vec::new(),
14255            },
14256            ..Default::default()
14257        },
14258        Some(tree_sitter_rust::LANGUAGE.into()),
14259    )));
14260    let mut fake_servers = language_registry.register_fake_lsp(
14261        "Rust",
14262        FakeLspAdapter {
14263            capabilities: lsp::ServerCapabilities {
14264                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14265                    first_trigger_character: "{".to_string(),
14266                    more_trigger_character: None,
14267                }),
14268                ..Default::default()
14269            },
14270            ..Default::default()
14271        },
14272    );
14273
14274    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14275
14276    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14277
14278    let worktree_id = workspace
14279        .update(cx, |workspace, _, cx| {
14280            workspace.project().update(cx, |project, cx| {
14281                project.worktrees(cx).next().unwrap().read(cx).id()
14282            })
14283        })
14284        .unwrap();
14285
14286    let buffer = project
14287        .update(cx, |project, cx| {
14288            project.open_local_buffer(path!("/a/main.rs"), cx)
14289        })
14290        .await
14291        .unwrap();
14292    let editor_handle = workspace
14293        .update(cx, |workspace, window, cx| {
14294            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14295        })
14296        .unwrap()
14297        .await
14298        .unwrap()
14299        .downcast::<Editor>()
14300        .unwrap();
14301
14302    cx.executor().start_waiting();
14303    let fake_server = fake_servers.next().await.unwrap();
14304
14305    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14306        |params, _| async move {
14307            assert_eq!(
14308                params.text_document_position.text_document.uri,
14309                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14310            );
14311            assert_eq!(
14312                params.text_document_position.position,
14313                lsp::Position::new(0, 21),
14314            );
14315
14316            Ok(Some(vec![lsp::TextEdit {
14317                new_text: "]".to_string(),
14318                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14319            }]))
14320        },
14321    );
14322
14323    editor_handle.update_in(cx, |editor, window, cx| {
14324        window.focus(&editor.focus_handle(cx));
14325        editor.change_selections(None, window, cx, |s| {
14326            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14327        });
14328        editor.handle_input("{", window, cx);
14329    });
14330
14331    cx.executor().run_until_parked();
14332
14333    buffer.update(cx, |buffer, _| {
14334        assert_eq!(
14335            buffer.text(),
14336            "fn main() { let a = {5}; }",
14337            "No extra braces from on type formatting should appear in the buffer"
14338        )
14339    });
14340}
14341
14342#[gpui::test]
14343async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14344    init_test(cx, |_| {});
14345
14346    let fs = FakeFs::new(cx.executor());
14347    fs.insert_tree(
14348        path!("/a"),
14349        json!({
14350            "main.rs": "fn main() { let a = 5; }",
14351            "other.rs": "// Test file",
14352        }),
14353    )
14354    .await;
14355
14356    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14357
14358    let server_restarts = Arc::new(AtomicUsize::new(0));
14359    let closure_restarts = Arc::clone(&server_restarts);
14360    let language_server_name = "test language server";
14361    let language_name: LanguageName = "Rust".into();
14362
14363    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14364    language_registry.add(Arc::new(Language::new(
14365        LanguageConfig {
14366            name: language_name.clone(),
14367            matcher: LanguageMatcher {
14368                path_suffixes: vec!["rs".to_string()],
14369                ..Default::default()
14370            },
14371            ..Default::default()
14372        },
14373        Some(tree_sitter_rust::LANGUAGE.into()),
14374    )));
14375    let mut fake_servers = language_registry.register_fake_lsp(
14376        "Rust",
14377        FakeLspAdapter {
14378            name: language_server_name,
14379            initialization_options: Some(json!({
14380                "testOptionValue": true
14381            })),
14382            initializer: Some(Box::new(move |fake_server| {
14383                let task_restarts = Arc::clone(&closure_restarts);
14384                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14385                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14386                    futures::future::ready(Ok(()))
14387                });
14388            })),
14389            ..Default::default()
14390        },
14391    );
14392
14393    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14394    let _buffer = project
14395        .update(cx, |project, cx| {
14396            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14397        })
14398        .await
14399        .unwrap();
14400    let _fake_server = fake_servers.next().await.unwrap();
14401    update_test_language_settings(cx, |language_settings| {
14402        language_settings.languages.insert(
14403            language_name.clone(),
14404            LanguageSettingsContent {
14405                tab_size: NonZeroU32::new(8),
14406                ..Default::default()
14407            },
14408        );
14409    });
14410    cx.executor().run_until_parked();
14411    assert_eq!(
14412        server_restarts.load(atomic::Ordering::Acquire),
14413        0,
14414        "Should not restart LSP server on an unrelated change"
14415    );
14416
14417    update_test_project_settings(cx, |project_settings| {
14418        project_settings.lsp.insert(
14419            "Some other server name".into(),
14420            LspSettings {
14421                binary: None,
14422                settings: None,
14423                initialization_options: Some(json!({
14424                    "some other init value": false
14425                })),
14426                enable_lsp_tasks: false,
14427            },
14428        );
14429    });
14430    cx.executor().run_until_parked();
14431    assert_eq!(
14432        server_restarts.load(atomic::Ordering::Acquire),
14433        0,
14434        "Should not restart LSP server on an unrelated LSP settings change"
14435    );
14436
14437    update_test_project_settings(cx, |project_settings| {
14438        project_settings.lsp.insert(
14439            language_server_name.into(),
14440            LspSettings {
14441                binary: None,
14442                settings: None,
14443                initialization_options: Some(json!({
14444                    "anotherInitValue": false
14445                })),
14446                enable_lsp_tasks: false,
14447            },
14448        );
14449    });
14450    cx.executor().run_until_parked();
14451    assert_eq!(
14452        server_restarts.load(atomic::Ordering::Acquire),
14453        1,
14454        "Should restart LSP server on a related LSP settings change"
14455    );
14456
14457    update_test_project_settings(cx, |project_settings| {
14458        project_settings.lsp.insert(
14459            language_server_name.into(),
14460            LspSettings {
14461                binary: None,
14462                settings: None,
14463                initialization_options: Some(json!({
14464                    "anotherInitValue": false
14465                })),
14466                enable_lsp_tasks: false,
14467            },
14468        );
14469    });
14470    cx.executor().run_until_parked();
14471    assert_eq!(
14472        server_restarts.load(atomic::Ordering::Acquire),
14473        1,
14474        "Should not restart LSP server on a related LSP settings change that is the same"
14475    );
14476
14477    update_test_project_settings(cx, |project_settings| {
14478        project_settings.lsp.insert(
14479            language_server_name.into(),
14480            LspSettings {
14481                binary: None,
14482                settings: None,
14483                initialization_options: None,
14484                enable_lsp_tasks: false,
14485            },
14486        );
14487    });
14488    cx.executor().run_until_parked();
14489    assert_eq!(
14490        server_restarts.load(atomic::Ordering::Acquire),
14491        2,
14492        "Should restart LSP server on another related LSP settings change"
14493    );
14494}
14495
14496#[gpui::test]
14497async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14498    init_test(cx, |_| {});
14499
14500    let mut cx = EditorLspTestContext::new_rust(
14501        lsp::ServerCapabilities {
14502            completion_provider: Some(lsp::CompletionOptions {
14503                trigger_characters: Some(vec![".".to_string()]),
14504                resolve_provider: Some(true),
14505                ..Default::default()
14506            }),
14507            ..Default::default()
14508        },
14509        cx,
14510    )
14511    .await;
14512
14513    cx.set_state("fn main() { let a = 2ˇ; }");
14514    cx.simulate_keystroke(".");
14515    let completion_item = lsp::CompletionItem {
14516        label: "some".into(),
14517        kind: Some(lsp::CompletionItemKind::SNIPPET),
14518        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14519        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14520            kind: lsp::MarkupKind::Markdown,
14521            value: "```rust\nSome(2)\n```".to_string(),
14522        })),
14523        deprecated: Some(false),
14524        sort_text: Some("fffffff2".to_string()),
14525        filter_text: Some("some".to_string()),
14526        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14527        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14528            range: lsp::Range {
14529                start: lsp::Position {
14530                    line: 0,
14531                    character: 22,
14532                },
14533                end: lsp::Position {
14534                    line: 0,
14535                    character: 22,
14536                },
14537            },
14538            new_text: "Some(2)".to_string(),
14539        })),
14540        additional_text_edits: Some(vec![lsp::TextEdit {
14541            range: lsp::Range {
14542                start: lsp::Position {
14543                    line: 0,
14544                    character: 20,
14545                },
14546                end: lsp::Position {
14547                    line: 0,
14548                    character: 22,
14549                },
14550            },
14551            new_text: "".to_string(),
14552        }]),
14553        ..Default::default()
14554    };
14555
14556    let closure_completion_item = completion_item.clone();
14557    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14558        let task_completion_item = closure_completion_item.clone();
14559        async move {
14560            Ok(Some(lsp::CompletionResponse::Array(vec![
14561                task_completion_item,
14562            ])))
14563        }
14564    });
14565
14566    request.next().await;
14567
14568    cx.condition(|editor, _| editor.context_menu_visible())
14569        .await;
14570    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14571        editor
14572            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14573            .unwrap()
14574    });
14575    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14576
14577    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14578        let task_completion_item = completion_item.clone();
14579        async move { Ok(task_completion_item) }
14580    })
14581    .next()
14582    .await
14583    .unwrap();
14584    apply_additional_edits.await.unwrap();
14585    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14586}
14587
14588#[gpui::test]
14589async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14590    init_test(cx, |_| {});
14591
14592    let mut cx = EditorLspTestContext::new_rust(
14593        lsp::ServerCapabilities {
14594            completion_provider: Some(lsp::CompletionOptions {
14595                trigger_characters: Some(vec![".".to_string()]),
14596                resolve_provider: Some(true),
14597                ..Default::default()
14598            }),
14599            ..Default::default()
14600        },
14601        cx,
14602    )
14603    .await;
14604
14605    cx.set_state("fn main() { let a = 2ˇ; }");
14606    cx.simulate_keystroke(".");
14607
14608    let item1 = lsp::CompletionItem {
14609        label: "method id()".to_string(),
14610        filter_text: Some("id".to_string()),
14611        detail: None,
14612        documentation: None,
14613        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14614            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14615            new_text: ".id".to_string(),
14616        })),
14617        ..lsp::CompletionItem::default()
14618    };
14619
14620    let item2 = lsp::CompletionItem {
14621        label: "other".to_string(),
14622        filter_text: Some("other".to_string()),
14623        detail: None,
14624        documentation: None,
14625        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14626            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14627            new_text: ".other".to_string(),
14628        })),
14629        ..lsp::CompletionItem::default()
14630    };
14631
14632    let item1 = item1.clone();
14633    cx.set_request_handler::<lsp::request::Completion, _, _>({
14634        let item1 = item1.clone();
14635        move |_, _, _| {
14636            let item1 = item1.clone();
14637            let item2 = item2.clone();
14638            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14639        }
14640    })
14641    .next()
14642    .await;
14643
14644    cx.condition(|editor, _| editor.context_menu_visible())
14645        .await;
14646    cx.update_editor(|editor, _, _| {
14647        let context_menu = editor.context_menu.borrow_mut();
14648        let context_menu = context_menu
14649            .as_ref()
14650            .expect("Should have the context menu deployed");
14651        match context_menu {
14652            CodeContextMenu::Completions(completions_menu) => {
14653                let completions = completions_menu.completions.borrow_mut();
14654                assert_eq!(
14655                    completions
14656                        .iter()
14657                        .map(|completion| &completion.label.text)
14658                        .collect::<Vec<_>>(),
14659                    vec!["method id()", "other"]
14660                )
14661            }
14662            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14663        }
14664    });
14665
14666    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14667        let item1 = item1.clone();
14668        move |_, item_to_resolve, _| {
14669            let item1 = item1.clone();
14670            async move {
14671                if item1 == item_to_resolve {
14672                    Ok(lsp::CompletionItem {
14673                        label: "method id()".to_string(),
14674                        filter_text: Some("id".to_string()),
14675                        detail: Some("Now resolved!".to_string()),
14676                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14677                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14678                            range: lsp::Range::new(
14679                                lsp::Position::new(0, 22),
14680                                lsp::Position::new(0, 22),
14681                            ),
14682                            new_text: ".id".to_string(),
14683                        })),
14684                        ..lsp::CompletionItem::default()
14685                    })
14686                } else {
14687                    Ok(item_to_resolve)
14688                }
14689            }
14690        }
14691    })
14692    .next()
14693    .await
14694    .unwrap();
14695    cx.run_until_parked();
14696
14697    cx.update_editor(|editor, window, cx| {
14698        editor.context_menu_next(&Default::default(), window, cx);
14699    });
14700
14701    cx.update_editor(|editor, _, _| {
14702        let context_menu = editor.context_menu.borrow_mut();
14703        let context_menu = context_menu
14704            .as_ref()
14705            .expect("Should have the context menu deployed");
14706        match context_menu {
14707            CodeContextMenu::Completions(completions_menu) => {
14708                let completions = completions_menu.completions.borrow_mut();
14709                assert_eq!(
14710                    completions
14711                        .iter()
14712                        .map(|completion| &completion.label.text)
14713                        .collect::<Vec<_>>(),
14714                    vec!["method id() Now resolved!", "other"],
14715                    "Should update first completion label, but not second as the filter text did not match."
14716                );
14717            }
14718            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14719        }
14720    });
14721}
14722
14723#[gpui::test]
14724async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14725    init_test(cx, |_| {});
14726    let mut cx = EditorLspTestContext::new_rust(
14727        lsp::ServerCapabilities {
14728            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14729            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14730            completion_provider: Some(lsp::CompletionOptions {
14731                resolve_provider: Some(true),
14732                ..Default::default()
14733            }),
14734            ..Default::default()
14735        },
14736        cx,
14737    )
14738    .await;
14739    cx.set_state(indoc! {"
14740        struct TestStruct {
14741            field: i32
14742        }
14743
14744        fn mainˇ() {
14745            let unused_var = 42;
14746            let test_struct = TestStruct { field: 42 };
14747        }
14748    "});
14749    let symbol_range = cx.lsp_range(indoc! {"
14750        struct TestStruct {
14751            field: i32
14752        }
14753
14754        «fn main»() {
14755            let unused_var = 42;
14756            let test_struct = TestStruct { field: 42 };
14757        }
14758    "});
14759    let mut hover_requests =
14760        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14761            Ok(Some(lsp::Hover {
14762                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14763                    kind: lsp::MarkupKind::Markdown,
14764                    value: "Function documentation".to_string(),
14765                }),
14766                range: Some(symbol_range),
14767            }))
14768        });
14769
14770    // Case 1: Test that code action menu hide hover popover
14771    cx.dispatch_action(Hover);
14772    hover_requests.next().await;
14773    cx.condition(|editor, _| editor.hover_state.visible()).await;
14774    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14775        move |_, _, _| async move {
14776            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14777                lsp::CodeAction {
14778                    title: "Remove unused variable".to_string(),
14779                    kind: Some(CodeActionKind::QUICKFIX),
14780                    edit: Some(lsp::WorkspaceEdit {
14781                        changes: Some(
14782                            [(
14783                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14784                                vec![lsp::TextEdit {
14785                                    range: lsp::Range::new(
14786                                        lsp::Position::new(5, 4),
14787                                        lsp::Position::new(5, 27),
14788                                    ),
14789                                    new_text: "".to_string(),
14790                                }],
14791                            )]
14792                            .into_iter()
14793                            .collect(),
14794                        ),
14795                        ..Default::default()
14796                    }),
14797                    ..Default::default()
14798                },
14799            )]))
14800        },
14801    );
14802    cx.update_editor(|editor, window, cx| {
14803        editor.toggle_code_actions(
14804            &ToggleCodeActions {
14805                deployed_from: None,
14806                quick_launch: false,
14807            },
14808            window,
14809            cx,
14810        );
14811    });
14812    code_action_requests.next().await;
14813    cx.run_until_parked();
14814    cx.condition(|editor, _| editor.context_menu_visible())
14815        .await;
14816    cx.update_editor(|editor, _, _| {
14817        assert!(
14818            !editor.hover_state.visible(),
14819            "Hover popover should be hidden when code action menu is shown"
14820        );
14821        // Hide code actions
14822        editor.context_menu.take();
14823    });
14824
14825    // Case 2: Test that code completions hide hover popover
14826    cx.dispatch_action(Hover);
14827    hover_requests.next().await;
14828    cx.condition(|editor, _| editor.hover_state.visible()).await;
14829    let counter = Arc::new(AtomicUsize::new(0));
14830    let mut completion_requests =
14831        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14832            let counter = counter.clone();
14833            async move {
14834                counter.fetch_add(1, atomic::Ordering::Release);
14835                Ok(Some(lsp::CompletionResponse::Array(vec![
14836                    lsp::CompletionItem {
14837                        label: "main".into(),
14838                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14839                        detail: Some("() -> ()".to_string()),
14840                        ..Default::default()
14841                    },
14842                    lsp::CompletionItem {
14843                        label: "TestStruct".into(),
14844                        kind: Some(lsp::CompletionItemKind::STRUCT),
14845                        detail: Some("struct TestStruct".to_string()),
14846                        ..Default::default()
14847                    },
14848                ])))
14849            }
14850        });
14851    cx.update_editor(|editor, window, cx| {
14852        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14853    });
14854    completion_requests.next().await;
14855    cx.condition(|editor, _| editor.context_menu_visible())
14856        .await;
14857    cx.update_editor(|editor, _, _| {
14858        assert!(
14859            !editor.hover_state.visible(),
14860            "Hover popover should be hidden when completion menu is shown"
14861        );
14862    });
14863}
14864
14865#[gpui::test]
14866async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14867    init_test(cx, |_| {});
14868
14869    let mut cx = EditorLspTestContext::new_rust(
14870        lsp::ServerCapabilities {
14871            completion_provider: Some(lsp::CompletionOptions {
14872                trigger_characters: Some(vec![".".to_string()]),
14873                resolve_provider: Some(true),
14874                ..Default::default()
14875            }),
14876            ..Default::default()
14877        },
14878        cx,
14879    )
14880    .await;
14881
14882    cx.set_state("fn main() { let a = 2ˇ; }");
14883    cx.simulate_keystroke(".");
14884
14885    let unresolved_item_1 = lsp::CompletionItem {
14886        label: "id".to_string(),
14887        filter_text: Some("id".to_string()),
14888        detail: None,
14889        documentation: None,
14890        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14891            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14892            new_text: ".id".to_string(),
14893        })),
14894        ..lsp::CompletionItem::default()
14895    };
14896    let resolved_item_1 = lsp::CompletionItem {
14897        additional_text_edits: Some(vec![lsp::TextEdit {
14898            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14899            new_text: "!!".to_string(),
14900        }]),
14901        ..unresolved_item_1.clone()
14902    };
14903    let unresolved_item_2 = lsp::CompletionItem {
14904        label: "other".to_string(),
14905        filter_text: Some("other".to_string()),
14906        detail: None,
14907        documentation: None,
14908        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14909            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14910            new_text: ".other".to_string(),
14911        })),
14912        ..lsp::CompletionItem::default()
14913    };
14914    let resolved_item_2 = lsp::CompletionItem {
14915        additional_text_edits: Some(vec![lsp::TextEdit {
14916            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14917            new_text: "??".to_string(),
14918        }]),
14919        ..unresolved_item_2.clone()
14920    };
14921
14922    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14923    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14924    cx.lsp
14925        .server
14926        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14927            let unresolved_item_1 = unresolved_item_1.clone();
14928            let resolved_item_1 = resolved_item_1.clone();
14929            let unresolved_item_2 = unresolved_item_2.clone();
14930            let resolved_item_2 = resolved_item_2.clone();
14931            let resolve_requests_1 = resolve_requests_1.clone();
14932            let resolve_requests_2 = resolve_requests_2.clone();
14933            move |unresolved_request, _| {
14934                let unresolved_item_1 = unresolved_item_1.clone();
14935                let resolved_item_1 = resolved_item_1.clone();
14936                let unresolved_item_2 = unresolved_item_2.clone();
14937                let resolved_item_2 = resolved_item_2.clone();
14938                let resolve_requests_1 = resolve_requests_1.clone();
14939                let resolve_requests_2 = resolve_requests_2.clone();
14940                async move {
14941                    if unresolved_request == unresolved_item_1 {
14942                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14943                        Ok(resolved_item_1.clone())
14944                    } else if unresolved_request == unresolved_item_2 {
14945                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14946                        Ok(resolved_item_2.clone())
14947                    } else {
14948                        panic!("Unexpected completion item {unresolved_request:?}")
14949                    }
14950                }
14951            }
14952        })
14953        .detach();
14954
14955    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14956        let unresolved_item_1 = unresolved_item_1.clone();
14957        let unresolved_item_2 = unresolved_item_2.clone();
14958        async move {
14959            Ok(Some(lsp::CompletionResponse::Array(vec![
14960                unresolved_item_1,
14961                unresolved_item_2,
14962            ])))
14963        }
14964    })
14965    .next()
14966    .await;
14967
14968    cx.condition(|editor, _| editor.context_menu_visible())
14969        .await;
14970    cx.update_editor(|editor, _, _| {
14971        let context_menu = editor.context_menu.borrow_mut();
14972        let context_menu = context_menu
14973            .as_ref()
14974            .expect("Should have the context menu deployed");
14975        match context_menu {
14976            CodeContextMenu::Completions(completions_menu) => {
14977                let completions = completions_menu.completions.borrow_mut();
14978                assert_eq!(
14979                    completions
14980                        .iter()
14981                        .map(|completion| &completion.label.text)
14982                        .collect::<Vec<_>>(),
14983                    vec!["id", "other"]
14984                )
14985            }
14986            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14987        }
14988    });
14989    cx.run_until_parked();
14990
14991    cx.update_editor(|editor, window, cx| {
14992        editor.context_menu_next(&ContextMenuNext, window, cx);
14993    });
14994    cx.run_until_parked();
14995    cx.update_editor(|editor, window, cx| {
14996        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14997    });
14998    cx.run_until_parked();
14999    cx.update_editor(|editor, window, cx| {
15000        editor.context_menu_next(&ContextMenuNext, window, cx);
15001    });
15002    cx.run_until_parked();
15003    cx.update_editor(|editor, window, cx| {
15004        editor
15005            .compose_completion(&ComposeCompletion::default(), window, cx)
15006            .expect("No task returned")
15007    })
15008    .await
15009    .expect("Completion failed");
15010    cx.run_until_parked();
15011
15012    cx.update_editor(|editor, _, cx| {
15013        assert_eq!(
15014            resolve_requests_1.load(atomic::Ordering::Acquire),
15015            1,
15016            "Should always resolve once despite multiple selections"
15017        );
15018        assert_eq!(
15019            resolve_requests_2.load(atomic::Ordering::Acquire),
15020            1,
15021            "Should always resolve once after multiple selections and applying the completion"
15022        );
15023        assert_eq!(
15024            editor.text(cx),
15025            "fn main() { let a = ??.other; }",
15026            "Should use resolved data when applying the completion"
15027        );
15028    });
15029}
15030
15031#[gpui::test]
15032async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15033    init_test(cx, |_| {});
15034
15035    let item_0 = lsp::CompletionItem {
15036        label: "abs".into(),
15037        insert_text: Some("abs".into()),
15038        data: Some(json!({ "very": "special"})),
15039        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15040        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15041            lsp::InsertReplaceEdit {
15042                new_text: "abs".to_string(),
15043                insert: lsp::Range::default(),
15044                replace: lsp::Range::default(),
15045            },
15046        )),
15047        ..lsp::CompletionItem::default()
15048    };
15049    let items = iter::once(item_0.clone())
15050        .chain((11..51).map(|i| lsp::CompletionItem {
15051            label: format!("item_{}", i),
15052            insert_text: Some(format!("item_{}", i)),
15053            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15054            ..lsp::CompletionItem::default()
15055        }))
15056        .collect::<Vec<_>>();
15057
15058    let default_commit_characters = vec!["?".to_string()];
15059    let default_data = json!({ "default": "data"});
15060    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15061    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15062    let default_edit_range = lsp::Range {
15063        start: lsp::Position {
15064            line: 0,
15065            character: 5,
15066        },
15067        end: lsp::Position {
15068            line: 0,
15069            character: 5,
15070        },
15071    };
15072
15073    let mut cx = EditorLspTestContext::new_rust(
15074        lsp::ServerCapabilities {
15075            completion_provider: Some(lsp::CompletionOptions {
15076                trigger_characters: Some(vec![".".to_string()]),
15077                resolve_provider: Some(true),
15078                ..Default::default()
15079            }),
15080            ..Default::default()
15081        },
15082        cx,
15083    )
15084    .await;
15085
15086    cx.set_state("fn main() { let a = 2ˇ; }");
15087    cx.simulate_keystroke(".");
15088
15089    let completion_data = default_data.clone();
15090    let completion_characters = default_commit_characters.clone();
15091    let completion_items = items.clone();
15092    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15093        let default_data = completion_data.clone();
15094        let default_commit_characters = completion_characters.clone();
15095        let items = completion_items.clone();
15096        async move {
15097            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15098                items,
15099                item_defaults: Some(lsp::CompletionListItemDefaults {
15100                    data: Some(default_data.clone()),
15101                    commit_characters: Some(default_commit_characters.clone()),
15102                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15103                        default_edit_range,
15104                    )),
15105                    insert_text_format: Some(default_insert_text_format),
15106                    insert_text_mode: Some(default_insert_text_mode),
15107                }),
15108                ..lsp::CompletionList::default()
15109            })))
15110        }
15111    })
15112    .next()
15113    .await;
15114
15115    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15116    cx.lsp
15117        .server
15118        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15119            let closure_resolved_items = resolved_items.clone();
15120            move |item_to_resolve, _| {
15121                let closure_resolved_items = closure_resolved_items.clone();
15122                async move {
15123                    closure_resolved_items.lock().push(item_to_resolve.clone());
15124                    Ok(item_to_resolve)
15125                }
15126            }
15127        })
15128        .detach();
15129
15130    cx.condition(|editor, _| editor.context_menu_visible())
15131        .await;
15132    cx.run_until_parked();
15133    cx.update_editor(|editor, _, _| {
15134        let menu = editor.context_menu.borrow_mut();
15135        match menu.as_ref().expect("should have the completions menu") {
15136            CodeContextMenu::Completions(completions_menu) => {
15137                assert_eq!(
15138                    completions_menu
15139                        .entries
15140                        .borrow()
15141                        .iter()
15142                        .map(|mat| mat.string.clone())
15143                        .collect::<Vec<String>>(),
15144                    items
15145                        .iter()
15146                        .map(|completion| completion.label.clone())
15147                        .collect::<Vec<String>>()
15148                );
15149            }
15150            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15151        }
15152    });
15153    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15154    // with 4 from the end.
15155    assert_eq!(
15156        *resolved_items.lock(),
15157        [&items[0..16], &items[items.len() - 4..items.len()]]
15158            .concat()
15159            .iter()
15160            .cloned()
15161            .map(|mut item| {
15162                if item.data.is_none() {
15163                    item.data = Some(default_data.clone());
15164                }
15165                item
15166            })
15167            .collect::<Vec<lsp::CompletionItem>>(),
15168        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15169    );
15170    resolved_items.lock().clear();
15171
15172    cx.update_editor(|editor, window, cx| {
15173        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15174    });
15175    cx.run_until_parked();
15176    // Completions that have already been resolved are skipped.
15177    assert_eq!(
15178        *resolved_items.lock(),
15179        items[items.len() - 16..items.len() - 4]
15180            .iter()
15181            .cloned()
15182            .map(|mut item| {
15183                if item.data.is_none() {
15184                    item.data = Some(default_data.clone());
15185                }
15186                item
15187            })
15188            .collect::<Vec<lsp::CompletionItem>>()
15189    );
15190    resolved_items.lock().clear();
15191}
15192
15193#[gpui::test]
15194async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15195    init_test(cx, |_| {});
15196
15197    let mut cx = EditorLspTestContext::new(
15198        Language::new(
15199            LanguageConfig {
15200                matcher: LanguageMatcher {
15201                    path_suffixes: vec!["jsx".into()],
15202                    ..Default::default()
15203                },
15204                overrides: [(
15205                    "element".into(),
15206                    LanguageConfigOverride {
15207                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15208                        ..Default::default()
15209                    },
15210                )]
15211                .into_iter()
15212                .collect(),
15213                ..Default::default()
15214            },
15215            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15216        )
15217        .with_override_query("(jsx_self_closing_element) @element")
15218        .unwrap(),
15219        lsp::ServerCapabilities {
15220            completion_provider: Some(lsp::CompletionOptions {
15221                trigger_characters: Some(vec![":".to_string()]),
15222                ..Default::default()
15223            }),
15224            ..Default::default()
15225        },
15226        cx,
15227    )
15228    .await;
15229
15230    cx.lsp
15231        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15232            Ok(Some(lsp::CompletionResponse::Array(vec![
15233                lsp::CompletionItem {
15234                    label: "bg-blue".into(),
15235                    ..Default::default()
15236                },
15237                lsp::CompletionItem {
15238                    label: "bg-red".into(),
15239                    ..Default::default()
15240                },
15241                lsp::CompletionItem {
15242                    label: "bg-yellow".into(),
15243                    ..Default::default()
15244                },
15245            ])))
15246        });
15247
15248    cx.set_state(r#"<p class="bgˇ" />"#);
15249
15250    // Trigger completion when typing a dash, because the dash is an extra
15251    // word character in the 'element' scope, which contains the cursor.
15252    cx.simulate_keystroke("-");
15253    cx.executor().run_until_parked();
15254    cx.update_editor(|editor, _, _| {
15255        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15256        {
15257            assert_eq!(
15258                completion_menu_entries(&menu),
15259                &["bg-red", "bg-blue", "bg-yellow"]
15260            );
15261        } else {
15262            panic!("expected completion menu to be open");
15263        }
15264    });
15265
15266    cx.simulate_keystroke("l");
15267    cx.executor().run_until_parked();
15268    cx.update_editor(|editor, _, _| {
15269        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15270        {
15271            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15272        } else {
15273            panic!("expected completion menu to be open");
15274        }
15275    });
15276
15277    // When filtering completions, consider the character after the '-' to
15278    // be the start of a subword.
15279    cx.set_state(r#"<p class="yelˇ" />"#);
15280    cx.simulate_keystroke("l");
15281    cx.executor().run_until_parked();
15282    cx.update_editor(|editor, _, _| {
15283        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15284        {
15285            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15286        } else {
15287            panic!("expected completion menu to be open");
15288        }
15289    });
15290}
15291
15292fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15293    let entries = menu.entries.borrow();
15294    entries.iter().map(|mat| mat.string.clone()).collect()
15295}
15296
15297#[gpui::test]
15298async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15299    init_test(cx, |settings| {
15300        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15301            FormatterList(vec![Formatter::Prettier].into()),
15302        ))
15303    });
15304
15305    let fs = FakeFs::new(cx.executor());
15306    fs.insert_file(path!("/file.ts"), Default::default()).await;
15307
15308    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15309    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15310
15311    language_registry.add(Arc::new(Language::new(
15312        LanguageConfig {
15313            name: "TypeScript".into(),
15314            matcher: LanguageMatcher {
15315                path_suffixes: vec!["ts".to_string()],
15316                ..Default::default()
15317            },
15318            ..Default::default()
15319        },
15320        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15321    )));
15322    update_test_language_settings(cx, |settings| {
15323        settings.defaults.prettier = Some(PrettierSettings {
15324            allowed: true,
15325            ..PrettierSettings::default()
15326        });
15327    });
15328
15329    let test_plugin = "test_plugin";
15330    let _ = language_registry.register_fake_lsp(
15331        "TypeScript",
15332        FakeLspAdapter {
15333            prettier_plugins: vec![test_plugin],
15334            ..Default::default()
15335        },
15336    );
15337
15338    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15339    let buffer = project
15340        .update(cx, |project, cx| {
15341            project.open_local_buffer(path!("/file.ts"), cx)
15342        })
15343        .await
15344        .unwrap();
15345
15346    let buffer_text = "one\ntwo\nthree\n";
15347    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15348    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15349    editor.update_in(cx, |editor, window, cx| {
15350        editor.set_text(buffer_text, window, cx)
15351    });
15352
15353    editor
15354        .update_in(cx, |editor, window, cx| {
15355            editor.perform_format(
15356                project.clone(),
15357                FormatTrigger::Manual,
15358                FormatTarget::Buffers,
15359                window,
15360                cx,
15361            )
15362        })
15363        .unwrap()
15364        .await;
15365    assert_eq!(
15366        editor.update(cx, |editor, cx| editor.text(cx)),
15367        buffer_text.to_string() + prettier_format_suffix,
15368        "Test prettier formatting was not applied to the original buffer text",
15369    );
15370
15371    update_test_language_settings(cx, |settings| {
15372        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15373    });
15374    let format = editor.update_in(cx, |editor, window, cx| {
15375        editor.perform_format(
15376            project.clone(),
15377            FormatTrigger::Manual,
15378            FormatTarget::Buffers,
15379            window,
15380            cx,
15381        )
15382    });
15383    format.await.unwrap();
15384    assert_eq!(
15385        editor.update(cx, |editor, cx| editor.text(cx)),
15386        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15387        "Autoformatting (via test prettier) was not applied to the original buffer text",
15388    );
15389}
15390
15391#[gpui::test]
15392async fn test_addition_reverts(cx: &mut TestAppContext) {
15393    init_test(cx, |_| {});
15394    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15395    let base_text = indoc! {r#"
15396        struct Row;
15397        struct Row1;
15398        struct Row2;
15399
15400        struct Row4;
15401        struct Row5;
15402        struct Row6;
15403
15404        struct Row8;
15405        struct Row9;
15406        struct Row10;"#};
15407
15408    // When addition hunks are not adjacent to carets, no hunk revert is performed
15409    assert_hunk_revert(
15410        indoc! {r#"struct Row;
15411                   struct Row1;
15412                   struct Row1.1;
15413                   struct Row1.2;
15414                   struct Row2;ˇ
15415
15416                   struct Row4;
15417                   struct Row5;
15418                   struct Row6;
15419
15420                   struct Row8;
15421                   ˇstruct Row9;
15422                   struct Row9.1;
15423                   struct Row9.2;
15424                   struct Row9.3;
15425                   struct Row10;"#},
15426        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15427        indoc! {r#"struct Row;
15428                   struct Row1;
15429                   struct Row1.1;
15430                   struct Row1.2;
15431                   struct Row2;ˇ
15432
15433                   struct Row4;
15434                   struct Row5;
15435                   struct Row6;
15436
15437                   struct Row8;
15438                   ˇstruct Row9;
15439                   struct Row9.1;
15440                   struct Row9.2;
15441                   struct Row9.3;
15442                   struct Row10;"#},
15443        base_text,
15444        &mut cx,
15445    );
15446    // Same for selections
15447    assert_hunk_revert(
15448        indoc! {r#"struct Row;
15449                   struct Row1;
15450                   struct Row2;
15451                   struct Row2.1;
15452                   struct Row2.2;
15453                   «ˇ
15454                   struct Row4;
15455                   struct» Row5;
15456                   «struct Row6;
15457                   ˇ»
15458                   struct Row9.1;
15459                   struct Row9.2;
15460                   struct Row9.3;
15461                   struct Row8;
15462                   struct Row9;
15463                   struct Row10;"#},
15464        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15465        indoc! {r#"struct Row;
15466                   struct Row1;
15467                   struct Row2;
15468                   struct Row2.1;
15469                   struct Row2.2;
15470                   «ˇ
15471                   struct Row4;
15472                   struct» Row5;
15473                   «struct Row6;
15474                   ˇ»
15475                   struct Row9.1;
15476                   struct Row9.2;
15477                   struct Row9.3;
15478                   struct Row8;
15479                   struct Row9;
15480                   struct Row10;"#},
15481        base_text,
15482        &mut cx,
15483    );
15484
15485    // When carets and selections intersect the addition hunks, those are reverted.
15486    // Adjacent carets got merged.
15487    assert_hunk_revert(
15488        indoc! {r#"struct Row;
15489                   ˇ// something on the top
15490                   struct Row1;
15491                   struct Row2;
15492                   struct Roˇw3.1;
15493                   struct Row2.2;
15494                   struct Row2.3;ˇ
15495
15496                   struct Row4;
15497                   struct ˇRow5.1;
15498                   struct Row5.2;
15499                   struct «Rowˇ»5.3;
15500                   struct Row5;
15501                   struct Row6;
15502                   ˇ
15503                   struct Row9.1;
15504                   struct «Rowˇ»9.2;
15505                   struct «ˇRow»9.3;
15506                   struct Row8;
15507                   struct Row9;
15508                   «ˇ// something on bottom»
15509                   struct Row10;"#},
15510        vec![
15511            DiffHunkStatusKind::Added,
15512            DiffHunkStatusKind::Added,
15513            DiffHunkStatusKind::Added,
15514            DiffHunkStatusKind::Added,
15515            DiffHunkStatusKind::Added,
15516        ],
15517        indoc! {r#"struct Row;
15518                   ˇstruct Row1;
15519                   struct Row2;
15520                   ˇ
15521                   struct Row4;
15522                   ˇstruct Row5;
15523                   struct Row6;
15524                   ˇ
15525                   ˇstruct Row8;
15526                   struct Row9;
15527                   ˇstruct Row10;"#},
15528        base_text,
15529        &mut cx,
15530    );
15531}
15532
15533#[gpui::test]
15534async fn test_modification_reverts(cx: &mut TestAppContext) {
15535    init_test(cx, |_| {});
15536    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15537    let base_text = indoc! {r#"
15538        struct Row;
15539        struct Row1;
15540        struct Row2;
15541
15542        struct Row4;
15543        struct Row5;
15544        struct Row6;
15545
15546        struct Row8;
15547        struct Row9;
15548        struct Row10;"#};
15549
15550    // Modification hunks behave the same as the addition ones.
15551    assert_hunk_revert(
15552        indoc! {r#"struct Row;
15553                   struct Row1;
15554                   struct Row33;
15555                   ˇ
15556                   struct Row4;
15557                   struct Row5;
15558                   struct Row6;
15559                   ˇ
15560                   struct Row99;
15561                   struct Row9;
15562                   struct Row10;"#},
15563        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15564        indoc! {r#"struct Row;
15565                   struct Row1;
15566                   struct Row33;
15567                   ˇ
15568                   struct Row4;
15569                   struct Row5;
15570                   struct Row6;
15571                   ˇ
15572                   struct Row99;
15573                   struct Row9;
15574                   struct Row10;"#},
15575        base_text,
15576        &mut cx,
15577    );
15578    assert_hunk_revert(
15579        indoc! {r#"struct Row;
15580                   struct Row1;
15581                   struct Row33;
15582                   «ˇ
15583                   struct Row4;
15584                   struct» Row5;
15585                   «struct Row6;
15586                   ˇ»
15587                   struct Row99;
15588                   struct Row9;
15589                   struct Row10;"#},
15590        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15591        indoc! {r#"struct Row;
15592                   struct Row1;
15593                   struct Row33;
15594                   «ˇ
15595                   struct Row4;
15596                   struct» Row5;
15597                   «struct Row6;
15598                   ˇ»
15599                   struct Row99;
15600                   struct Row9;
15601                   struct Row10;"#},
15602        base_text,
15603        &mut cx,
15604    );
15605
15606    assert_hunk_revert(
15607        indoc! {r#"ˇstruct Row1.1;
15608                   struct Row1;
15609                   «ˇstr»uct Row22;
15610
15611                   struct ˇRow44;
15612                   struct Row5;
15613                   struct «Rˇ»ow66;ˇ
15614
15615                   «struˇ»ct Row88;
15616                   struct Row9;
15617                   struct Row1011;ˇ"#},
15618        vec![
15619            DiffHunkStatusKind::Modified,
15620            DiffHunkStatusKind::Modified,
15621            DiffHunkStatusKind::Modified,
15622            DiffHunkStatusKind::Modified,
15623            DiffHunkStatusKind::Modified,
15624            DiffHunkStatusKind::Modified,
15625        ],
15626        indoc! {r#"struct Row;
15627                   ˇstruct Row1;
15628                   struct Row2;
15629                   ˇ
15630                   struct Row4;
15631                   ˇstruct Row5;
15632                   struct Row6;
15633                   ˇ
15634                   struct Row8;
15635                   ˇstruct Row9;
15636                   struct Row10;ˇ"#},
15637        base_text,
15638        &mut cx,
15639    );
15640}
15641
15642#[gpui::test]
15643async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15644    init_test(cx, |_| {});
15645    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15646    let base_text = indoc! {r#"
15647        one
15648
15649        two
15650        three
15651        "#};
15652
15653    cx.set_head_text(base_text);
15654    cx.set_state("\nˇ\n");
15655    cx.executor().run_until_parked();
15656    cx.update_editor(|editor, _window, cx| {
15657        editor.expand_selected_diff_hunks(cx);
15658    });
15659    cx.executor().run_until_parked();
15660    cx.update_editor(|editor, window, cx| {
15661        editor.backspace(&Default::default(), window, cx);
15662    });
15663    cx.run_until_parked();
15664    cx.assert_state_with_diff(
15665        indoc! {r#"
15666
15667        - two
15668        - threeˇ
15669        +
15670        "#}
15671        .to_string(),
15672    );
15673}
15674
15675#[gpui::test]
15676async fn test_deletion_reverts(cx: &mut TestAppContext) {
15677    init_test(cx, |_| {});
15678    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15679    let base_text = indoc! {r#"struct Row;
15680struct Row1;
15681struct Row2;
15682
15683struct Row4;
15684struct Row5;
15685struct Row6;
15686
15687struct Row8;
15688struct Row9;
15689struct Row10;"#};
15690
15691    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15692    assert_hunk_revert(
15693        indoc! {r#"struct Row;
15694                   struct Row2;
15695
15696                   ˇstruct Row4;
15697                   struct Row5;
15698                   struct Row6;
15699                   ˇ
15700                   struct Row8;
15701                   struct Row10;"#},
15702        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15703        indoc! {r#"struct Row;
15704                   struct Row2;
15705
15706                   ˇstruct Row4;
15707                   struct Row5;
15708                   struct Row6;
15709                   ˇ
15710                   struct Row8;
15711                   struct Row10;"#},
15712        base_text,
15713        &mut cx,
15714    );
15715    assert_hunk_revert(
15716        indoc! {r#"struct Row;
15717                   struct Row2;
15718
15719                   «ˇstruct Row4;
15720                   struct» Row5;
15721                   «struct Row6;
15722                   ˇ»
15723                   struct Row8;
15724                   struct Row10;"#},
15725        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15726        indoc! {r#"struct Row;
15727                   struct Row2;
15728
15729                   «ˇstruct Row4;
15730                   struct» Row5;
15731                   «struct Row6;
15732                   ˇ»
15733                   struct Row8;
15734                   struct Row10;"#},
15735        base_text,
15736        &mut cx,
15737    );
15738
15739    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15740    assert_hunk_revert(
15741        indoc! {r#"struct Row;
15742                   ˇstruct Row2;
15743
15744                   struct Row4;
15745                   struct Row5;
15746                   struct Row6;
15747
15748                   struct Row8;ˇ
15749                   struct Row10;"#},
15750        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15751        indoc! {r#"struct Row;
15752                   struct Row1;
15753                   ˇstruct Row2;
15754
15755                   struct Row4;
15756                   struct Row5;
15757                   struct Row6;
15758
15759                   struct Row8;ˇ
15760                   struct Row9;
15761                   struct Row10;"#},
15762        base_text,
15763        &mut cx,
15764    );
15765    assert_hunk_revert(
15766        indoc! {r#"struct Row;
15767                   struct Row2«ˇ;
15768                   struct Row4;
15769                   struct» Row5;
15770                   «struct Row6;
15771
15772                   struct Row8;ˇ»
15773                   struct Row10;"#},
15774        vec![
15775            DiffHunkStatusKind::Deleted,
15776            DiffHunkStatusKind::Deleted,
15777            DiffHunkStatusKind::Deleted,
15778        ],
15779        indoc! {r#"struct Row;
15780                   struct Row1;
15781                   struct Row2«ˇ;
15782
15783                   struct Row4;
15784                   struct» Row5;
15785                   «struct Row6;
15786
15787                   struct Row8;ˇ»
15788                   struct Row9;
15789                   struct Row10;"#},
15790        base_text,
15791        &mut cx,
15792    );
15793}
15794
15795#[gpui::test]
15796async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15797    init_test(cx, |_| {});
15798
15799    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15800    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15801    let base_text_3 =
15802        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15803
15804    let text_1 = edit_first_char_of_every_line(base_text_1);
15805    let text_2 = edit_first_char_of_every_line(base_text_2);
15806    let text_3 = edit_first_char_of_every_line(base_text_3);
15807
15808    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15809    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15810    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15811
15812    let multibuffer = cx.new(|cx| {
15813        let mut multibuffer = MultiBuffer::new(ReadWrite);
15814        multibuffer.push_excerpts(
15815            buffer_1.clone(),
15816            [
15817                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15818                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15819                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15820            ],
15821            cx,
15822        );
15823        multibuffer.push_excerpts(
15824            buffer_2.clone(),
15825            [
15826                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15827                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15828                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15829            ],
15830            cx,
15831        );
15832        multibuffer.push_excerpts(
15833            buffer_3.clone(),
15834            [
15835                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15836                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15837                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15838            ],
15839            cx,
15840        );
15841        multibuffer
15842    });
15843
15844    let fs = FakeFs::new(cx.executor());
15845    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15846    let (editor, cx) = cx
15847        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15848    editor.update_in(cx, |editor, _window, cx| {
15849        for (buffer, diff_base) in [
15850            (buffer_1.clone(), base_text_1),
15851            (buffer_2.clone(), base_text_2),
15852            (buffer_3.clone(), base_text_3),
15853        ] {
15854            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15855            editor
15856                .buffer
15857                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15858        }
15859    });
15860    cx.executor().run_until_parked();
15861
15862    editor.update_in(cx, |editor, window, cx| {
15863        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}");
15864        editor.select_all(&SelectAll, window, cx);
15865        editor.git_restore(&Default::default(), window, cx);
15866    });
15867    cx.executor().run_until_parked();
15868
15869    // When all ranges are selected, all buffer hunks are reverted.
15870    editor.update(cx, |editor, cx| {
15871        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");
15872    });
15873    buffer_1.update(cx, |buffer, _| {
15874        assert_eq!(buffer.text(), base_text_1);
15875    });
15876    buffer_2.update(cx, |buffer, _| {
15877        assert_eq!(buffer.text(), base_text_2);
15878    });
15879    buffer_3.update(cx, |buffer, _| {
15880        assert_eq!(buffer.text(), base_text_3);
15881    });
15882
15883    editor.update_in(cx, |editor, window, cx| {
15884        editor.undo(&Default::default(), window, cx);
15885    });
15886
15887    editor.update_in(cx, |editor, window, cx| {
15888        editor.change_selections(None, window, cx, |s| {
15889            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15890        });
15891        editor.git_restore(&Default::default(), window, cx);
15892    });
15893
15894    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15895    // but not affect buffer_2 and its related excerpts.
15896    editor.update(cx, |editor, cx| {
15897        assert_eq!(
15898            editor.text(cx),
15899            "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}"
15900        );
15901    });
15902    buffer_1.update(cx, |buffer, _| {
15903        assert_eq!(buffer.text(), base_text_1);
15904    });
15905    buffer_2.update(cx, |buffer, _| {
15906        assert_eq!(
15907            buffer.text(),
15908            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15909        );
15910    });
15911    buffer_3.update(cx, |buffer, _| {
15912        assert_eq!(
15913            buffer.text(),
15914            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15915        );
15916    });
15917
15918    fn edit_first_char_of_every_line(text: &str) -> String {
15919        text.split('\n')
15920            .map(|line| format!("X{}", &line[1..]))
15921            .collect::<Vec<_>>()
15922            .join("\n")
15923    }
15924}
15925
15926#[gpui::test]
15927async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15928    init_test(cx, |_| {});
15929
15930    let cols = 4;
15931    let rows = 10;
15932    let sample_text_1 = sample_text(rows, cols, 'a');
15933    assert_eq!(
15934        sample_text_1,
15935        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15936    );
15937    let sample_text_2 = sample_text(rows, cols, 'l');
15938    assert_eq!(
15939        sample_text_2,
15940        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15941    );
15942    let sample_text_3 = sample_text(rows, cols, 'v');
15943    assert_eq!(
15944        sample_text_3,
15945        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15946    );
15947
15948    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15949    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15950    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15951
15952    let multi_buffer = cx.new(|cx| {
15953        let mut multibuffer = MultiBuffer::new(ReadWrite);
15954        multibuffer.push_excerpts(
15955            buffer_1.clone(),
15956            [
15957                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15958                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15959                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15960            ],
15961            cx,
15962        );
15963        multibuffer.push_excerpts(
15964            buffer_2.clone(),
15965            [
15966                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15967                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15968                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15969            ],
15970            cx,
15971        );
15972        multibuffer.push_excerpts(
15973            buffer_3.clone(),
15974            [
15975                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15976                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15977                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15978            ],
15979            cx,
15980        );
15981        multibuffer
15982    });
15983
15984    let fs = FakeFs::new(cx.executor());
15985    fs.insert_tree(
15986        "/a",
15987        json!({
15988            "main.rs": sample_text_1,
15989            "other.rs": sample_text_2,
15990            "lib.rs": sample_text_3,
15991        }),
15992    )
15993    .await;
15994    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15995    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15996    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15997    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15998        Editor::new(
15999            EditorMode::full(),
16000            multi_buffer,
16001            Some(project.clone()),
16002            window,
16003            cx,
16004        )
16005    });
16006    let multibuffer_item_id = workspace
16007        .update(cx, |workspace, window, cx| {
16008            assert!(
16009                workspace.active_item(cx).is_none(),
16010                "active item should be None before the first item is added"
16011            );
16012            workspace.add_item_to_active_pane(
16013                Box::new(multi_buffer_editor.clone()),
16014                None,
16015                true,
16016                window,
16017                cx,
16018            );
16019            let active_item = workspace
16020                .active_item(cx)
16021                .expect("should have an active item after adding the multi buffer");
16022            assert!(
16023                !active_item.is_singleton(cx),
16024                "A multi buffer was expected to active after adding"
16025            );
16026            active_item.item_id()
16027        })
16028        .unwrap();
16029    cx.executor().run_until_parked();
16030
16031    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16032        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16033            s.select_ranges(Some(1..2))
16034        });
16035        editor.open_excerpts(&OpenExcerpts, window, cx);
16036    });
16037    cx.executor().run_until_parked();
16038    let first_item_id = workspace
16039        .update(cx, |workspace, window, cx| {
16040            let active_item = workspace
16041                .active_item(cx)
16042                .expect("should have an active item after navigating into the 1st buffer");
16043            let first_item_id = active_item.item_id();
16044            assert_ne!(
16045                first_item_id, multibuffer_item_id,
16046                "Should navigate into the 1st buffer and activate it"
16047            );
16048            assert!(
16049                active_item.is_singleton(cx),
16050                "New active item should be a singleton buffer"
16051            );
16052            assert_eq!(
16053                active_item
16054                    .act_as::<Editor>(cx)
16055                    .expect("should have navigated into an editor for the 1st buffer")
16056                    .read(cx)
16057                    .text(cx),
16058                sample_text_1
16059            );
16060
16061            workspace
16062                .go_back(workspace.active_pane().downgrade(), window, cx)
16063                .detach_and_log_err(cx);
16064
16065            first_item_id
16066        })
16067        .unwrap();
16068    cx.executor().run_until_parked();
16069    workspace
16070        .update(cx, |workspace, _, cx| {
16071            let active_item = workspace
16072                .active_item(cx)
16073                .expect("should have an active item after navigating back");
16074            assert_eq!(
16075                active_item.item_id(),
16076                multibuffer_item_id,
16077                "Should navigate back to the multi buffer"
16078            );
16079            assert!(!active_item.is_singleton(cx));
16080        })
16081        .unwrap();
16082
16083    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16084        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16085            s.select_ranges(Some(39..40))
16086        });
16087        editor.open_excerpts(&OpenExcerpts, window, cx);
16088    });
16089    cx.executor().run_until_parked();
16090    let second_item_id = workspace
16091        .update(cx, |workspace, window, cx| {
16092            let active_item = workspace
16093                .active_item(cx)
16094                .expect("should have an active item after navigating into the 2nd buffer");
16095            let second_item_id = active_item.item_id();
16096            assert_ne!(
16097                second_item_id, multibuffer_item_id,
16098                "Should navigate away from the multibuffer"
16099            );
16100            assert_ne!(
16101                second_item_id, first_item_id,
16102                "Should navigate into the 2nd buffer and activate it"
16103            );
16104            assert!(
16105                active_item.is_singleton(cx),
16106                "New active item should be a singleton buffer"
16107            );
16108            assert_eq!(
16109                active_item
16110                    .act_as::<Editor>(cx)
16111                    .expect("should have navigated into an editor")
16112                    .read(cx)
16113                    .text(cx),
16114                sample_text_2
16115            );
16116
16117            workspace
16118                .go_back(workspace.active_pane().downgrade(), window, cx)
16119                .detach_and_log_err(cx);
16120
16121            second_item_id
16122        })
16123        .unwrap();
16124    cx.executor().run_until_parked();
16125    workspace
16126        .update(cx, |workspace, _, cx| {
16127            let active_item = workspace
16128                .active_item(cx)
16129                .expect("should have an active item after navigating back from the 2nd buffer");
16130            assert_eq!(
16131                active_item.item_id(),
16132                multibuffer_item_id,
16133                "Should navigate back from the 2nd buffer to the multi buffer"
16134            );
16135            assert!(!active_item.is_singleton(cx));
16136        })
16137        .unwrap();
16138
16139    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16140        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16141            s.select_ranges(Some(70..70))
16142        });
16143        editor.open_excerpts(&OpenExcerpts, window, cx);
16144    });
16145    cx.executor().run_until_parked();
16146    workspace
16147        .update(cx, |workspace, window, cx| {
16148            let active_item = workspace
16149                .active_item(cx)
16150                .expect("should have an active item after navigating into the 3rd buffer");
16151            let third_item_id = active_item.item_id();
16152            assert_ne!(
16153                third_item_id, multibuffer_item_id,
16154                "Should navigate into the 3rd buffer and activate it"
16155            );
16156            assert_ne!(third_item_id, first_item_id);
16157            assert_ne!(third_item_id, second_item_id);
16158            assert!(
16159                active_item.is_singleton(cx),
16160                "New active item should be a singleton buffer"
16161            );
16162            assert_eq!(
16163                active_item
16164                    .act_as::<Editor>(cx)
16165                    .expect("should have navigated into an editor")
16166                    .read(cx)
16167                    .text(cx),
16168                sample_text_3
16169            );
16170
16171            workspace
16172                .go_back(workspace.active_pane().downgrade(), window, cx)
16173                .detach_and_log_err(cx);
16174        })
16175        .unwrap();
16176    cx.executor().run_until_parked();
16177    workspace
16178        .update(cx, |workspace, _, cx| {
16179            let active_item = workspace
16180                .active_item(cx)
16181                .expect("should have an active item after navigating back from the 3rd buffer");
16182            assert_eq!(
16183                active_item.item_id(),
16184                multibuffer_item_id,
16185                "Should navigate back from the 3rd buffer to the multi buffer"
16186            );
16187            assert!(!active_item.is_singleton(cx));
16188        })
16189        .unwrap();
16190}
16191
16192#[gpui::test]
16193async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16194    init_test(cx, |_| {});
16195
16196    let mut cx = EditorTestContext::new(cx).await;
16197
16198    let diff_base = r#"
16199        use some::mod;
16200
16201        const A: u32 = 42;
16202
16203        fn main() {
16204            println!("hello");
16205
16206            println!("world");
16207        }
16208        "#
16209    .unindent();
16210
16211    cx.set_state(
16212        &r#"
16213        use some::modified;
16214
16215        ˇ
16216        fn main() {
16217            println!("hello there");
16218
16219            println!("around the");
16220            println!("world");
16221        }
16222        "#
16223        .unindent(),
16224    );
16225
16226    cx.set_head_text(&diff_base);
16227    executor.run_until_parked();
16228
16229    cx.update_editor(|editor, window, cx| {
16230        editor.go_to_next_hunk(&GoToHunk, window, cx);
16231        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16232    });
16233    executor.run_until_parked();
16234    cx.assert_state_with_diff(
16235        r#"
16236          use some::modified;
16237
16238
16239          fn main() {
16240        -     println!("hello");
16241        + ˇ    println!("hello there");
16242
16243              println!("around the");
16244              println!("world");
16245          }
16246        "#
16247        .unindent(),
16248    );
16249
16250    cx.update_editor(|editor, window, cx| {
16251        for _ in 0..2 {
16252            editor.go_to_next_hunk(&GoToHunk, window, cx);
16253            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16254        }
16255    });
16256    executor.run_until_parked();
16257    cx.assert_state_with_diff(
16258        r#"
16259        - use some::mod;
16260        + ˇuse some::modified;
16261
16262
16263          fn main() {
16264        -     println!("hello");
16265        +     println!("hello there");
16266
16267        +     println!("around the");
16268              println!("world");
16269          }
16270        "#
16271        .unindent(),
16272    );
16273
16274    cx.update_editor(|editor, window, cx| {
16275        editor.go_to_next_hunk(&GoToHunk, window, cx);
16276        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16277    });
16278    executor.run_until_parked();
16279    cx.assert_state_with_diff(
16280        r#"
16281        - use some::mod;
16282        + use some::modified;
16283
16284        - const A: u32 = 42;
16285          ˇ
16286          fn main() {
16287        -     println!("hello");
16288        +     println!("hello there");
16289
16290        +     println!("around the");
16291              println!("world");
16292          }
16293        "#
16294        .unindent(),
16295    );
16296
16297    cx.update_editor(|editor, window, cx| {
16298        editor.cancel(&Cancel, window, cx);
16299    });
16300
16301    cx.assert_state_with_diff(
16302        r#"
16303          use some::modified;
16304
16305          ˇ
16306          fn main() {
16307              println!("hello there");
16308
16309              println!("around the");
16310              println!("world");
16311          }
16312        "#
16313        .unindent(),
16314    );
16315}
16316
16317#[gpui::test]
16318async fn test_diff_base_change_with_expanded_diff_hunks(
16319    executor: BackgroundExecutor,
16320    cx: &mut TestAppContext,
16321) {
16322    init_test(cx, |_| {});
16323
16324    let mut cx = EditorTestContext::new(cx).await;
16325
16326    let diff_base = r#"
16327        use some::mod1;
16328        use some::mod2;
16329
16330        const A: u32 = 42;
16331        const B: u32 = 42;
16332        const C: u32 = 42;
16333
16334        fn main() {
16335            println!("hello");
16336
16337            println!("world");
16338        }
16339        "#
16340    .unindent();
16341
16342    cx.set_state(
16343        &r#"
16344        use some::mod2;
16345
16346        const A: u32 = 42;
16347        const C: u32 = 42;
16348
16349        fn main(ˇ) {
16350            //println!("hello");
16351
16352            println!("world");
16353            //
16354            //
16355        }
16356        "#
16357        .unindent(),
16358    );
16359
16360    cx.set_head_text(&diff_base);
16361    executor.run_until_parked();
16362
16363    cx.update_editor(|editor, window, cx| {
16364        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16365    });
16366    executor.run_until_parked();
16367    cx.assert_state_with_diff(
16368        r#"
16369        - use some::mod1;
16370          use some::mod2;
16371
16372          const A: u32 = 42;
16373        - const B: u32 = 42;
16374          const C: u32 = 42;
16375
16376          fn main(ˇ) {
16377        -     println!("hello");
16378        +     //println!("hello");
16379
16380              println!("world");
16381        +     //
16382        +     //
16383          }
16384        "#
16385        .unindent(),
16386    );
16387
16388    cx.set_head_text("new diff base!");
16389    executor.run_until_parked();
16390    cx.assert_state_with_diff(
16391        r#"
16392        - new diff base!
16393        + use some::mod2;
16394        +
16395        + const A: u32 = 42;
16396        + const C: u32 = 42;
16397        +
16398        + fn main(ˇ) {
16399        +     //println!("hello");
16400        +
16401        +     println!("world");
16402        +     //
16403        +     //
16404        + }
16405        "#
16406        .unindent(),
16407    );
16408}
16409
16410#[gpui::test]
16411async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16412    init_test(cx, |_| {});
16413
16414    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16415    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16416    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16417    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16418    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16419    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16420
16421    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16422    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16423    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16424
16425    let multi_buffer = cx.new(|cx| {
16426        let mut multibuffer = MultiBuffer::new(ReadWrite);
16427        multibuffer.push_excerpts(
16428            buffer_1.clone(),
16429            [
16430                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16431                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16432                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16433            ],
16434            cx,
16435        );
16436        multibuffer.push_excerpts(
16437            buffer_2.clone(),
16438            [
16439                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16440                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16441                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16442            ],
16443            cx,
16444        );
16445        multibuffer.push_excerpts(
16446            buffer_3.clone(),
16447            [
16448                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16449                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16450                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16451            ],
16452            cx,
16453        );
16454        multibuffer
16455    });
16456
16457    let editor =
16458        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16459    editor
16460        .update(cx, |editor, _window, cx| {
16461            for (buffer, diff_base) in [
16462                (buffer_1.clone(), file_1_old),
16463                (buffer_2.clone(), file_2_old),
16464                (buffer_3.clone(), file_3_old),
16465            ] {
16466                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16467                editor
16468                    .buffer
16469                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16470            }
16471        })
16472        .unwrap();
16473
16474    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16475    cx.run_until_parked();
16476
16477    cx.assert_editor_state(
16478        &"
16479            ˇaaa
16480            ccc
16481            ddd
16482
16483            ggg
16484            hhh
16485
16486
16487            lll
16488            mmm
16489            NNN
16490
16491            qqq
16492            rrr
16493
16494            uuu
16495            111
16496            222
16497            333
16498
16499            666
16500            777
16501
16502            000
16503            !!!"
16504        .unindent(),
16505    );
16506
16507    cx.update_editor(|editor, window, cx| {
16508        editor.select_all(&SelectAll, window, cx);
16509        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16510    });
16511    cx.executor().run_until_parked();
16512
16513    cx.assert_state_with_diff(
16514        "
16515            «aaa
16516          - bbb
16517            ccc
16518            ddd
16519
16520            ggg
16521            hhh
16522
16523
16524            lll
16525            mmm
16526          - nnn
16527          + NNN
16528
16529            qqq
16530            rrr
16531
16532            uuu
16533            111
16534            222
16535            333
16536
16537          + 666
16538            777
16539
16540            000
16541            !!!ˇ»"
16542            .unindent(),
16543    );
16544}
16545
16546#[gpui::test]
16547async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16548    init_test(cx, |_| {});
16549
16550    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16551    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16552
16553    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16554    let multi_buffer = cx.new(|cx| {
16555        let mut multibuffer = MultiBuffer::new(ReadWrite);
16556        multibuffer.push_excerpts(
16557            buffer.clone(),
16558            [
16559                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16560                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16561                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16562            ],
16563            cx,
16564        );
16565        multibuffer
16566    });
16567
16568    let editor =
16569        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16570    editor
16571        .update(cx, |editor, _window, cx| {
16572            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16573            editor
16574                .buffer
16575                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16576        })
16577        .unwrap();
16578
16579    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16580    cx.run_until_parked();
16581
16582    cx.update_editor(|editor, window, cx| {
16583        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16584    });
16585    cx.executor().run_until_parked();
16586
16587    // When the start of a hunk coincides with the start of its excerpt,
16588    // the hunk is expanded. When the start of a a hunk is earlier than
16589    // the start of its excerpt, the hunk is not expanded.
16590    cx.assert_state_with_diff(
16591        "
16592            ˇaaa
16593          - bbb
16594          + BBB
16595
16596          - ddd
16597          - eee
16598          + DDD
16599          + EEE
16600            fff
16601
16602            iii
16603        "
16604        .unindent(),
16605    );
16606}
16607
16608#[gpui::test]
16609async fn test_edits_around_expanded_insertion_hunks(
16610    executor: BackgroundExecutor,
16611    cx: &mut TestAppContext,
16612) {
16613    init_test(cx, |_| {});
16614
16615    let mut cx = EditorTestContext::new(cx).await;
16616
16617    let diff_base = r#"
16618        use some::mod1;
16619        use some::mod2;
16620
16621        const A: u32 = 42;
16622
16623        fn main() {
16624            println!("hello");
16625
16626            println!("world");
16627        }
16628        "#
16629    .unindent();
16630    executor.run_until_parked();
16631    cx.set_state(
16632        &r#"
16633        use some::mod1;
16634        use some::mod2;
16635
16636        const A: u32 = 42;
16637        const B: u32 = 42;
16638        const C: u32 = 42;
16639        ˇ
16640
16641        fn main() {
16642            println!("hello");
16643
16644            println!("world");
16645        }
16646        "#
16647        .unindent(),
16648    );
16649
16650    cx.set_head_text(&diff_base);
16651    executor.run_until_parked();
16652
16653    cx.update_editor(|editor, window, cx| {
16654        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16655    });
16656    executor.run_until_parked();
16657
16658    cx.assert_state_with_diff(
16659        r#"
16660        use some::mod1;
16661        use some::mod2;
16662
16663        const A: u32 = 42;
16664      + const B: u32 = 42;
16665      + const C: u32 = 42;
16666      + ˇ
16667
16668        fn main() {
16669            println!("hello");
16670
16671            println!("world");
16672        }
16673      "#
16674        .unindent(),
16675    );
16676
16677    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16678    executor.run_until_parked();
16679
16680    cx.assert_state_with_diff(
16681        r#"
16682        use some::mod1;
16683        use some::mod2;
16684
16685        const A: u32 = 42;
16686      + const B: u32 = 42;
16687      + const C: u32 = 42;
16688      + const D: u32 = 42;
16689      + ˇ
16690
16691        fn main() {
16692            println!("hello");
16693
16694            println!("world");
16695        }
16696      "#
16697        .unindent(),
16698    );
16699
16700    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16701    executor.run_until_parked();
16702
16703    cx.assert_state_with_diff(
16704        r#"
16705        use some::mod1;
16706        use some::mod2;
16707
16708        const A: u32 = 42;
16709      + const B: u32 = 42;
16710      + const C: u32 = 42;
16711      + const D: u32 = 42;
16712      + const E: u32 = 42;
16713      + ˇ
16714
16715        fn main() {
16716            println!("hello");
16717
16718            println!("world");
16719        }
16720      "#
16721        .unindent(),
16722    );
16723
16724    cx.update_editor(|editor, window, cx| {
16725        editor.delete_line(&DeleteLine, window, cx);
16726    });
16727    executor.run_until_parked();
16728
16729    cx.assert_state_with_diff(
16730        r#"
16731        use some::mod1;
16732        use some::mod2;
16733
16734        const A: u32 = 42;
16735      + const B: u32 = 42;
16736      + const C: u32 = 42;
16737      + const D: u32 = 42;
16738      + const E: u32 = 42;
16739        ˇ
16740        fn main() {
16741            println!("hello");
16742
16743            println!("world");
16744        }
16745      "#
16746        .unindent(),
16747    );
16748
16749    cx.update_editor(|editor, window, cx| {
16750        editor.move_up(&MoveUp, window, cx);
16751        editor.delete_line(&DeleteLine, window, cx);
16752        editor.move_up(&MoveUp, window, cx);
16753        editor.delete_line(&DeleteLine, window, cx);
16754        editor.move_up(&MoveUp, window, cx);
16755        editor.delete_line(&DeleteLine, window, cx);
16756    });
16757    executor.run_until_parked();
16758    cx.assert_state_with_diff(
16759        r#"
16760        use some::mod1;
16761        use some::mod2;
16762
16763        const A: u32 = 42;
16764      + const B: u32 = 42;
16765        ˇ
16766        fn main() {
16767            println!("hello");
16768
16769            println!("world");
16770        }
16771      "#
16772        .unindent(),
16773    );
16774
16775    cx.update_editor(|editor, window, cx| {
16776        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16777        editor.delete_line(&DeleteLine, window, cx);
16778    });
16779    executor.run_until_parked();
16780    cx.assert_state_with_diff(
16781        r#"
16782        ˇ
16783        fn main() {
16784            println!("hello");
16785
16786            println!("world");
16787        }
16788      "#
16789        .unindent(),
16790    );
16791}
16792
16793#[gpui::test]
16794async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16795    init_test(cx, |_| {});
16796
16797    let mut cx = EditorTestContext::new(cx).await;
16798    cx.set_head_text(indoc! { "
16799        one
16800        two
16801        three
16802        four
16803        five
16804        "
16805    });
16806    cx.set_state(indoc! { "
16807        one
16808        ˇthree
16809        five
16810    "});
16811    cx.run_until_parked();
16812    cx.update_editor(|editor, window, cx| {
16813        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16814    });
16815    cx.assert_state_with_diff(
16816        indoc! { "
16817        one
16818      - two
16819        ˇthree
16820      - four
16821        five
16822    "}
16823        .to_string(),
16824    );
16825    cx.update_editor(|editor, window, cx| {
16826        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16827    });
16828
16829    cx.assert_state_with_diff(
16830        indoc! { "
16831        one
16832        ˇthree
16833        five
16834    "}
16835        .to_string(),
16836    );
16837
16838    cx.set_state(indoc! { "
16839        one
16840        ˇTWO
16841        three
16842        four
16843        five
16844    "});
16845    cx.run_until_parked();
16846    cx.update_editor(|editor, window, cx| {
16847        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16848    });
16849
16850    cx.assert_state_with_diff(
16851        indoc! { "
16852            one
16853          - two
16854          + ˇTWO
16855            three
16856            four
16857            five
16858        "}
16859        .to_string(),
16860    );
16861    cx.update_editor(|editor, window, cx| {
16862        editor.move_up(&Default::default(), window, cx);
16863        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16864    });
16865    cx.assert_state_with_diff(
16866        indoc! { "
16867            one
16868            ˇTWO
16869            three
16870            four
16871            five
16872        "}
16873        .to_string(),
16874    );
16875}
16876
16877#[gpui::test]
16878async fn test_edits_around_expanded_deletion_hunks(
16879    executor: BackgroundExecutor,
16880    cx: &mut TestAppContext,
16881) {
16882    init_test(cx, |_| {});
16883
16884    let mut cx = EditorTestContext::new(cx).await;
16885
16886    let diff_base = r#"
16887        use some::mod1;
16888        use some::mod2;
16889
16890        const A: u32 = 42;
16891        const B: u32 = 42;
16892        const C: u32 = 42;
16893
16894
16895        fn main() {
16896            println!("hello");
16897
16898            println!("world");
16899        }
16900    "#
16901    .unindent();
16902    executor.run_until_parked();
16903    cx.set_state(
16904        &r#"
16905        use some::mod1;
16906        use some::mod2;
16907
16908        ˇconst B: u32 = 42;
16909        const C: u32 = 42;
16910
16911
16912        fn main() {
16913            println!("hello");
16914
16915            println!("world");
16916        }
16917        "#
16918        .unindent(),
16919    );
16920
16921    cx.set_head_text(&diff_base);
16922    executor.run_until_parked();
16923
16924    cx.update_editor(|editor, window, cx| {
16925        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16926    });
16927    executor.run_until_parked();
16928
16929    cx.assert_state_with_diff(
16930        r#"
16931        use some::mod1;
16932        use some::mod2;
16933
16934      - const A: u32 = 42;
16935        ˇconst B: u32 = 42;
16936        const C: u32 = 42;
16937
16938
16939        fn main() {
16940            println!("hello");
16941
16942            println!("world");
16943        }
16944      "#
16945        .unindent(),
16946    );
16947
16948    cx.update_editor(|editor, window, cx| {
16949        editor.delete_line(&DeleteLine, window, cx);
16950    });
16951    executor.run_until_parked();
16952    cx.assert_state_with_diff(
16953        r#"
16954        use some::mod1;
16955        use some::mod2;
16956
16957      - const A: u32 = 42;
16958      - const B: u32 = 42;
16959        ˇconst C: u32 = 42;
16960
16961
16962        fn main() {
16963            println!("hello");
16964
16965            println!("world");
16966        }
16967      "#
16968        .unindent(),
16969    );
16970
16971    cx.update_editor(|editor, window, cx| {
16972        editor.delete_line(&DeleteLine, window, cx);
16973    });
16974    executor.run_until_parked();
16975    cx.assert_state_with_diff(
16976        r#"
16977        use some::mod1;
16978        use some::mod2;
16979
16980      - const A: u32 = 42;
16981      - const B: u32 = 42;
16982      - const C: u32 = 42;
16983        ˇ
16984
16985        fn main() {
16986            println!("hello");
16987
16988            println!("world");
16989        }
16990      "#
16991        .unindent(),
16992    );
16993
16994    cx.update_editor(|editor, window, cx| {
16995        editor.handle_input("replacement", window, cx);
16996    });
16997    executor.run_until_parked();
16998    cx.assert_state_with_diff(
16999        r#"
17000        use some::mod1;
17001        use some::mod2;
17002
17003      - const A: u32 = 42;
17004      - const B: u32 = 42;
17005      - const C: u32 = 42;
17006      -
17007      + replacementˇ
17008
17009        fn main() {
17010            println!("hello");
17011
17012            println!("world");
17013        }
17014      "#
17015        .unindent(),
17016    );
17017}
17018
17019#[gpui::test]
17020async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17021    init_test(cx, |_| {});
17022
17023    let mut cx = EditorTestContext::new(cx).await;
17024
17025    let base_text = r#"
17026        one
17027        two
17028        three
17029        four
17030        five
17031    "#
17032    .unindent();
17033    executor.run_until_parked();
17034    cx.set_state(
17035        &r#"
17036        one
17037        two
17038        fˇour
17039        five
17040        "#
17041        .unindent(),
17042    );
17043
17044    cx.set_head_text(&base_text);
17045    executor.run_until_parked();
17046
17047    cx.update_editor(|editor, window, cx| {
17048        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17049    });
17050    executor.run_until_parked();
17051
17052    cx.assert_state_with_diff(
17053        r#"
17054          one
17055          two
17056        - three
17057          fˇour
17058          five
17059        "#
17060        .unindent(),
17061    );
17062
17063    cx.update_editor(|editor, window, cx| {
17064        editor.backspace(&Backspace, window, cx);
17065        editor.backspace(&Backspace, window, cx);
17066    });
17067    executor.run_until_parked();
17068    cx.assert_state_with_diff(
17069        r#"
17070          one
17071          two
17072        - threeˇ
17073        - four
17074        + our
17075          five
17076        "#
17077        .unindent(),
17078    );
17079}
17080
17081#[gpui::test]
17082async fn test_edit_after_expanded_modification_hunk(
17083    executor: BackgroundExecutor,
17084    cx: &mut TestAppContext,
17085) {
17086    init_test(cx, |_| {});
17087
17088    let mut cx = EditorTestContext::new(cx).await;
17089
17090    let diff_base = r#"
17091        use some::mod1;
17092        use some::mod2;
17093
17094        const A: u32 = 42;
17095        const B: u32 = 42;
17096        const C: u32 = 42;
17097        const D: u32 = 42;
17098
17099
17100        fn main() {
17101            println!("hello");
17102
17103            println!("world");
17104        }"#
17105    .unindent();
17106
17107    cx.set_state(
17108        &r#"
17109        use some::mod1;
17110        use some::mod2;
17111
17112        const A: u32 = 42;
17113        const B: u32 = 42;
17114        const C: u32 = 43ˇ
17115        const D: u32 = 42;
17116
17117
17118        fn main() {
17119            println!("hello");
17120
17121            println!("world");
17122        }"#
17123        .unindent(),
17124    );
17125
17126    cx.set_head_text(&diff_base);
17127    executor.run_until_parked();
17128    cx.update_editor(|editor, window, cx| {
17129        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17130    });
17131    executor.run_until_parked();
17132
17133    cx.assert_state_with_diff(
17134        r#"
17135        use some::mod1;
17136        use some::mod2;
17137
17138        const A: u32 = 42;
17139        const B: u32 = 42;
17140      - const C: u32 = 42;
17141      + const C: u32 = 43ˇ
17142        const D: u32 = 42;
17143
17144
17145        fn main() {
17146            println!("hello");
17147
17148            println!("world");
17149        }"#
17150        .unindent(),
17151    );
17152
17153    cx.update_editor(|editor, window, cx| {
17154        editor.handle_input("\nnew_line\n", window, cx);
17155    });
17156    executor.run_until_parked();
17157
17158    cx.assert_state_with_diff(
17159        r#"
17160        use some::mod1;
17161        use some::mod2;
17162
17163        const A: u32 = 42;
17164        const B: u32 = 42;
17165      - const C: u32 = 42;
17166      + const C: u32 = 43
17167      + new_line
17168      + ˇ
17169        const D: u32 = 42;
17170
17171
17172        fn main() {
17173            println!("hello");
17174
17175            println!("world");
17176        }"#
17177        .unindent(),
17178    );
17179}
17180
17181#[gpui::test]
17182async fn test_stage_and_unstage_added_file_hunk(
17183    executor: BackgroundExecutor,
17184    cx: &mut TestAppContext,
17185) {
17186    init_test(cx, |_| {});
17187
17188    let mut cx = EditorTestContext::new(cx).await;
17189    cx.update_editor(|editor, _, cx| {
17190        editor.set_expand_all_diff_hunks(cx);
17191    });
17192
17193    let working_copy = r#"
17194            ˇfn main() {
17195                println!("hello, world!");
17196            }
17197        "#
17198    .unindent();
17199
17200    cx.set_state(&working_copy);
17201    executor.run_until_parked();
17202
17203    cx.assert_state_with_diff(
17204        r#"
17205            + ˇfn main() {
17206            +     println!("hello, world!");
17207            + }
17208        "#
17209        .unindent(),
17210    );
17211    cx.assert_index_text(None);
17212
17213    cx.update_editor(|editor, window, cx| {
17214        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17215    });
17216    executor.run_until_parked();
17217    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17218    cx.assert_state_with_diff(
17219        r#"
17220            + ˇfn main() {
17221            +     println!("hello, world!");
17222            + }
17223        "#
17224        .unindent(),
17225    );
17226
17227    cx.update_editor(|editor, window, cx| {
17228        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17229    });
17230    executor.run_until_parked();
17231    cx.assert_index_text(None);
17232}
17233
17234async fn setup_indent_guides_editor(
17235    text: &str,
17236    cx: &mut TestAppContext,
17237) -> (BufferId, EditorTestContext) {
17238    init_test(cx, |_| {});
17239
17240    let mut cx = EditorTestContext::new(cx).await;
17241
17242    let buffer_id = cx.update_editor(|editor, window, cx| {
17243        editor.set_text(text, window, cx);
17244        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17245
17246        buffer_ids[0]
17247    });
17248
17249    (buffer_id, cx)
17250}
17251
17252fn assert_indent_guides(
17253    range: Range<u32>,
17254    expected: Vec<IndentGuide>,
17255    active_indices: Option<Vec<usize>>,
17256    cx: &mut EditorTestContext,
17257) {
17258    let indent_guides = cx.update_editor(|editor, window, cx| {
17259        let snapshot = editor.snapshot(window, cx).display_snapshot;
17260        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17261            editor,
17262            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17263            true,
17264            &snapshot,
17265            cx,
17266        );
17267
17268        indent_guides.sort_by(|a, b| {
17269            a.depth.cmp(&b.depth).then(
17270                a.start_row
17271                    .cmp(&b.start_row)
17272                    .then(a.end_row.cmp(&b.end_row)),
17273            )
17274        });
17275        indent_guides
17276    });
17277
17278    if let Some(expected) = active_indices {
17279        let active_indices = cx.update_editor(|editor, window, cx| {
17280            let snapshot = editor.snapshot(window, cx).display_snapshot;
17281            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17282        });
17283
17284        assert_eq!(
17285            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17286            expected,
17287            "Active indent guide indices do not match"
17288        );
17289    }
17290
17291    assert_eq!(indent_guides, expected, "Indent guides do not match");
17292}
17293
17294fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17295    IndentGuide {
17296        buffer_id,
17297        start_row: MultiBufferRow(start_row),
17298        end_row: MultiBufferRow(end_row),
17299        depth,
17300        tab_size: 4,
17301        settings: IndentGuideSettings {
17302            enabled: true,
17303            line_width: 1,
17304            active_line_width: 1,
17305            ..Default::default()
17306        },
17307    }
17308}
17309
17310#[gpui::test]
17311async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17312    let (buffer_id, mut cx) = setup_indent_guides_editor(
17313        &"
17314        fn main() {
17315            let a = 1;
17316        }"
17317        .unindent(),
17318        cx,
17319    )
17320    .await;
17321
17322    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17323}
17324
17325#[gpui::test]
17326async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17327    let (buffer_id, mut cx) = setup_indent_guides_editor(
17328        &"
17329        fn main() {
17330            let a = 1;
17331            let b = 2;
17332        }"
17333        .unindent(),
17334        cx,
17335    )
17336    .await;
17337
17338    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17339}
17340
17341#[gpui::test]
17342async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17343    let (buffer_id, mut cx) = setup_indent_guides_editor(
17344        &"
17345        fn main() {
17346            let a = 1;
17347            if a == 3 {
17348                let b = 2;
17349            } else {
17350                let c = 3;
17351            }
17352        }"
17353        .unindent(),
17354        cx,
17355    )
17356    .await;
17357
17358    assert_indent_guides(
17359        0..8,
17360        vec![
17361            indent_guide(buffer_id, 1, 6, 0),
17362            indent_guide(buffer_id, 3, 3, 1),
17363            indent_guide(buffer_id, 5, 5, 1),
17364        ],
17365        None,
17366        &mut cx,
17367    );
17368}
17369
17370#[gpui::test]
17371async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17372    let (buffer_id, mut cx) = setup_indent_guides_editor(
17373        &"
17374        fn main() {
17375            let a = 1;
17376                let b = 2;
17377            let c = 3;
17378        }"
17379        .unindent(),
17380        cx,
17381    )
17382    .await;
17383
17384    assert_indent_guides(
17385        0..5,
17386        vec![
17387            indent_guide(buffer_id, 1, 3, 0),
17388            indent_guide(buffer_id, 2, 2, 1),
17389        ],
17390        None,
17391        &mut cx,
17392    );
17393}
17394
17395#[gpui::test]
17396async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17397    let (buffer_id, mut cx) = setup_indent_guides_editor(
17398        &"
17399        fn main() {
17400            let a = 1;
17401
17402            let c = 3;
17403        }"
17404        .unindent(),
17405        cx,
17406    )
17407    .await;
17408
17409    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17410}
17411
17412#[gpui::test]
17413async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17414    let (buffer_id, mut cx) = setup_indent_guides_editor(
17415        &"
17416        fn main() {
17417            let a = 1;
17418
17419            let c = 3;
17420
17421            if a == 3 {
17422                let b = 2;
17423            } else {
17424                let c = 3;
17425            }
17426        }"
17427        .unindent(),
17428        cx,
17429    )
17430    .await;
17431
17432    assert_indent_guides(
17433        0..11,
17434        vec![
17435            indent_guide(buffer_id, 1, 9, 0),
17436            indent_guide(buffer_id, 6, 6, 1),
17437            indent_guide(buffer_id, 8, 8, 1),
17438        ],
17439        None,
17440        &mut cx,
17441    );
17442}
17443
17444#[gpui::test]
17445async fn test_indent_guide_starts_off_screen(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        1..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_ends_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..10,
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_with_folds(cx: &mut TestAppContext) {
17510    let (buffer_id, mut cx) = setup_indent_guides_editor(
17511        &"
17512        fn main() {
17513            if a {
17514                b(
17515                    c,
17516                    d,
17517                )
17518            } else {
17519                e(
17520                    f
17521                )
17522            }
17523        }"
17524        .unindent(),
17525        cx,
17526    )
17527    .await;
17528
17529    assert_indent_guides(
17530        0..11,
17531        vec![
17532            indent_guide(buffer_id, 1, 10, 0),
17533            indent_guide(buffer_id, 2, 5, 1),
17534            indent_guide(buffer_id, 7, 9, 1),
17535            indent_guide(buffer_id, 3, 4, 2),
17536            indent_guide(buffer_id, 8, 8, 2),
17537        ],
17538        None,
17539        &mut cx,
17540    );
17541
17542    cx.update_editor(|editor, window, cx| {
17543        editor.fold_at(MultiBufferRow(2), window, cx);
17544        assert_eq!(
17545            editor.display_text(cx),
17546            "
17547            fn main() {
17548                if a {
17549                    b(⋯
17550                    )
17551                } else {
17552                    e(
17553                        f
17554                    )
17555                }
17556            }"
17557            .unindent()
17558        );
17559    });
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, 8, 8, 2),
17568        ],
17569        None,
17570        &mut cx,
17571    );
17572}
17573
17574#[gpui::test]
17575async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17576    let (buffer_id, mut cx) = setup_indent_guides_editor(
17577        &"
17578        block1
17579            block2
17580                block3
17581                    block4
17582            block2
17583        block1
17584        block1"
17585            .unindent(),
17586        cx,
17587    )
17588    .await;
17589
17590    assert_indent_guides(
17591        1..10,
17592        vec![
17593            indent_guide(buffer_id, 1, 4, 0),
17594            indent_guide(buffer_id, 2, 3, 1),
17595            indent_guide(buffer_id, 3, 3, 2),
17596        ],
17597        None,
17598        &mut cx,
17599    );
17600}
17601
17602#[gpui::test]
17603async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17604    let (buffer_id, mut cx) = setup_indent_guides_editor(
17605        &"
17606        block1
17607            block2
17608                block3
17609
17610        block1
17611        block1"
17612            .unindent(),
17613        cx,
17614    )
17615    .await;
17616
17617    assert_indent_guides(
17618        0..6,
17619        vec![
17620            indent_guide(buffer_id, 1, 2, 0),
17621            indent_guide(buffer_id, 2, 2, 1),
17622        ],
17623        None,
17624        &mut cx,
17625    );
17626}
17627
17628#[gpui::test]
17629async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17630    let (buffer_id, mut cx) = setup_indent_guides_editor(
17631        &"
17632        function component() {
17633        \treturn (
17634        \t\t\t
17635        \t\t<div>
17636        \t\t\t<abc></abc>
17637        \t\t</div>
17638        \t)
17639        }"
17640        .unindent(),
17641        cx,
17642    )
17643    .await;
17644
17645    assert_indent_guides(
17646        0..8,
17647        vec![
17648            indent_guide(buffer_id, 1, 6, 0),
17649            indent_guide(buffer_id, 2, 5, 1),
17650            indent_guide(buffer_id, 4, 4, 2),
17651        ],
17652        None,
17653        &mut cx,
17654    );
17655}
17656
17657#[gpui::test]
17658async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17659    let (buffer_id, mut cx) = setup_indent_guides_editor(
17660        &"
17661        function component() {
17662        \treturn (
17663        \t
17664        \t\t<div>
17665        \t\t\t<abc></abc>
17666        \t\t</div>
17667        \t)
17668        }"
17669        .unindent(),
17670        cx,
17671    )
17672    .await;
17673
17674    assert_indent_guides(
17675        0..8,
17676        vec![
17677            indent_guide(buffer_id, 1, 6, 0),
17678            indent_guide(buffer_id, 2, 5, 1),
17679            indent_guide(buffer_id, 4, 4, 2),
17680        ],
17681        None,
17682        &mut cx,
17683    );
17684}
17685
17686#[gpui::test]
17687async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17688    let (buffer_id, mut cx) = setup_indent_guides_editor(
17689        &"
17690        block1
17691
17692
17693
17694            block2
17695        "
17696        .unindent(),
17697        cx,
17698    )
17699    .await;
17700
17701    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17702}
17703
17704#[gpui::test]
17705async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17706    let (buffer_id, mut cx) = setup_indent_guides_editor(
17707        &"
17708        def a:
17709        \tb = 3
17710        \tif True:
17711        \t\tc = 4
17712        \t\td = 5
17713        \tprint(b)
17714        "
17715        .unindent(),
17716        cx,
17717    )
17718    .await;
17719
17720    assert_indent_guides(
17721        0..6,
17722        vec![
17723            indent_guide(buffer_id, 1, 5, 0),
17724            indent_guide(buffer_id, 3, 4, 1),
17725        ],
17726        None,
17727        &mut cx,
17728    );
17729}
17730
17731#[gpui::test]
17732async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17733    let (buffer_id, mut cx) = setup_indent_guides_editor(
17734        &"
17735    fn main() {
17736        let a = 1;
17737    }"
17738        .unindent(),
17739        cx,
17740    )
17741    .await;
17742
17743    cx.update_editor(|editor, window, cx| {
17744        editor.change_selections(None, window, cx, |s| {
17745            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17746        });
17747    });
17748
17749    assert_indent_guides(
17750        0..3,
17751        vec![indent_guide(buffer_id, 1, 1, 0)],
17752        Some(vec![0]),
17753        &mut cx,
17754    );
17755}
17756
17757#[gpui::test]
17758async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17759    let (buffer_id, mut cx) = setup_indent_guides_editor(
17760        &"
17761    fn main() {
17762        if 1 == 2 {
17763            let a = 1;
17764        }
17765    }"
17766        .unindent(),
17767        cx,
17768    )
17769    .await;
17770
17771    cx.update_editor(|editor, window, cx| {
17772        editor.change_selections(None, window, cx, |s| {
17773            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17774        });
17775    });
17776
17777    assert_indent_guides(
17778        0..4,
17779        vec![
17780            indent_guide(buffer_id, 1, 3, 0),
17781            indent_guide(buffer_id, 2, 2, 1),
17782        ],
17783        Some(vec![1]),
17784        &mut cx,
17785    );
17786
17787    cx.update_editor(|editor, window, cx| {
17788        editor.change_selections(None, window, cx, |s| {
17789            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17790        });
17791    });
17792
17793    assert_indent_guides(
17794        0..4,
17795        vec![
17796            indent_guide(buffer_id, 1, 3, 0),
17797            indent_guide(buffer_id, 2, 2, 1),
17798        ],
17799        Some(vec![1]),
17800        &mut cx,
17801    );
17802
17803    cx.update_editor(|editor, window, cx| {
17804        editor.change_selections(None, window, cx, |s| {
17805            s.select_ranges([Point::new(3, 0)..Point::new(3, 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![0]),
17816        &mut cx,
17817    );
17818}
17819
17820#[gpui::test]
17821async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17822    let (buffer_id, mut cx) = setup_indent_guides_editor(
17823        &"
17824    fn main() {
17825        let a = 1;
17826
17827        let b = 2;
17828    }"
17829        .unindent(),
17830        cx,
17831    )
17832    .await;
17833
17834    cx.update_editor(|editor, window, cx| {
17835        editor.change_selections(None, window, cx, |s| {
17836            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17837        });
17838    });
17839
17840    assert_indent_guides(
17841        0..5,
17842        vec![indent_guide(buffer_id, 1, 3, 0)],
17843        Some(vec![0]),
17844        &mut cx,
17845    );
17846}
17847
17848#[gpui::test]
17849async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17850    let (buffer_id, mut cx) = setup_indent_guides_editor(
17851        &"
17852    def m:
17853        a = 1
17854        pass"
17855            .unindent(),
17856        cx,
17857    )
17858    .await;
17859
17860    cx.update_editor(|editor, window, cx| {
17861        editor.change_selections(None, window, cx, |s| {
17862            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17863        });
17864    });
17865
17866    assert_indent_guides(
17867        0..3,
17868        vec![indent_guide(buffer_id, 1, 2, 0)],
17869        Some(vec![0]),
17870        &mut cx,
17871    );
17872}
17873
17874#[gpui::test]
17875async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17876    init_test(cx, |_| {});
17877    let mut cx = EditorTestContext::new(cx).await;
17878    let text = indoc! {
17879        "
17880        impl A {
17881            fn b() {
17882                0;
17883                3;
17884                5;
17885                6;
17886                7;
17887            }
17888        }
17889        "
17890    };
17891    let base_text = indoc! {
17892        "
17893        impl A {
17894            fn b() {
17895                0;
17896                1;
17897                2;
17898                3;
17899                4;
17900            }
17901            fn c() {
17902                5;
17903                6;
17904                7;
17905            }
17906        }
17907        "
17908    };
17909
17910    cx.update_editor(|editor, window, cx| {
17911        editor.set_text(text, window, cx);
17912
17913        editor.buffer().update(cx, |multibuffer, cx| {
17914            let buffer = multibuffer.as_singleton().unwrap();
17915            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17916
17917            multibuffer.set_all_diff_hunks_expanded(cx);
17918            multibuffer.add_diff(diff, cx);
17919
17920            buffer.read(cx).remote_id()
17921        })
17922    });
17923    cx.run_until_parked();
17924
17925    cx.assert_state_with_diff(
17926        indoc! { "
17927          impl A {
17928              fn b() {
17929                  0;
17930        -         1;
17931        -         2;
17932                  3;
17933        -         4;
17934        -     }
17935        -     fn c() {
17936                  5;
17937                  6;
17938                  7;
17939              }
17940          }
17941          ˇ"
17942        }
17943        .to_string(),
17944    );
17945
17946    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17947        editor
17948            .snapshot(window, cx)
17949            .buffer_snapshot
17950            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17951            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17952            .collect::<Vec<_>>()
17953    });
17954    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17955    assert_eq!(
17956        actual_guides,
17957        vec![
17958            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17959            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17960            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17961        ]
17962    );
17963}
17964
17965#[gpui::test]
17966async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17967    init_test(cx, |_| {});
17968    let mut cx = EditorTestContext::new(cx).await;
17969
17970    let diff_base = r#"
17971        a
17972        b
17973        c
17974        "#
17975    .unindent();
17976
17977    cx.set_state(
17978        &r#"
17979        ˇA
17980        b
17981        C
17982        "#
17983        .unindent(),
17984    );
17985    cx.set_head_text(&diff_base);
17986    cx.update_editor(|editor, window, cx| {
17987        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17988    });
17989    executor.run_until_parked();
17990
17991    let both_hunks_expanded = r#"
17992        - a
17993        + ˇA
17994          b
17995        - c
17996        + C
17997        "#
17998    .unindent();
17999
18000    cx.assert_state_with_diff(both_hunks_expanded.clone());
18001
18002    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18003        let snapshot = editor.snapshot(window, cx);
18004        let hunks = editor
18005            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18006            .collect::<Vec<_>>();
18007        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18008        let buffer_id = hunks[0].buffer_id;
18009        hunks
18010            .into_iter()
18011            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18012            .collect::<Vec<_>>()
18013    });
18014    assert_eq!(hunk_ranges.len(), 2);
18015
18016    cx.update_editor(|editor, _, cx| {
18017        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18018    });
18019    executor.run_until_parked();
18020
18021    let second_hunk_expanded = r#"
18022          ˇA
18023          b
18024        - c
18025        + C
18026        "#
18027    .unindent();
18028
18029    cx.assert_state_with_diff(second_hunk_expanded);
18030
18031    cx.update_editor(|editor, _, cx| {
18032        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18033    });
18034    executor.run_until_parked();
18035
18036    cx.assert_state_with_diff(both_hunks_expanded.clone());
18037
18038    cx.update_editor(|editor, _, cx| {
18039        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18040    });
18041    executor.run_until_parked();
18042
18043    let first_hunk_expanded = r#"
18044        - a
18045        + ˇA
18046          b
18047          C
18048        "#
18049    .unindent();
18050
18051    cx.assert_state_with_diff(first_hunk_expanded);
18052
18053    cx.update_editor(|editor, _, cx| {
18054        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18055    });
18056    executor.run_until_parked();
18057
18058    cx.assert_state_with_diff(both_hunks_expanded);
18059
18060    cx.set_state(
18061        &r#"
18062        ˇA
18063        b
18064        "#
18065        .unindent(),
18066    );
18067    cx.run_until_parked();
18068
18069    // TODO this cursor position seems bad
18070    cx.assert_state_with_diff(
18071        r#"
18072        - ˇa
18073        + A
18074          b
18075        "#
18076        .unindent(),
18077    );
18078
18079    cx.update_editor(|editor, window, cx| {
18080        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18081    });
18082
18083    cx.assert_state_with_diff(
18084        r#"
18085            - ˇa
18086            + A
18087              b
18088            - c
18089            "#
18090        .unindent(),
18091    );
18092
18093    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18094        let snapshot = editor.snapshot(window, cx);
18095        let hunks = editor
18096            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18097            .collect::<Vec<_>>();
18098        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18099        let buffer_id = hunks[0].buffer_id;
18100        hunks
18101            .into_iter()
18102            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18103            .collect::<Vec<_>>()
18104    });
18105    assert_eq!(hunk_ranges.len(), 2);
18106
18107    cx.update_editor(|editor, _, cx| {
18108        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18109    });
18110    executor.run_until_parked();
18111
18112    cx.assert_state_with_diff(
18113        r#"
18114        - ˇa
18115        + A
18116          b
18117        "#
18118        .unindent(),
18119    );
18120}
18121
18122#[gpui::test]
18123async fn test_toggle_deletion_hunk_at_start_of_file(
18124    executor: BackgroundExecutor,
18125    cx: &mut TestAppContext,
18126) {
18127    init_test(cx, |_| {});
18128    let mut cx = EditorTestContext::new(cx).await;
18129
18130    let diff_base = r#"
18131        a
18132        b
18133        c
18134        "#
18135    .unindent();
18136
18137    cx.set_state(
18138        &r#"
18139        ˇb
18140        c
18141        "#
18142        .unindent(),
18143    );
18144    cx.set_head_text(&diff_base);
18145    cx.update_editor(|editor, window, cx| {
18146        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18147    });
18148    executor.run_until_parked();
18149
18150    let hunk_expanded = r#"
18151        - a
18152          ˇb
18153          c
18154        "#
18155    .unindent();
18156
18157    cx.assert_state_with_diff(hunk_expanded.clone());
18158
18159    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18160        let snapshot = editor.snapshot(window, cx);
18161        let hunks = editor
18162            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18163            .collect::<Vec<_>>();
18164        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18165        let buffer_id = hunks[0].buffer_id;
18166        hunks
18167            .into_iter()
18168            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18169            .collect::<Vec<_>>()
18170    });
18171    assert_eq!(hunk_ranges.len(), 1);
18172
18173    cx.update_editor(|editor, _, cx| {
18174        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18175    });
18176    executor.run_until_parked();
18177
18178    let hunk_collapsed = r#"
18179          ˇb
18180          c
18181        "#
18182    .unindent();
18183
18184    cx.assert_state_with_diff(hunk_collapsed);
18185
18186    cx.update_editor(|editor, _, cx| {
18187        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18188    });
18189    executor.run_until_parked();
18190
18191    cx.assert_state_with_diff(hunk_expanded.clone());
18192}
18193
18194#[gpui::test]
18195async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18196    init_test(cx, |_| {});
18197
18198    let fs = FakeFs::new(cx.executor());
18199    fs.insert_tree(
18200        path!("/test"),
18201        json!({
18202            ".git": {},
18203            "file-1": "ONE\n",
18204            "file-2": "TWO\n",
18205            "file-3": "THREE\n",
18206        }),
18207    )
18208    .await;
18209
18210    fs.set_head_for_repo(
18211        path!("/test/.git").as_ref(),
18212        &[
18213            ("file-1".into(), "one\n".into()),
18214            ("file-2".into(), "two\n".into()),
18215            ("file-3".into(), "three\n".into()),
18216        ],
18217        "deadbeef",
18218    );
18219
18220    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18221    let mut buffers = vec![];
18222    for i in 1..=3 {
18223        let buffer = project
18224            .update(cx, |project, cx| {
18225                let path = format!(path!("/test/file-{}"), i);
18226                project.open_local_buffer(path, cx)
18227            })
18228            .await
18229            .unwrap();
18230        buffers.push(buffer);
18231    }
18232
18233    let multibuffer = cx.new(|cx| {
18234        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18235        multibuffer.set_all_diff_hunks_expanded(cx);
18236        for buffer in &buffers {
18237            let snapshot = buffer.read(cx).snapshot();
18238            multibuffer.set_excerpts_for_path(
18239                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18240                buffer.clone(),
18241                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18242                DEFAULT_MULTIBUFFER_CONTEXT,
18243                cx,
18244            );
18245        }
18246        multibuffer
18247    });
18248
18249    let editor = cx.add_window(|window, cx| {
18250        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18251    });
18252    cx.run_until_parked();
18253
18254    let snapshot = editor
18255        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18256        .unwrap();
18257    let hunks = snapshot
18258        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18259        .map(|hunk| match hunk {
18260            DisplayDiffHunk::Unfolded {
18261                display_row_range, ..
18262            } => display_row_range,
18263            DisplayDiffHunk::Folded { .. } => unreachable!(),
18264        })
18265        .collect::<Vec<_>>();
18266    assert_eq!(
18267        hunks,
18268        [
18269            DisplayRow(2)..DisplayRow(4),
18270            DisplayRow(7)..DisplayRow(9),
18271            DisplayRow(12)..DisplayRow(14),
18272        ]
18273    );
18274}
18275
18276#[gpui::test]
18277async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18278    init_test(cx, |_| {});
18279
18280    let mut cx = EditorTestContext::new(cx).await;
18281    cx.set_head_text(indoc! { "
18282        one
18283        two
18284        three
18285        four
18286        five
18287        "
18288    });
18289    cx.set_index_text(indoc! { "
18290        one
18291        two
18292        three
18293        four
18294        five
18295        "
18296    });
18297    cx.set_state(indoc! {"
18298        one
18299        TWO
18300        ˇTHREE
18301        FOUR
18302        five
18303    "});
18304    cx.run_until_parked();
18305    cx.update_editor(|editor, window, cx| {
18306        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18307    });
18308    cx.run_until_parked();
18309    cx.assert_index_text(Some(indoc! {"
18310        one
18311        TWO
18312        THREE
18313        FOUR
18314        five
18315    "}));
18316    cx.set_state(indoc! { "
18317        one
18318        TWO
18319        ˇTHREE-HUNDRED
18320        FOUR
18321        five
18322    "});
18323    cx.run_until_parked();
18324    cx.update_editor(|editor, window, cx| {
18325        let snapshot = editor.snapshot(window, cx);
18326        let hunks = editor
18327            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18328            .collect::<Vec<_>>();
18329        assert_eq!(hunks.len(), 1);
18330        assert_eq!(
18331            hunks[0].status(),
18332            DiffHunkStatus {
18333                kind: DiffHunkStatusKind::Modified,
18334                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18335            }
18336        );
18337
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-HUNDRED
18345        FOUR
18346        five
18347    "}));
18348}
18349
18350#[gpui::test]
18351fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18352    init_test(cx, |_| {});
18353
18354    let editor = cx.add_window(|window, cx| {
18355        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18356        build_editor(buffer, window, cx)
18357    });
18358
18359    let render_args = Arc::new(Mutex::new(None));
18360    let snapshot = editor
18361        .update(cx, |editor, window, cx| {
18362            let snapshot = editor.buffer().read(cx).snapshot(cx);
18363            let range =
18364                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18365
18366            struct RenderArgs {
18367                row: MultiBufferRow,
18368                folded: bool,
18369                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18370            }
18371
18372            let crease = Crease::inline(
18373                range,
18374                FoldPlaceholder::test(),
18375                {
18376                    let toggle_callback = render_args.clone();
18377                    move |row, folded, callback, _window, _cx| {
18378                        *toggle_callback.lock() = Some(RenderArgs {
18379                            row,
18380                            folded,
18381                            callback,
18382                        });
18383                        div()
18384                    }
18385                },
18386                |_row, _folded, _window, _cx| div(),
18387            );
18388
18389            editor.insert_creases(Some(crease), cx);
18390            let snapshot = editor.snapshot(window, cx);
18391            let _div = snapshot.render_crease_toggle(
18392                MultiBufferRow(1),
18393                false,
18394                cx.entity().clone(),
18395                window,
18396                cx,
18397            );
18398            snapshot
18399        })
18400        .unwrap();
18401
18402    let render_args = render_args.lock().take().unwrap();
18403    assert_eq!(render_args.row, MultiBufferRow(1));
18404    assert!(!render_args.folded);
18405    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18406
18407    cx.update_window(*editor, |_, window, cx| {
18408        (render_args.callback)(true, window, cx)
18409    })
18410    .unwrap();
18411    let snapshot = editor
18412        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18413        .unwrap();
18414    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18415
18416    cx.update_window(*editor, |_, window, cx| {
18417        (render_args.callback)(false, window, cx)
18418    })
18419    .unwrap();
18420    let snapshot = editor
18421        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18422        .unwrap();
18423    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18424}
18425
18426#[gpui::test]
18427async fn test_input_text(cx: &mut TestAppContext) {
18428    init_test(cx, |_| {});
18429    let mut cx = EditorTestContext::new(cx).await;
18430
18431    cx.set_state(
18432        &r#"ˇone
18433        two
18434
18435        three
18436        fourˇ
18437        five
18438
18439        siˇx"#
18440            .unindent(),
18441    );
18442
18443    cx.dispatch_action(HandleInput(String::new()));
18444    cx.assert_editor_state(
18445        &r#"ˇone
18446        two
18447
18448        three
18449        fourˇ
18450        five
18451
18452        siˇx"#
18453            .unindent(),
18454    );
18455
18456    cx.dispatch_action(HandleInput("AAAA".to_string()));
18457    cx.assert_editor_state(
18458        &r#"AAAAˇone
18459        two
18460
18461        three
18462        fourAAAAˇ
18463        five
18464
18465        siAAAAˇx"#
18466            .unindent(),
18467    );
18468}
18469
18470#[gpui::test]
18471async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18472    init_test(cx, |_| {});
18473
18474    let mut cx = EditorTestContext::new(cx).await;
18475    cx.set_state(
18476        r#"let foo = 1;
18477let foo = 2;
18478let foo = 3;
18479let fooˇ = 4;
18480let foo = 5;
18481let foo = 6;
18482let foo = 7;
18483let foo = 8;
18484let foo = 9;
18485let foo = 10;
18486let foo = 11;
18487let foo = 12;
18488let foo = 13;
18489let foo = 14;
18490let foo = 15;"#,
18491    );
18492
18493    cx.update_editor(|e, window, cx| {
18494        assert_eq!(
18495            e.next_scroll_position,
18496            NextScrollCursorCenterTopBottom::Center,
18497            "Default next scroll direction is center",
18498        );
18499
18500        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18501        assert_eq!(
18502            e.next_scroll_position,
18503            NextScrollCursorCenterTopBottom::Top,
18504            "After center, next scroll direction should be top",
18505        );
18506
18507        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18508        assert_eq!(
18509            e.next_scroll_position,
18510            NextScrollCursorCenterTopBottom::Bottom,
18511            "After top, next scroll direction should be bottom",
18512        );
18513
18514        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18515        assert_eq!(
18516            e.next_scroll_position,
18517            NextScrollCursorCenterTopBottom::Center,
18518            "After bottom, scrolling should start over",
18519        );
18520
18521        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18522        assert_eq!(
18523            e.next_scroll_position,
18524            NextScrollCursorCenterTopBottom::Top,
18525            "Scrolling continues if retriggered fast enough"
18526        );
18527    });
18528
18529    cx.executor()
18530        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18531    cx.executor().run_until_parked();
18532    cx.update_editor(|e, _, _| {
18533        assert_eq!(
18534            e.next_scroll_position,
18535            NextScrollCursorCenterTopBottom::Center,
18536            "If scrolling is not triggered fast enough, it should reset"
18537        );
18538    });
18539}
18540
18541#[gpui::test]
18542async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18543    init_test(cx, |_| {});
18544    let mut cx = EditorLspTestContext::new_rust(
18545        lsp::ServerCapabilities {
18546            definition_provider: Some(lsp::OneOf::Left(true)),
18547            references_provider: Some(lsp::OneOf::Left(true)),
18548            ..lsp::ServerCapabilities::default()
18549        },
18550        cx,
18551    )
18552    .await;
18553
18554    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18555        let go_to_definition = cx
18556            .lsp
18557            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18558                move |params, _| async move {
18559                    if empty_go_to_definition {
18560                        Ok(None)
18561                    } else {
18562                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18563                            uri: params.text_document_position_params.text_document.uri,
18564                            range: lsp::Range::new(
18565                                lsp::Position::new(4, 3),
18566                                lsp::Position::new(4, 6),
18567                            ),
18568                        })))
18569                    }
18570                },
18571            );
18572        let references = cx
18573            .lsp
18574            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18575                Ok(Some(vec![lsp::Location {
18576                    uri: params.text_document_position.text_document.uri,
18577                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18578                }]))
18579            });
18580        (go_to_definition, references)
18581    };
18582
18583    cx.set_state(
18584        &r#"fn one() {
18585            let mut a = ˇtwo();
18586        }
18587
18588        fn two() {}"#
18589            .unindent(),
18590    );
18591    set_up_lsp_handlers(false, &mut cx);
18592    let navigated = cx
18593        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18594        .await
18595        .expect("Failed to navigate to definition");
18596    assert_eq!(
18597        navigated,
18598        Navigated::Yes,
18599        "Should have navigated to definition from the GetDefinition response"
18600    );
18601    cx.assert_editor_state(
18602        &r#"fn one() {
18603            let mut a = two();
18604        }
18605
18606        fn «twoˇ»() {}"#
18607            .unindent(),
18608    );
18609
18610    let editors = cx.update_workspace(|workspace, _, cx| {
18611        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18612    });
18613    cx.update_editor(|_, _, test_editor_cx| {
18614        assert_eq!(
18615            editors.len(),
18616            1,
18617            "Initially, only one, test, editor should be open in the workspace"
18618        );
18619        assert_eq!(
18620            test_editor_cx.entity(),
18621            editors.last().expect("Asserted len is 1").clone()
18622        );
18623    });
18624
18625    set_up_lsp_handlers(true, &mut cx);
18626    let navigated = cx
18627        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18628        .await
18629        .expect("Failed to navigate to lookup references");
18630    assert_eq!(
18631        navigated,
18632        Navigated::Yes,
18633        "Should have navigated to references as a fallback after empty GoToDefinition response"
18634    );
18635    // We should not change the selections in the existing file,
18636    // if opening another milti buffer with the references
18637    cx.assert_editor_state(
18638        &r#"fn one() {
18639            let mut a = two();
18640        }
18641
18642        fn «twoˇ»() {}"#
18643            .unindent(),
18644    );
18645    let editors = cx.update_workspace(|workspace, _, cx| {
18646        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18647    });
18648    cx.update_editor(|_, _, test_editor_cx| {
18649        assert_eq!(
18650            editors.len(),
18651            2,
18652            "After falling back to references search, we open a new editor with the results"
18653        );
18654        let references_fallback_text = editors
18655            .into_iter()
18656            .find(|new_editor| *new_editor != test_editor_cx.entity())
18657            .expect("Should have one non-test editor now")
18658            .read(test_editor_cx)
18659            .text(test_editor_cx);
18660        assert_eq!(
18661            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18662            "Should use the range from the references response and not the GoToDefinition one"
18663        );
18664    });
18665}
18666
18667#[gpui::test]
18668async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18669    init_test(cx, |_| {});
18670    cx.update(|cx| {
18671        let mut editor_settings = EditorSettings::get_global(cx).clone();
18672        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18673        EditorSettings::override_global(editor_settings, cx);
18674    });
18675    let mut cx = EditorLspTestContext::new_rust(
18676        lsp::ServerCapabilities {
18677            definition_provider: Some(lsp::OneOf::Left(true)),
18678            references_provider: Some(lsp::OneOf::Left(true)),
18679            ..lsp::ServerCapabilities::default()
18680        },
18681        cx,
18682    )
18683    .await;
18684    let original_state = r#"fn one() {
18685        let mut a = ˇtwo();
18686    }
18687
18688    fn two() {}"#
18689        .unindent();
18690    cx.set_state(&original_state);
18691
18692    let mut go_to_definition = cx
18693        .lsp
18694        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18695            move |_, _| async move { Ok(None) },
18696        );
18697    let _references = cx
18698        .lsp
18699        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18700            panic!("Should not call for references with no go to definition fallback")
18701        });
18702
18703    let navigated = cx
18704        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18705        .await
18706        .expect("Failed to navigate to lookup references");
18707    go_to_definition
18708        .next()
18709        .await
18710        .expect("Should have called the go_to_definition handler");
18711
18712    assert_eq!(
18713        navigated,
18714        Navigated::No,
18715        "Should have navigated to references as a fallback after empty GoToDefinition response"
18716    );
18717    cx.assert_editor_state(&original_state);
18718    let editors = cx.update_workspace(|workspace, _, cx| {
18719        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18720    });
18721    cx.update_editor(|_, _, _| {
18722        assert_eq!(
18723            editors.len(),
18724            1,
18725            "After unsuccessful fallback, no other editor should have been opened"
18726        );
18727    });
18728}
18729
18730#[gpui::test]
18731async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18732    init_test(cx, |_| {});
18733
18734    let language = Arc::new(Language::new(
18735        LanguageConfig::default(),
18736        Some(tree_sitter_rust::LANGUAGE.into()),
18737    ));
18738
18739    let text = r#"
18740        #[cfg(test)]
18741        mod tests() {
18742            #[test]
18743            fn runnable_1() {
18744                let a = 1;
18745            }
18746
18747            #[test]
18748            fn runnable_2() {
18749                let a = 1;
18750                let b = 2;
18751            }
18752        }
18753    "#
18754    .unindent();
18755
18756    let fs = FakeFs::new(cx.executor());
18757    fs.insert_file("/file.rs", Default::default()).await;
18758
18759    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18760    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18761    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18762    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18763    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18764
18765    let editor = cx.new_window_entity(|window, cx| {
18766        Editor::new(
18767            EditorMode::full(),
18768            multi_buffer,
18769            Some(project.clone()),
18770            window,
18771            cx,
18772        )
18773    });
18774
18775    editor.update_in(cx, |editor, window, cx| {
18776        let snapshot = editor.buffer().read(cx).snapshot(cx);
18777        editor.tasks.insert(
18778            (buffer.read(cx).remote_id(), 3),
18779            RunnableTasks {
18780                templates: vec![],
18781                offset: snapshot.anchor_before(43),
18782                column: 0,
18783                extra_variables: HashMap::default(),
18784                context_range: BufferOffset(43)..BufferOffset(85),
18785            },
18786        );
18787        editor.tasks.insert(
18788            (buffer.read(cx).remote_id(), 8),
18789            RunnableTasks {
18790                templates: vec![],
18791                offset: snapshot.anchor_before(86),
18792                column: 0,
18793                extra_variables: HashMap::default(),
18794                context_range: BufferOffset(86)..BufferOffset(191),
18795            },
18796        );
18797
18798        // Test finding task when cursor is inside function body
18799        editor.change_selections(None, window, cx, |s| {
18800            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18801        });
18802        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18803        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18804
18805        // Test finding task when cursor is on function name
18806        editor.change_selections(None, window, cx, |s| {
18807            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18808        });
18809        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18810        assert_eq!(row, 8, "Should find task when cursor is on function name");
18811    });
18812}
18813
18814#[gpui::test]
18815async fn test_folding_buffers(cx: &mut TestAppContext) {
18816    init_test(cx, |_| {});
18817
18818    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18819    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18820    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18821
18822    let fs = FakeFs::new(cx.executor());
18823    fs.insert_tree(
18824        path!("/a"),
18825        json!({
18826            "first.rs": sample_text_1,
18827            "second.rs": sample_text_2,
18828            "third.rs": sample_text_3,
18829        }),
18830    )
18831    .await;
18832    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18833    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18834    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18835    let worktree = project.update(cx, |project, cx| {
18836        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18837        assert_eq!(worktrees.len(), 1);
18838        worktrees.pop().unwrap()
18839    });
18840    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18841
18842    let buffer_1 = project
18843        .update(cx, |project, cx| {
18844            project.open_buffer((worktree_id, "first.rs"), cx)
18845        })
18846        .await
18847        .unwrap();
18848    let buffer_2 = project
18849        .update(cx, |project, cx| {
18850            project.open_buffer((worktree_id, "second.rs"), cx)
18851        })
18852        .await
18853        .unwrap();
18854    let buffer_3 = project
18855        .update(cx, |project, cx| {
18856            project.open_buffer((worktree_id, "third.rs"), cx)
18857        })
18858        .await
18859        .unwrap();
18860
18861    let multi_buffer = cx.new(|cx| {
18862        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18863        multi_buffer.push_excerpts(
18864            buffer_1.clone(),
18865            [
18866                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18867                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18868                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18869            ],
18870            cx,
18871        );
18872        multi_buffer.push_excerpts(
18873            buffer_2.clone(),
18874            [
18875                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18876                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18877                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18878            ],
18879            cx,
18880        );
18881        multi_buffer.push_excerpts(
18882            buffer_3.clone(),
18883            [
18884                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18885                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18886                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18887            ],
18888            cx,
18889        );
18890        multi_buffer
18891    });
18892    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18893        Editor::new(
18894            EditorMode::full(),
18895            multi_buffer.clone(),
18896            Some(project.clone()),
18897            window,
18898            cx,
18899        )
18900    });
18901
18902    assert_eq!(
18903        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18904        "\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",
18905    );
18906
18907    multi_buffer_editor.update(cx, |editor, cx| {
18908        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18909    });
18910    assert_eq!(
18911        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18912        "\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",
18913        "After folding the first buffer, its text should not be displayed"
18914    );
18915
18916    multi_buffer_editor.update(cx, |editor, cx| {
18917        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18918    });
18919    assert_eq!(
18920        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18921        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18922        "After folding the second buffer, its text should not be displayed"
18923    );
18924
18925    multi_buffer_editor.update(cx, |editor, cx| {
18926        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18927    });
18928    assert_eq!(
18929        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18930        "\n\n\n\n\n",
18931        "After folding the third buffer, its text should not be displayed"
18932    );
18933
18934    // Emulate selection inside the fold logic, that should work
18935    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18936        editor
18937            .snapshot(window, cx)
18938            .next_line_boundary(Point::new(0, 4));
18939    });
18940
18941    multi_buffer_editor.update(cx, |editor, cx| {
18942        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18943    });
18944    assert_eq!(
18945        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18946        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18947        "After unfolding the second buffer, its text should be displayed"
18948    );
18949
18950    // Typing inside of buffer 1 causes that buffer to be unfolded.
18951    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18952        assert_eq!(
18953            multi_buffer
18954                .read(cx)
18955                .snapshot(cx)
18956                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18957                .collect::<String>(),
18958            "bbbb"
18959        );
18960        editor.change_selections(None, window, cx, |selections| {
18961            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18962        });
18963        editor.handle_input("B", window, cx);
18964    });
18965
18966    assert_eq!(
18967        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18968        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18969        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18970    );
18971
18972    multi_buffer_editor.update(cx, |editor, cx| {
18973        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18974    });
18975    assert_eq!(
18976        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18977        "\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",
18978        "After unfolding the all buffers, all original text should be displayed"
18979    );
18980}
18981
18982#[gpui::test]
18983async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18984    init_test(cx, |_| {});
18985
18986    let sample_text_1 = "1111\n2222\n3333".to_string();
18987    let sample_text_2 = "4444\n5555\n6666".to_string();
18988    let sample_text_3 = "7777\n8888\n9999".to_string();
18989
18990    let fs = FakeFs::new(cx.executor());
18991    fs.insert_tree(
18992        path!("/a"),
18993        json!({
18994            "first.rs": sample_text_1,
18995            "second.rs": sample_text_2,
18996            "third.rs": sample_text_3,
18997        }),
18998    )
18999    .await;
19000    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19001    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19002    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19003    let worktree = project.update(cx, |project, cx| {
19004        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19005        assert_eq!(worktrees.len(), 1);
19006        worktrees.pop().unwrap()
19007    });
19008    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19009
19010    let buffer_1 = project
19011        .update(cx, |project, cx| {
19012            project.open_buffer((worktree_id, "first.rs"), cx)
19013        })
19014        .await
19015        .unwrap();
19016    let buffer_2 = project
19017        .update(cx, |project, cx| {
19018            project.open_buffer((worktree_id, "second.rs"), cx)
19019        })
19020        .await
19021        .unwrap();
19022    let buffer_3 = project
19023        .update(cx, |project, cx| {
19024            project.open_buffer((worktree_id, "third.rs"), cx)
19025        })
19026        .await
19027        .unwrap();
19028
19029    let multi_buffer = cx.new(|cx| {
19030        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19031        multi_buffer.push_excerpts(
19032            buffer_1.clone(),
19033            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19034            cx,
19035        );
19036        multi_buffer.push_excerpts(
19037            buffer_2.clone(),
19038            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19039            cx,
19040        );
19041        multi_buffer.push_excerpts(
19042            buffer_3.clone(),
19043            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19044            cx,
19045        );
19046        multi_buffer
19047    });
19048
19049    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19050        Editor::new(
19051            EditorMode::full(),
19052            multi_buffer,
19053            Some(project.clone()),
19054            window,
19055            cx,
19056        )
19057    });
19058
19059    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19060    assert_eq!(
19061        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19062        full_text,
19063    );
19064
19065    multi_buffer_editor.update(cx, |editor, cx| {
19066        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19067    });
19068    assert_eq!(
19069        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19070        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19071        "After folding the first buffer, its text should not be displayed"
19072    );
19073
19074    multi_buffer_editor.update(cx, |editor, cx| {
19075        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19076    });
19077
19078    assert_eq!(
19079        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19080        "\n\n\n\n\n\n7777\n8888\n9999",
19081        "After folding the second buffer, its text should not be displayed"
19082    );
19083
19084    multi_buffer_editor.update(cx, |editor, cx| {
19085        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19086    });
19087    assert_eq!(
19088        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19089        "\n\n\n\n\n",
19090        "After folding the third buffer, its text should not be displayed"
19091    );
19092
19093    multi_buffer_editor.update(cx, |editor, cx| {
19094        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19095    });
19096    assert_eq!(
19097        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19098        "\n\n\n\n4444\n5555\n6666\n\n",
19099        "After unfolding the second buffer, its text should be displayed"
19100    );
19101
19102    multi_buffer_editor.update(cx, |editor, cx| {
19103        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19104    });
19105    assert_eq!(
19106        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19107        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19108        "After unfolding the first buffer, its text should be displayed"
19109    );
19110
19111    multi_buffer_editor.update(cx, |editor, cx| {
19112        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19113    });
19114    assert_eq!(
19115        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19116        full_text,
19117        "After unfolding all buffers, all original text should be displayed"
19118    );
19119}
19120
19121#[gpui::test]
19122async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19123    init_test(cx, |_| {});
19124
19125    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19126
19127    let fs = FakeFs::new(cx.executor());
19128    fs.insert_tree(
19129        path!("/a"),
19130        json!({
19131            "main.rs": sample_text,
19132        }),
19133    )
19134    .await;
19135    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19136    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19137    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19138    let worktree = project.update(cx, |project, cx| {
19139        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19140        assert_eq!(worktrees.len(), 1);
19141        worktrees.pop().unwrap()
19142    });
19143    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19144
19145    let buffer_1 = project
19146        .update(cx, |project, cx| {
19147            project.open_buffer((worktree_id, "main.rs"), cx)
19148        })
19149        .await
19150        .unwrap();
19151
19152    let multi_buffer = cx.new(|cx| {
19153        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19154        multi_buffer.push_excerpts(
19155            buffer_1.clone(),
19156            [ExcerptRange::new(
19157                Point::new(0, 0)
19158                    ..Point::new(
19159                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19160                        0,
19161                    ),
19162            )],
19163            cx,
19164        );
19165        multi_buffer
19166    });
19167    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19168        Editor::new(
19169            EditorMode::full(),
19170            multi_buffer,
19171            Some(project.clone()),
19172            window,
19173            cx,
19174        )
19175    });
19176
19177    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19178    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19179        enum TestHighlight {}
19180        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19181        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19182        editor.highlight_text::<TestHighlight>(
19183            vec![highlight_range.clone()],
19184            HighlightStyle::color(Hsla::green()),
19185            cx,
19186        );
19187        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19188    });
19189
19190    let full_text = format!("\n\n{sample_text}");
19191    assert_eq!(
19192        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19193        full_text,
19194    );
19195}
19196
19197#[gpui::test]
19198async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19199    init_test(cx, |_| {});
19200    cx.update(|cx| {
19201        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19202            "keymaps/default-linux.json",
19203            cx,
19204        )
19205        .unwrap();
19206        cx.bind_keys(default_key_bindings);
19207    });
19208
19209    let (editor, cx) = cx.add_window_view(|window, cx| {
19210        let multi_buffer = MultiBuffer::build_multi(
19211            [
19212                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19213                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19214                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19215                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19216            ],
19217            cx,
19218        );
19219        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19220
19221        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19222        // fold all but the second buffer, so that we test navigating between two
19223        // adjacent folded buffers, as well as folded buffers at the start and
19224        // end the multibuffer
19225        editor.fold_buffer(buffer_ids[0], cx);
19226        editor.fold_buffer(buffer_ids[2], cx);
19227        editor.fold_buffer(buffer_ids[3], cx);
19228
19229        editor
19230    });
19231    cx.simulate_resize(size(px(1000.), px(1000.)));
19232
19233    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19234    cx.assert_excerpts_with_selections(indoc! {"
19235        [EXCERPT]
19236        ˇ[FOLDED]
19237        [EXCERPT]
19238        a1
19239        b1
19240        [EXCERPT]
19241        [FOLDED]
19242        [EXCERPT]
19243        [FOLDED]
19244        "
19245    });
19246    cx.simulate_keystroke("down");
19247    cx.assert_excerpts_with_selections(indoc! {"
19248        [EXCERPT]
19249        [FOLDED]
19250        [EXCERPT]
19251        ˇa1
19252        b1
19253        [EXCERPT]
19254        [FOLDED]
19255        [EXCERPT]
19256        [FOLDED]
19257        "
19258    });
19259    cx.simulate_keystroke("down");
19260    cx.assert_excerpts_with_selections(indoc! {"
19261        [EXCERPT]
19262        [FOLDED]
19263        [EXCERPT]
19264        a1
19265        ˇb1
19266        [EXCERPT]
19267        [FOLDED]
19268        [EXCERPT]
19269        [FOLDED]
19270        "
19271    });
19272    cx.simulate_keystroke("down");
19273    cx.assert_excerpts_with_selections(indoc! {"
19274        [EXCERPT]
19275        [FOLDED]
19276        [EXCERPT]
19277        a1
19278        b1
19279        ˇ[EXCERPT]
19280        [FOLDED]
19281        [EXCERPT]
19282        [FOLDED]
19283        "
19284    });
19285    cx.simulate_keystroke("down");
19286    cx.assert_excerpts_with_selections(indoc! {"
19287        [EXCERPT]
19288        [FOLDED]
19289        [EXCERPT]
19290        a1
19291        b1
19292        [EXCERPT]
19293        ˇ[FOLDED]
19294        [EXCERPT]
19295        [FOLDED]
19296        "
19297    });
19298    for _ in 0..5 {
19299        cx.simulate_keystroke("down");
19300        cx.assert_excerpts_with_selections(indoc! {"
19301            [EXCERPT]
19302            [FOLDED]
19303            [EXCERPT]
19304            a1
19305            b1
19306            [EXCERPT]
19307            [FOLDED]
19308            [EXCERPT]
19309            ˇ[FOLDED]
19310            "
19311        });
19312    }
19313
19314    cx.simulate_keystroke("up");
19315    cx.assert_excerpts_with_selections(indoc! {"
19316        [EXCERPT]
19317        [FOLDED]
19318        [EXCERPT]
19319        a1
19320        b1
19321        [EXCERPT]
19322        ˇ[FOLDED]
19323        [EXCERPT]
19324        [FOLDED]
19325        "
19326    });
19327    cx.simulate_keystroke("up");
19328    cx.assert_excerpts_with_selections(indoc! {"
19329        [EXCERPT]
19330        [FOLDED]
19331        [EXCERPT]
19332        a1
19333        b1
19334        ˇ[EXCERPT]
19335        [FOLDED]
19336        [EXCERPT]
19337        [FOLDED]
19338        "
19339    });
19340    cx.simulate_keystroke("up");
19341    cx.assert_excerpts_with_selections(indoc! {"
19342        [EXCERPT]
19343        [FOLDED]
19344        [EXCERPT]
19345        a1
19346        ˇb1
19347        [EXCERPT]
19348        [FOLDED]
19349        [EXCERPT]
19350        [FOLDED]
19351        "
19352    });
19353    cx.simulate_keystroke("up");
19354    cx.assert_excerpts_with_selections(indoc! {"
19355        [EXCERPT]
19356        [FOLDED]
19357        [EXCERPT]
19358        ˇa1
19359        b1
19360        [EXCERPT]
19361        [FOLDED]
19362        [EXCERPT]
19363        [FOLDED]
19364        "
19365    });
19366    for _ in 0..5 {
19367        cx.simulate_keystroke("up");
19368        cx.assert_excerpts_with_selections(indoc! {"
19369            [EXCERPT]
19370            ˇ[FOLDED]
19371            [EXCERPT]
19372            a1
19373            b1
19374            [EXCERPT]
19375            [FOLDED]
19376            [EXCERPT]
19377            [FOLDED]
19378            "
19379        });
19380    }
19381}
19382
19383#[gpui::test]
19384async fn test_inline_completion_text(cx: &mut TestAppContext) {
19385    init_test(cx, |_| {});
19386
19387    // Simple insertion
19388    assert_highlighted_edits(
19389        "Hello, world!",
19390        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19391        true,
19392        cx,
19393        |highlighted_edits, cx| {
19394            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19395            assert_eq!(highlighted_edits.highlights.len(), 1);
19396            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19397            assert_eq!(
19398                highlighted_edits.highlights[0].1.background_color,
19399                Some(cx.theme().status().created_background)
19400            );
19401        },
19402    )
19403    .await;
19404
19405    // Replacement
19406    assert_highlighted_edits(
19407        "This is a test.",
19408        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19409        false,
19410        cx,
19411        |highlighted_edits, cx| {
19412            assert_eq!(highlighted_edits.text, "That is a test.");
19413            assert_eq!(highlighted_edits.highlights.len(), 1);
19414            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19415            assert_eq!(
19416                highlighted_edits.highlights[0].1.background_color,
19417                Some(cx.theme().status().created_background)
19418            );
19419        },
19420    )
19421    .await;
19422
19423    // Multiple edits
19424    assert_highlighted_edits(
19425        "Hello, world!",
19426        vec![
19427            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19428            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19429        ],
19430        false,
19431        cx,
19432        |highlighted_edits, cx| {
19433            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19434            assert_eq!(highlighted_edits.highlights.len(), 2);
19435            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19436            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19437            assert_eq!(
19438                highlighted_edits.highlights[0].1.background_color,
19439                Some(cx.theme().status().created_background)
19440            );
19441            assert_eq!(
19442                highlighted_edits.highlights[1].1.background_color,
19443                Some(cx.theme().status().created_background)
19444            );
19445        },
19446    )
19447    .await;
19448
19449    // Multiple lines with edits
19450    assert_highlighted_edits(
19451        "First line\nSecond line\nThird line\nFourth line",
19452        vec![
19453            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19454            (
19455                Point::new(2, 0)..Point::new(2, 10),
19456                "New third line".to_string(),
19457            ),
19458            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19459        ],
19460        false,
19461        cx,
19462        |highlighted_edits, cx| {
19463            assert_eq!(
19464                highlighted_edits.text,
19465                "Second modified\nNew third line\nFourth updated line"
19466            );
19467            assert_eq!(highlighted_edits.highlights.len(), 3);
19468            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19469            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19470            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19471            for highlight in &highlighted_edits.highlights {
19472                assert_eq!(
19473                    highlight.1.background_color,
19474                    Some(cx.theme().status().created_background)
19475                );
19476            }
19477        },
19478    )
19479    .await;
19480}
19481
19482#[gpui::test]
19483async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19484    init_test(cx, |_| {});
19485
19486    // Deletion
19487    assert_highlighted_edits(
19488        "Hello, world!",
19489        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19490        true,
19491        cx,
19492        |highlighted_edits, cx| {
19493            assert_eq!(highlighted_edits.text, "Hello, world!");
19494            assert_eq!(highlighted_edits.highlights.len(), 1);
19495            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19496            assert_eq!(
19497                highlighted_edits.highlights[0].1.background_color,
19498                Some(cx.theme().status().deleted_background)
19499            );
19500        },
19501    )
19502    .await;
19503
19504    // Insertion
19505    assert_highlighted_edits(
19506        "Hello, world!",
19507        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19508        true,
19509        cx,
19510        |highlighted_edits, cx| {
19511            assert_eq!(highlighted_edits.highlights.len(), 1);
19512            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19513            assert_eq!(
19514                highlighted_edits.highlights[0].1.background_color,
19515                Some(cx.theme().status().created_background)
19516            );
19517        },
19518    )
19519    .await;
19520}
19521
19522async fn assert_highlighted_edits(
19523    text: &str,
19524    edits: Vec<(Range<Point>, String)>,
19525    include_deletions: bool,
19526    cx: &mut TestAppContext,
19527    assertion_fn: impl Fn(HighlightedText, &App),
19528) {
19529    let window = cx.add_window(|window, cx| {
19530        let buffer = MultiBuffer::build_simple(text, cx);
19531        Editor::new(EditorMode::full(), buffer, None, window, cx)
19532    });
19533    let cx = &mut VisualTestContext::from_window(*window, cx);
19534
19535    let (buffer, snapshot) = window
19536        .update(cx, |editor, _window, cx| {
19537            (
19538                editor.buffer().clone(),
19539                editor.buffer().read(cx).snapshot(cx),
19540            )
19541        })
19542        .unwrap();
19543
19544    let edits = edits
19545        .into_iter()
19546        .map(|(range, edit)| {
19547            (
19548                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19549                edit,
19550            )
19551        })
19552        .collect::<Vec<_>>();
19553
19554    let text_anchor_edits = edits
19555        .clone()
19556        .into_iter()
19557        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19558        .collect::<Vec<_>>();
19559
19560    let edit_preview = window
19561        .update(cx, |_, _window, cx| {
19562            buffer
19563                .read(cx)
19564                .as_singleton()
19565                .unwrap()
19566                .read(cx)
19567                .preview_edits(text_anchor_edits.into(), cx)
19568        })
19569        .unwrap()
19570        .await;
19571
19572    cx.update(|_window, cx| {
19573        let highlighted_edits = inline_completion_edit_text(
19574            &snapshot.as_singleton().unwrap().2,
19575            &edits,
19576            &edit_preview,
19577            include_deletions,
19578            cx,
19579        );
19580        assertion_fn(highlighted_edits, cx)
19581    });
19582}
19583
19584#[track_caller]
19585fn assert_breakpoint(
19586    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19587    path: &Arc<Path>,
19588    expected: Vec<(u32, Breakpoint)>,
19589) {
19590    if expected.len() == 0usize {
19591        assert!(!breakpoints.contains_key(path), "{}", path.display());
19592    } else {
19593        let mut breakpoint = breakpoints
19594            .get(path)
19595            .unwrap()
19596            .into_iter()
19597            .map(|breakpoint| {
19598                (
19599                    breakpoint.row,
19600                    Breakpoint {
19601                        message: breakpoint.message.clone(),
19602                        state: breakpoint.state,
19603                        condition: breakpoint.condition.clone(),
19604                        hit_condition: breakpoint.hit_condition.clone(),
19605                    },
19606                )
19607            })
19608            .collect::<Vec<_>>();
19609
19610        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19611
19612        assert_eq!(expected, breakpoint);
19613    }
19614}
19615
19616fn add_log_breakpoint_at_cursor(
19617    editor: &mut Editor,
19618    log_message: &str,
19619    window: &mut Window,
19620    cx: &mut Context<Editor>,
19621) {
19622    let (anchor, bp) = editor
19623        .breakpoints_at_cursors(window, cx)
19624        .first()
19625        .and_then(|(anchor, bp)| {
19626            if let Some(bp) = bp {
19627                Some((*anchor, bp.clone()))
19628            } else {
19629                None
19630            }
19631        })
19632        .unwrap_or_else(|| {
19633            let cursor_position: Point = editor.selections.newest(cx).head();
19634
19635            let breakpoint_position = editor
19636                .snapshot(window, cx)
19637                .display_snapshot
19638                .buffer_snapshot
19639                .anchor_before(Point::new(cursor_position.row, 0));
19640
19641            (breakpoint_position, Breakpoint::new_log(&log_message))
19642        });
19643
19644    editor.edit_breakpoint_at_anchor(
19645        anchor,
19646        bp,
19647        BreakpointEditAction::EditLogMessage(log_message.into()),
19648        cx,
19649    );
19650}
19651
19652#[gpui::test]
19653async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19654    init_test(cx, |_| {});
19655
19656    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19657    let fs = FakeFs::new(cx.executor());
19658    fs.insert_tree(
19659        path!("/a"),
19660        json!({
19661            "main.rs": sample_text,
19662        }),
19663    )
19664    .await;
19665    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19666    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19667    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19668
19669    let fs = FakeFs::new(cx.executor());
19670    fs.insert_tree(
19671        path!("/a"),
19672        json!({
19673            "main.rs": sample_text,
19674        }),
19675    )
19676    .await;
19677    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19678    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19679    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19680    let worktree_id = workspace
19681        .update(cx, |workspace, _window, cx| {
19682            workspace.project().update(cx, |project, cx| {
19683                project.worktrees(cx).next().unwrap().read(cx).id()
19684            })
19685        })
19686        .unwrap();
19687
19688    let buffer = project
19689        .update(cx, |project, cx| {
19690            project.open_buffer((worktree_id, "main.rs"), cx)
19691        })
19692        .await
19693        .unwrap();
19694
19695    let (editor, cx) = cx.add_window_view(|window, cx| {
19696        Editor::new(
19697            EditorMode::full(),
19698            MultiBuffer::build_from_buffer(buffer, cx),
19699            Some(project.clone()),
19700            window,
19701            cx,
19702        )
19703    });
19704
19705    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19706    let abs_path = project.read_with(cx, |project, cx| {
19707        project
19708            .absolute_path(&project_path, cx)
19709            .map(|path_buf| Arc::from(path_buf.to_owned()))
19710            .unwrap()
19711    });
19712
19713    // assert we can add breakpoint on the first line
19714    editor.update_in(cx, |editor, window, cx| {
19715        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19716        editor.move_to_end(&MoveToEnd, window, cx);
19717        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19718    });
19719
19720    let breakpoints = editor.update(cx, |editor, cx| {
19721        editor
19722            .breakpoint_store()
19723            .as_ref()
19724            .unwrap()
19725            .read(cx)
19726            .all_source_breakpoints(cx)
19727            .clone()
19728    });
19729
19730    assert_eq!(1, breakpoints.len());
19731    assert_breakpoint(
19732        &breakpoints,
19733        &abs_path,
19734        vec![
19735            (0, Breakpoint::new_standard()),
19736            (3, Breakpoint::new_standard()),
19737        ],
19738    );
19739
19740    editor.update_in(cx, |editor, window, cx| {
19741        editor.move_to_beginning(&MoveToBeginning, window, cx);
19742        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19743    });
19744
19745    let breakpoints = editor.update(cx, |editor, cx| {
19746        editor
19747            .breakpoint_store()
19748            .as_ref()
19749            .unwrap()
19750            .read(cx)
19751            .all_source_breakpoints(cx)
19752            .clone()
19753    });
19754
19755    assert_eq!(1, breakpoints.len());
19756    assert_breakpoint(
19757        &breakpoints,
19758        &abs_path,
19759        vec![(3, Breakpoint::new_standard())],
19760    );
19761
19762    editor.update_in(cx, |editor, window, cx| {
19763        editor.move_to_end(&MoveToEnd, window, cx);
19764        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19765    });
19766
19767    let breakpoints = editor.update(cx, |editor, cx| {
19768        editor
19769            .breakpoint_store()
19770            .as_ref()
19771            .unwrap()
19772            .read(cx)
19773            .all_source_breakpoints(cx)
19774            .clone()
19775    });
19776
19777    assert_eq!(0, breakpoints.len());
19778    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19779}
19780
19781#[gpui::test]
19782async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19783    init_test(cx, |_| {});
19784
19785    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19786
19787    let fs = FakeFs::new(cx.executor());
19788    fs.insert_tree(
19789        path!("/a"),
19790        json!({
19791            "main.rs": sample_text,
19792        }),
19793    )
19794    .await;
19795    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19796    let (workspace, cx) =
19797        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19798
19799    let worktree_id = workspace.update(cx, |workspace, cx| {
19800        workspace.project().update(cx, |project, cx| {
19801            project.worktrees(cx).next().unwrap().read(cx).id()
19802        })
19803    });
19804
19805    let buffer = project
19806        .update(cx, |project, cx| {
19807            project.open_buffer((worktree_id, "main.rs"), cx)
19808        })
19809        .await
19810        .unwrap();
19811
19812    let (editor, cx) = cx.add_window_view(|window, cx| {
19813        Editor::new(
19814            EditorMode::full(),
19815            MultiBuffer::build_from_buffer(buffer, cx),
19816            Some(project.clone()),
19817            window,
19818            cx,
19819        )
19820    });
19821
19822    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19823    let abs_path = project.read_with(cx, |project, cx| {
19824        project
19825            .absolute_path(&project_path, cx)
19826            .map(|path_buf| Arc::from(path_buf.to_owned()))
19827            .unwrap()
19828    });
19829
19830    editor.update_in(cx, |editor, window, cx| {
19831        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19832    });
19833
19834    let breakpoints = editor.update(cx, |editor, cx| {
19835        editor
19836            .breakpoint_store()
19837            .as_ref()
19838            .unwrap()
19839            .read(cx)
19840            .all_source_breakpoints(cx)
19841            .clone()
19842    });
19843
19844    assert_breakpoint(
19845        &breakpoints,
19846        &abs_path,
19847        vec![(0, Breakpoint::new_log("hello world"))],
19848    );
19849
19850    // Removing a log message from a log breakpoint should remove it
19851    editor.update_in(cx, |editor, window, cx| {
19852        add_log_breakpoint_at_cursor(editor, "", window, cx);
19853    });
19854
19855    let breakpoints = editor.update(cx, |editor, cx| {
19856        editor
19857            .breakpoint_store()
19858            .as_ref()
19859            .unwrap()
19860            .read(cx)
19861            .all_source_breakpoints(cx)
19862            .clone()
19863    });
19864
19865    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19866
19867    editor.update_in(cx, |editor, window, cx| {
19868        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19869        editor.move_to_end(&MoveToEnd, window, cx);
19870        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19871        // Not adding a log message to a standard breakpoint shouldn't remove it
19872        add_log_breakpoint_at_cursor(editor, "", window, cx);
19873    });
19874
19875    let breakpoints = editor.update(cx, |editor, cx| {
19876        editor
19877            .breakpoint_store()
19878            .as_ref()
19879            .unwrap()
19880            .read(cx)
19881            .all_source_breakpoints(cx)
19882            .clone()
19883    });
19884
19885    assert_breakpoint(
19886        &breakpoints,
19887        &abs_path,
19888        vec![
19889            (0, Breakpoint::new_standard()),
19890            (3, Breakpoint::new_standard()),
19891        ],
19892    );
19893
19894    editor.update_in(cx, |editor, window, cx| {
19895        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19896    });
19897
19898    let breakpoints = editor.update(cx, |editor, cx| {
19899        editor
19900            .breakpoint_store()
19901            .as_ref()
19902            .unwrap()
19903            .read(cx)
19904            .all_source_breakpoints(cx)
19905            .clone()
19906    });
19907
19908    assert_breakpoint(
19909        &breakpoints,
19910        &abs_path,
19911        vec![
19912            (0, Breakpoint::new_standard()),
19913            (3, Breakpoint::new_log("hello world")),
19914        ],
19915    );
19916
19917    editor.update_in(cx, |editor, window, cx| {
19918        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19919    });
19920
19921    let breakpoints = editor.update(cx, |editor, cx| {
19922        editor
19923            .breakpoint_store()
19924            .as_ref()
19925            .unwrap()
19926            .read(cx)
19927            .all_source_breakpoints(cx)
19928            .clone()
19929    });
19930
19931    assert_breakpoint(
19932        &breakpoints,
19933        &abs_path,
19934        vec![
19935            (0, Breakpoint::new_standard()),
19936            (3, Breakpoint::new_log("hello Earth!!")),
19937        ],
19938    );
19939}
19940
19941/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19942/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19943/// or when breakpoints were placed out of order. This tests for a regression too
19944#[gpui::test]
19945async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19946    init_test(cx, |_| {});
19947
19948    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19949    let fs = FakeFs::new(cx.executor());
19950    fs.insert_tree(
19951        path!("/a"),
19952        json!({
19953            "main.rs": sample_text,
19954        }),
19955    )
19956    .await;
19957    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19958    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19959    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19960
19961    let fs = FakeFs::new(cx.executor());
19962    fs.insert_tree(
19963        path!("/a"),
19964        json!({
19965            "main.rs": sample_text,
19966        }),
19967    )
19968    .await;
19969    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19970    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19971    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19972    let worktree_id = workspace
19973        .update(cx, |workspace, _window, cx| {
19974            workspace.project().update(cx, |project, cx| {
19975                project.worktrees(cx).next().unwrap().read(cx).id()
19976            })
19977        })
19978        .unwrap();
19979
19980    let buffer = project
19981        .update(cx, |project, cx| {
19982            project.open_buffer((worktree_id, "main.rs"), cx)
19983        })
19984        .await
19985        .unwrap();
19986
19987    let (editor, cx) = cx.add_window_view(|window, cx| {
19988        Editor::new(
19989            EditorMode::full(),
19990            MultiBuffer::build_from_buffer(buffer, cx),
19991            Some(project.clone()),
19992            window,
19993            cx,
19994        )
19995    });
19996
19997    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19998    let abs_path = project.read_with(cx, |project, cx| {
19999        project
20000            .absolute_path(&project_path, cx)
20001            .map(|path_buf| Arc::from(path_buf.to_owned()))
20002            .unwrap()
20003    });
20004
20005    // assert we can add breakpoint on the first line
20006    editor.update_in(cx, |editor, window, cx| {
20007        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20008        editor.move_to_end(&MoveToEnd, window, cx);
20009        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20010        editor.move_up(&MoveUp, window, cx);
20011        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20012    });
20013
20014    let breakpoints = editor.update(cx, |editor, cx| {
20015        editor
20016            .breakpoint_store()
20017            .as_ref()
20018            .unwrap()
20019            .read(cx)
20020            .all_source_breakpoints(cx)
20021            .clone()
20022    });
20023
20024    assert_eq!(1, breakpoints.len());
20025    assert_breakpoint(
20026        &breakpoints,
20027        &abs_path,
20028        vec![
20029            (0, Breakpoint::new_standard()),
20030            (2, Breakpoint::new_standard()),
20031            (3, Breakpoint::new_standard()),
20032        ],
20033    );
20034
20035    editor.update_in(cx, |editor, window, cx| {
20036        editor.move_to_beginning(&MoveToBeginning, window, cx);
20037        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20038        editor.move_to_end(&MoveToEnd, window, cx);
20039        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20040        // Disabling a breakpoint that doesn't exist should do nothing
20041        editor.move_up(&MoveUp, window, cx);
20042        editor.move_up(&MoveUp, window, cx);
20043        editor.disable_breakpoint(&actions::DisableBreakpoint, 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    let disable_breakpoint = {
20057        let mut bp = Breakpoint::new_standard();
20058        bp.state = BreakpointState::Disabled;
20059        bp
20060    };
20061
20062    assert_eq!(1, breakpoints.len());
20063    assert_breakpoint(
20064        &breakpoints,
20065        &abs_path,
20066        vec![
20067            (0, disable_breakpoint.clone()),
20068            (2, Breakpoint::new_standard()),
20069            (3, disable_breakpoint.clone()),
20070        ],
20071    );
20072
20073    editor.update_in(cx, |editor, window, cx| {
20074        editor.move_to_beginning(&MoveToBeginning, window, cx);
20075        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20076        editor.move_to_end(&MoveToEnd, window, cx);
20077        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20078        editor.move_up(&MoveUp, window, cx);
20079        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20080    });
20081
20082    let breakpoints = editor.update(cx, |editor, cx| {
20083        editor
20084            .breakpoint_store()
20085            .as_ref()
20086            .unwrap()
20087            .read(cx)
20088            .all_source_breakpoints(cx)
20089            .clone()
20090    });
20091
20092    assert_eq!(1, breakpoints.len());
20093    assert_breakpoint(
20094        &breakpoints,
20095        &abs_path,
20096        vec![
20097            (0, Breakpoint::new_standard()),
20098            (2, disable_breakpoint),
20099            (3, Breakpoint::new_standard()),
20100        ],
20101    );
20102}
20103
20104#[gpui::test]
20105async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20106    init_test(cx, |_| {});
20107    let capabilities = lsp::ServerCapabilities {
20108        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20109            prepare_provider: Some(true),
20110            work_done_progress_options: Default::default(),
20111        })),
20112        ..Default::default()
20113    };
20114    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20115
20116    cx.set_state(indoc! {"
20117        struct Fˇoo {}
20118    "});
20119
20120    cx.update_editor(|editor, _, cx| {
20121        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20122        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20123        editor.highlight_background::<DocumentHighlightRead>(
20124            &[highlight_range],
20125            |c| c.editor_document_highlight_read_background,
20126            cx,
20127        );
20128    });
20129
20130    let mut prepare_rename_handler = cx
20131        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20132            move |_, _, _| async move {
20133                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20134                    start: lsp::Position {
20135                        line: 0,
20136                        character: 7,
20137                    },
20138                    end: lsp::Position {
20139                        line: 0,
20140                        character: 10,
20141                    },
20142                })))
20143            },
20144        );
20145    let prepare_rename_task = cx
20146        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20147        .expect("Prepare rename was not started");
20148    prepare_rename_handler.next().await.unwrap();
20149    prepare_rename_task.await.expect("Prepare rename failed");
20150
20151    let mut rename_handler =
20152        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20153            let edit = lsp::TextEdit {
20154                range: lsp::Range {
20155                    start: lsp::Position {
20156                        line: 0,
20157                        character: 7,
20158                    },
20159                    end: lsp::Position {
20160                        line: 0,
20161                        character: 10,
20162                    },
20163                },
20164                new_text: "FooRenamed".to_string(),
20165            };
20166            Ok(Some(lsp::WorkspaceEdit::new(
20167                // Specify the same edit twice
20168                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20169            )))
20170        });
20171    let rename_task = cx
20172        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20173        .expect("Confirm rename was not started");
20174    rename_handler.next().await.unwrap();
20175    rename_task.await.expect("Confirm rename failed");
20176    cx.run_until_parked();
20177
20178    // Despite two edits, only one is actually applied as those are identical
20179    cx.assert_editor_state(indoc! {"
20180        struct FooRenamedˇ {}
20181    "});
20182}
20183
20184#[gpui::test]
20185async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20186    init_test(cx, |_| {});
20187    // These capabilities indicate that the server does not support prepare rename.
20188    let capabilities = lsp::ServerCapabilities {
20189        rename_provider: Some(lsp::OneOf::Left(true)),
20190        ..Default::default()
20191    };
20192    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20193
20194    cx.set_state(indoc! {"
20195        struct Fˇoo {}
20196    "});
20197
20198    cx.update_editor(|editor, _window, cx| {
20199        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20200        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20201        editor.highlight_background::<DocumentHighlightRead>(
20202            &[highlight_range],
20203            |c| c.editor_document_highlight_read_background,
20204            cx,
20205        );
20206    });
20207
20208    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20209        .expect("Prepare rename was not started")
20210        .await
20211        .expect("Prepare rename failed");
20212
20213    let mut rename_handler =
20214        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20215            let edit = lsp::TextEdit {
20216                range: lsp::Range {
20217                    start: lsp::Position {
20218                        line: 0,
20219                        character: 7,
20220                    },
20221                    end: lsp::Position {
20222                        line: 0,
20223                        character: 10,
20224                    },
20225                },
20226                new_text: "FooRenamed".to_string(),
20227            };
20228            Ok(Some(lsp::WorkspaceEdit::new(
20229                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20230            )))
20231        });
20232    let rename_task = cx
20233        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20234        .expect("Confirm rename was not started");
20235    rename_handler.next().await.unwrap();
20236    rename_task.await.expect("Confirm rename failed");
20237    cx.run_until_parked();
20238
20239    // Correct range is renamed, as `surrounding_word` is used to find it.
20240    cx.assert_editor_state(indoc! {"
20241        struct FooRenamedˇ {}
20242    "});
20243}
20244
20245#[gpui::test]
20246async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20247    init_test(cx, |_| {});
20248    let mut cx = EditorTestContext::new(cx).await;
20249
20250    let language = Arc::new(
20251        Language::new(
20252            LanguageConfig::default(),
20253            Some(tree_sitter_html::LANGUAGE.into()),
20254        )
20255        .with_brackets_query(
20256            r#"
20257            ("<" @open "/>" @close)
20258            ("</" @open ">" @close)
20259            ("<" @open ">" @close)
20260            ("\"" @open "\"" @close)
20261            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20262        "#,
20263        )
20264        .unwrap(),
20265    );
20266    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20267
20268    cx.set_state(indoc! {"
20269        <span>ˇ</span>
20270    "});
20271    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20272    cx.assert_editor_state(indoc! {"
20273        <span>
20274        ˇ
20275        </span>
20276    "});
20277
20278    cx.set_state(indoc! {"
20279        <span><span></span>ˇ</span>
20280    "});
20281    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20282    cx.assert_editor_state(indoc! {"
20283        <span><span></span>
20284        ˇ</span>
20285    "});
20286
20287    cx.set_state(indoc! {"
20288        <span>ˇ
20289        </span>
20290    "});
20291    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20292    cx.assert_editor_state(indoc! {"
20293        <span>
20294        ˇ
20295        </span>
20296    "});
20297}
20298
20299#[gpui::test(iterations = 10)]
20300async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20301    init_test(cx, |_| {});
20302
20303    let fs = FakeFs::new(cx.executor());
20304    fs.insert_tree(
20305        path!("/dir"),
20306        json!({
20307            "a.ts": "a",
20308        }),
20309    )
20310    .await;
20311
20312    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20313    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20314    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20315
20316    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20317    language_registry.add(Arc::new(Language::new(
20318        LanguageConfig {
20319            name: "TypeScript".into(),
20320            matcher: LanguageMatcher {
20321                path_suffixes: vec!["ts".to_string()],
20322                ..Default::default()
20323            },
20324            ..Default::default()
20325        },
20326        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20327    )));
20328    let mut fake_language_servers = language_registry.register_fake_lsp(
20329        "TypeScript",
20330        FakeLspAdapter {
20331            capabilities: lsp::ServerCapabilities {
20332                code_lens_provider: Some(lsp::CodeLensOptions {
20333                    resolve_provider: Some(true),
20334                }),
20335                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20336                    commands: vec!["_the/command".to_string()],
20337                    ..lsp::ExecuteCommandOptions::default()
20338                }),
20339                ..lsp::ServerCapabilities::default()
20340            },
20341            ..FakeLspAdapter::default()
20342        },
20343    );
20344
20345    let (buffer, _handle) = project
20346        .update(cx, |p, cx| {
20347            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20348        })
20349        .await
20350        .unwrap();
20351    cx.executor().run_until_parked();
20352
20353    let fake_server = fake_language_servers.next().await.unwrap();
20354
20355    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20356    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20357    drop(buffer_snapshot);
20358    let actions = cx
20359        .update_window(*workspace, |_, window, cx| {
20360            project.code_actions(&buffer, anchor..anchor, window, cx)
20361        })
20362        .unwrap();
20363
20364    fake_server
20365        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20366            Ok(Some(vec![
20367                lsp::CodeLens {
20368                    range: lsp::Range::default(),
20369                    command: Some(lsp::Command {
20370                        title: "Code lens command".to_owned(),
20371                        command: "_the/command".to_owned(),
20372                        arguments: None,
20373                    }),
20374                    data: None,
20375                },
20376                lsp::CodeLens {
20377                    range: lsp::Range::default(),
20378                    command: Some(lsp::Command {
20379                        title: "Command not in capabilities".to_owned(),
20380                        command: "not in capabilities".to_owned(),
20381                        arguments: None,
20382                    }),
20383                    data: None,
20384                },
20385                lsp::CodeLens {
20386                    range: lsp::Range {
20387                        start: lsp::Position {
20388                            line: 1,
20389                            character: 1,
20390                        },
20391                        end: lsp::Position {
20392                            line: 1,
20393                            character: 1,
20394                        },
20395                    },
20396                    command: Some(lsp::Command {
20397                        title: "Command not in range".to_owned(),
20398                        command: "_the/command".to_owned(),
20399                        arguments: None,
20400                    }),
20401                    data: None,
20402                },
20403            ]))
20404        })
20405        .next()
20406        .await;
20407
20408    let actions = actions.await.unwrap();
20409    assert_eq!(
20410        actions.len(),
20411        1,
20412        "Should have only one valid action for the 0..0 range"
20413    );
20414    let action = actions[0].clone();
20415    let apply = project.update(cx, |project, cx| {
20416        project.apply_code_action(buffer.clone(), action, true, cx)
20417    });
20418
20419    // Resolving the code action does not populate its edits. In absence of
20420    // edits, we must execute the given command.
20421    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20422        |mut lens, _| async move {
20423            let lens_command = lens.command.as_mut().expect("should have a command");
20424            assert_eq!(lens_command.title, "Code lens command");
20425            lens_command.arguments = Some(vec![json!("the-argument")]);
20426            Ok(lens)
20427        },
20428    );
20429
20430    // While executing the command, the language server sends the editor
20431    // a `workspaceEdit` request.
20432    fake_server
20433        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20434            let fake = fake_server.clone();
20435            move |params, _| {
20436                assert_eq!(params.command, "_the/command");
20437                let fake = fake.clone();
20438                async move {
20439                    fake.server
20440                        .request::<lsp::request::ApplyWorkspaceEdit>(
20441                            lsp::ApplyWorkspaceEditParams {
20442                                label: None,
20443                                edit: lsp::WorkspaceEdit {
20444                                    changes: Some(
20445                                        [(
20446                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20447                                            vec![lsp::TextEdit {
20448                                                range: lsp::Range::new(
20449                                                    lsp::Position::new(0, 0),
20450                                                    lsp::Position::new(0, 0),
20451                                                ),
20452                                                new_text: "X".into(),
20453                                            }],
20454                                        )]
20455                                        .into_iter()
20456                                        .collect(),
20457                                    ),
20458                                    ..Default::default()
20459                                },
20460                            },
20461                        )
20462                        .await
20463                        .into_response()
20464                        .unwrap();
20465                    Ok(Some(json!(null)))
20466                }
20467            }
20468        })
20469        .next()
20470        .await;
20471
20472    // Applying the code lens command returns a project transaction containing the edits
20473    // sent by the language server in its `workspaceEdit` request.
20474    let transaction = apply.await.unwrap();
20475    assert!(transaction.0.contains_key(&buffer));
20476    buffer.update(cx, |buffer, cx| {
20477        assert_eq!(buffer.text(), "Xa");
20478        buffer.undo(cx);
20479        assert_eq!(buffer.text(), "a");
20480    });
20481}
20482
20483#[gpui::test]
20484async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20485    init_test(cx, |_| {});
20486
20487    let fs = FakeFs::new(cx.executor());
20488    let main_text = r#"fn main() {
20489println!("1");
20490println!("2");
20491println!("3");
20492println!("4");
20493println!("5");
20494}"#;
20495    let lib_text = "mod foo {}";
20496    fs.insert_tree(
20497        path!("/a"),
20498        json!({
20499            "lib.rs": lib_text,
20500            "main.rs": main_text,
20501        }),
20502    )
20503    .await;
20504
20505    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20506    let (workspace, cx) =
20507        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20508    let worktree_id = workspace.update(cx, |workspace, cx| {
20509        workspace.project().update(cx, |project, cx| {
20510            project.worktrees(cx).next().unwrap().read(cx).id()
20511        })
20512    });
20513
20514    let expected_ranges = vec![
20515        Point::new(0, 0)..Point::new(0, 0),
20516        Point::new(1, 0)..Point::new(1, 1),
20517        Point::new(2, 0)..Point::new(2, 2),
20518        Point::new(3, 0)..Point::new(3, 3),
20519    ];
20520
20521    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20522    let editor_1 = workspace
20523        .update_in(cx, |workspace, window, cx| {
20524            workspace.open_path(
20525                (worktree_id, "main.rs"),
20526                Some(pane_1.downgrade()),
20527                true,
20528                window,
20529                cx,
20530            )
20531        })
20532        .unwrap()
20533        .await
20534        .downcast::<Editor>()
20535        .unwrap();
20536    pane_1.update(cx, |pane, cx| {
20537        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20538        open_editor.update(cx, |editor, cx| {
20539            assert_eq!(
20540                editor.display_text(cx),
20541                main_text,
20542                "Original main.rs text on initial open",
20543            );
20544            assert_eq!(
20545                editor
20546                    .selections
20547                    .all::<Point>(cx)
20548                    .into_iter()
20549                    .map(|s| s.range())
20550                    .collect::<Vec<_>>(),
20551                vec![Point::zero()..Point::zero()],
20552                "Default selections on initial open",
20553            );
20554        })
20555    });
20556    editor_1.update_in(cx, |editor, window, cx| {
20557        editor.change_selections(None, window, cx, |s| {
20558            s.select_ranges(expected_ranges.clone());
20559        });
20560    });
20561
20562    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20563        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20564    });
20565    let editor_2 = workspace
20566        .update_in(cx, |workspace, window, cx| {
20567            workspace.open_path(
20568                (worktree_id, "main.rs"),
20569                Some(pane_2.downgrade()),
20570                true,
20571                window,
20572                cx,
20573            )
20574        })
20575        .unwrap()
20576        .await
20577        .downcast::<Editor>()
20578        .unwrap();
20579    pane_2.update(cx, |pane, cx| {
20580        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20581        open_editor.update(cx, |editor, cx| {
20582            assert_eq!(
20583                editor.display_text(cx),
20584                main_text,
20585                "Original main.rs text on initial open in another panel",
20586            );
20587            assert_eq!(
20588                editor
20589                    .selections
20590                    .all::<Point>(cx)
20591                    .into_iter()
20592                    .map(|s| s.range())
20593                    .collect::<Vec<_>>(),
20594                vec![Point::zero()..Point::zero()],
20595                "Default selections on initial open in another panel",
20596            );
20597        })
20598    });
20599
20600    editor_2.update_in(cx, |editor, window, cx| {
20601        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20602    });
20603
20604    let _other_editor_1 = workspace
20605        .update_in(cx, |workspace, window, cx| {
20606            workspace.open_path(
20607                (worktree_id, "lib.rs"),
20608                Some(pane_1.downgrade()),
20609                true,
20610                window,
20611                cx,
20612            )
20613        })
20614        .unwrap()
20615        .await
20616        .downcast::<Editor>()
20617        .unwrap();
20618    pane_1
20619        .update_in(cx, |pane, window, cx| {
20620            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20621        })
20622        .await
20623        .unwrap();
20624    drop(editor_1);
20625    pane_1.update(cx, |pane, cx| {
20626        pane.active_item()
20627            .unwrap()
20628            .downcast::<Editor>()
20629            .unwrap()
20630            .update(cx, |editor, cx| {
20631                assert_eq!(
20632                    editor.display_text(cx),
20633                    lib_text,
20634                    "Other file should be open and active",
20635                );
20636            });
20637        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20638    });
20639
20640    let _other_editor_2 = workspace
20641        .update_in(cx, |workspace, window, cx| {
20642            workspace.open_path(
20643                (worktree_id, "lib.rs"),
20644                Some(pane_2.downgrade()),
20645                true,
20646                window,
20647                cx,
20648            )
20649        })
20650        .unwrap()
20651        .await
20652        .downcast::<Editor>()
20653        .unwrap();
20654    pane_2
20655        .update_in(cx, |pane, window, cx| {
20656            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20657        })
20658        .await
20659        .unwrap();
20660    drop(editor_2);
20661    pane_2.update(cx, |pane, cx| {
20662        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20663        open_editor.update(cx, |editor, cx| {
20664            assert_eq!(
20665                editor.display_text(cx),
20666                lib_text,
20667                "Other file should be open and active in another panel too",
20668            );
20669        });
20670        assert_eq!(
20671            pane.items().count(),
20672            1,
20673            "No other editors should be open in another pane",
20674        );
20675    });
20676
20677    let _editor_1_reopened = workspace
20678        .update_in(cx, |workspace, window, cx| {
20679            workspace.open_path(
20680                (worktree_id, "main.rs"),
20681                Some(pane_1.downgrade()),
20682                true,
20683                window,
20684                cx,
20685            )
20686        })
20687        .unwrap()
20688        .await
20689        .downcast::<Editor>()
20690        .unwrap();
20691    let _editor_2_reopened = workspace
20692        .update_in(cx, |workspace, window, cx| {
20693            workspace.open_path(
20694                (worktree_id, "main.rs"),
20695                Some(pane_2.downgrade()),
20696                true,
20697                window,
20698                cx,
20699            )
20700        })
20701        .unwrap()
20702        .await
20703        .downcast::<Editor>()
20704        .unwrap();
20705    pane_1.update(cx, |pane, cx| {
20706        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20707        open_editor.update(cx, |editor, cx| {
20708            assert_eq!(
20709                editor.display_text(cx),
20710                main_text,
20711                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20712            );
20713            assert_eq!(
20714                editor
20715                    .selections
20716                    .all::<Point>(cx)
20717                    .into_iter()
20718                    .map(|s| s.range())
20719                    .collect::<Vec<_>>(),
20720                expected_ranges,
20721                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20722            );
20723        })
20724    });
20725    pane_2.update(cx, |pane, cx| {
20726        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20727        open_editor.update(cx, |editor, cx| {
20728            assert_eq!(
20729                editor.display_text(cx),
20730                r#"fn main() {
20731⋯rintln!("1");
20732⋯intln!("2");
20733⋯ntln!("3");
20734println!("4");
20735println!("5");
20736}"#,
20737                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20738            );
20739            assert_eq!(
20740                editor
20741                    .selections
20742                    .all::<Point>(cx)
20743                    .into_iter()
20744                    .map(|s| s.range())
20745                    .collect::<Vec<_>>(),
20746                vec![Point::zero()..Point::zero()],
20747                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20748            );
20749        })
20750    });
20751}
20752
20753#[gpui::test]
20754async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20755    init_test(cx, |_| {});
20756
20757    let fs = FakeFs::new(cx.executor());
20758    let main_text = r#"fn main() {
20759println!("1");
20760println!("2");
20761println!("3");
20762println!("4");
20763println!("5");
20764}"#;
20765    let lib_text = "mod foo {}";
20766    fs.insert_tree(
20767        path!("/a"),
20768        json!({
20769            "lib.rs": lib_text,
20770            "main.rs": main_text,
20771        }),
20772    )
20773    .await;
20774
20775    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20776    let (workspace, cx) =
20777        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20778    let worktree_id = workspace.update(cx, |workspace, cx| {
20779        workspace.project().update(cx, |project, cx| {
20780            project.worktrees(cx).next().unwrap().read(cx).id()
20781        })
20782    });
20783
20784    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20785    let editor = workspace
20786        .update_in(cx, |workspace, window, cx| {
20787            workspace.open_path(
20788                (worktree_id, "main.rs"),
20789                Some(pane.downgrade()),
20790                true,
20791                window,
20792                cx,
20793            )
20794        })
20795        .unwrap()
20796        .await
20797        .downcast::<Editor>()
20798        .unwrap();
20799    pane.update(cx, |pane, cx| {
20800        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20801        open_editor.update(cx, |editor, cx| {
20802            assert_eq!(
20803                editor.display_text(cx),
20804                main_text,
20805                "Original main.rs text on initial open",
20806            );
20807        })
20808    });
20809    editor.update_in(cx, |editor, window, cx| {
20810        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20811    });
20812
20813    cx.update_global(|store: &mut SettingsStore, cx| {
20814        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20815            s.restore_on_file_reopen = Some(false);
20816        });
20817    });
20818    editor.update_in(cx, |editor, window, cx| {
20819        editor.fold_ranges(
20820            vec![
20821                Point::new(1, 0)..Point::new(1, 1),
20822                Point::new(2, 0)..Point::new(2, 2),
20823                Point::new(3, 0)..Point::new(3, 3),
20824            ],
20825            false,
20826            window,
20827            cx,
20828        );
20829    });
20830    pane.update_in(cx, |pane, window, cx| {
20831        pane.close_all_items(&CloseAllItems::default(), window, cx)
20832    })
20833    .await
20834    .unwrap();
20835    pane.update(cx, |pane, _| {
20836        assert!(pane.active_item().is_none());
20837    });
20838    cx.update_global(|store: &mut SettingsStore, cx| {
20839        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20840            s.restore_on_file_reopen = Some(true);
20841        });
20842    });
20843
20844    let _editor_reopened = workspace
20845        .update_in(cx, |workspace, window, cx| {
20846            workspace.open_path(
20847                (worktree_id, "main.rs"),
20848                Some(pane.downgrade()),
20849                true,
20850                window,
20851                cx,
20852            )
20853        })
20854        .unwrap()
20855        .await
20856        .downcast::<Editor>()
20857        .unwrap();
20858    pane.update(cx, |pane, cx| {
20859        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20860        open_editor.update(cx, |editor, cx| {
20861            assert_eq!(
20862                editor.display_text(cx),
20863                main_text,
20864                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20865            );
20866        })
20867    });
20868}
20869
20870#[gpui::test]
20871async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20872    struct EmptyModalView {
20873        focus_handle: gpui::FocusHandle,
20874    }
20875    impl EventEmitter<DismissEvent> for EmptyModalView {}
20876    impl Render for EmptyModalView {
20877        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20878            div()
20879        }
20880    }
20881    impl Focusable for EmptyModalView {
20882        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20883            self.focus_handle.clone()
20884        }
20885    }
20886    impl workspace::ModalView for EmptyModalView {}
20887    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20888        EmptyModalView {
20889            focus_handle: cx.focus_handle(),
20890        }
20891    }
20892
20893    init_test(cx, |_| {});
20894
20895    let fs = FakeFs::new(cx.executor());
20896    let project = Project::test(fs, [], cx).await;
20897    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20898    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20899    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20900    let editor = cx.new_window_entity(|window, cx| {
20901        Editor::new(
20902            EditorMode::full(),
20903            buffer,
20904            Some(project.clone()),
20905            window,
20906            cx,
20907        )
20908    });
20909    workspace
20910        .update(cx, |workspace, window, cx| {
20911            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20912        })
20913        .unwrap();
20914    editor.update_in(cx, |editor, window, cx| {
20915        editor.open_context_menu(&OpenContextMenu, window, cx);
20916        assert!(editor.mouse_context_menu.is_some());
20917    });
20918    workspace
20919        .update(cx, |workspace, window, cx| {
20920            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20921        })
20922        .unwrap();
20923    cx.read(|cx| {
20924        assert!(editor.read(cx).mouse_context_menu.is_none());
20925    });
20926}
20927
20928#[gpui::test]
20929async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20930    init_test(cx, |_| {});
20931
20932    let fs = FakeFs::new(cx.executor());
20933    fs.insert_file(path!("/file.html"), Default::default())
20934        .await;
20935
20936    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20937
20938    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20939    let html_language = Arc::new(Language::new(
20940        LanguageConfig {
20941            name: "HTML".into(),
20942            matcher: LanguageMatcher {
20943                path_suffixes: vec!["html".to_string()],
20944                ..LanguageMatcher::default()
20945            },
20946            brackets: BracketPairConfig {
20947                pairs: vec![BracketPair {
20948                    start: "<".into(),
20949                    end: ">".into(),
20950                    close: true,
20951                    ..Default::default()
20952                }],
20953                ..Default::default()
20954            },
20955            ..Default::default()
20956        },
20957        Some(tree_sitter_html::LANGUAGE.into()),
20958    ));
20959    language_registry.add(html_language);
20960    let mut fake_servers = language_registry.register_fake_lsp(
20961        "HTML",
20962        FakeLspAdapter {
20963            capabilities: lsp::ServerCapabilities {
20964                completion_provider: Some(lsp::CompletionOptions {
20965                    resolve_provider: Some(true),
20966                    ..Default::default()
20967                }),
20968                ..Default::default()
20969            },
20970            ..Default::default()
20971        },
20972    );
20973
20974    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20975    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20976
20977    let worktree_id = workspace
20978        .update(cx, |workspace, _window, cx| {
20979            workspace.project().update(cx, |project, cx| {
20980                project.worktrees(cx).next().unwrap().read(cx).id()
20981            })
20982        })
20983        .unwrap();
20984    project
20985        .update(cx, |project, cx| {
20986            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20987        })
20988        .await
20989        .unwrap();
20990    let editor = workspace
20991        .update(cx, |workspace, window, cx| {
20992            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20993        })
20994        .unwrap()
20995        .await
20996        .unwrap()
20997        .downcast::<Editor>()
20998        .unwrap();
20999
21000    let fake_server = fake_servers.next().await.unwrap();
21001    editor.update_in(cx, |editor, window, cx| {
21002        editor.set_text("<ad></ad>", window, cx);
21003        editor.change_selections(None, window, cx, |selections| {
21004            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21005        });
21006        let Some((buffer, _)) = editor
21007            .buffer
21008            .read(cx)
21009            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21010        else {
21011            panic!("Failed to get buffer for selection position");
21012        };
21013        let buffer = buffer.read(cx);
21014        let buffer_id = buffer.remote_id();
21015        let opening_range =
21016            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21017        let closing_range =
21018            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21019        let mut linked_ranges = HashMap::default();
21020        linked_ranges.insert(
21021            buffer_id,
21022            vec![(opening_range.clone(), vec![closing_range.clone()])],
21023        );
21024        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21025    });
21026    let mut completion_handle =
21027        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21028            Ok(Some(lsp::CompletionResponse::Array(vec![
21029                lsp::CompletionItem {
21030                    label: "head".to_string(),
21031                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21032                        lsp::InsertReplaceEdit {
21033                            new_text: "head".to_string(),
21034                            insert: lsp::Range::new(
21035                                lsp::Position::new(0, 1),
21036                                lsp::Position::new(0, 3),
21037                            ),
21038                            replace: lsp::Range::new(
21039                                lsp::Position::new(0, 1),
21040                                lsp::Position::new(0, 3),
21041                            ),
21042                        },
21043                    )),
21044                    ..Default::default()
21045                },
21046            ])))
21047        });
21048    editor.update_in(cx, |editor, window, cx| {
21049        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21050    });
21051    cx.run_until_parked();
21052    completion_handle.next().await.unwrap();
21053    editor.update(cx, |editor, _| {
21054        assert!(
21055            editor.context_menu_visible(),
21056            "Completion menu should be visible"
21057        );
21058    });
21059    editor.update_in(cx, |editor, window, cx| {
21060        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21061    });
21062    cx.executor().run_until_parked();
21063    editor.update(cx, |editor, cx| {
21064        assert_eq!(editor.text(cx), "<head></head>");
21065    });
21066}
21067
21068#[gpui::test]
21069async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21070    init_test(cx, |_| {});
21071
21072    let fs = FakeFs::new(cx.executor());
21073    fs.insert_tree(
21074        path!("/root"),
21075        json!({
21076            "a": {
21077                "main.rs": "fn main() {}",
21078            },
21079            "foo": {
21080                "bar": {
21081                    "external_file.rs": "pub mod external {}",
21082                }
21083            }
21084        }),
21085    )
21086    .await;
21087
21088    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21089    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21090    language_registry.add(rust_lang());
21091    let _fake_servers = language_registry.register_fake_lsp(
21092        "Rust",
21093        FakeLspAdapter {
21094            ..FakeLspAdapter::default()
21095        },
21096    );
21097    let (workspace, cx) =
21098        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21099    let worktree_id = workspace.update(cx, |workspace, cx| {
21100        workspace.project().update(cx, |project, cx| {
21101            project.worktrees(cx).next().unwrap().read(cx).id()
21102        })
21103    });
21104
21105    let assert_language_servers_count =
21106        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21107            project.update(cx, |project, cx| {
21108                let current = project
21109                    .lsp_store()
21110                    .read(cx)
21111                    .as_local()
21112                    .unwrap()
21113                    .language_servers
21114                    .len();
21115                assert_eq!(expected, current, "{context}");
21116            });
21117        };
21118
21119    assert_language_servers_count(
21120        0,
21121        "No servers should be running before any file is open",
21122        cx,
21123    );
21124    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21125    let main_editor = workspace
21126        .update_in(cx, |workspace, window, cx| {
21127            workspace.open_path(
21128                (worktree_id, "main.rs"),
21129                Some(pane.downgrade()),
21130                true,
21131                window,
21132                cx,
21133            )
21134        })
21135        .unwrap()
21136        .await
21137        .downcast::<Editor>()
21138        .unwrap();
21139    pane.update(cx, |pane, cx| {
21140        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21141        open_editor.update(cx, |editor, cx| {
21142            assert_eq!(
21143                editor.display_text(cx),
21144                "fn main() {}",
21145                "Original main.rs text on initial open",
21146            );
21147        });
21148        assert_eq!(open_editor, main_editor);
21149    });
21150    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21151
21152    let external_editor = workspace
21153        .update_in(cx, |workspace, window, cx| {
21154            workspace.open_abs_path(
21155                PathBuf::from("/root/foo/bar/external_file.rs"),
21156                OpenOptions::default(),
21157                window,
21158                cx,
21159            )
21160        })
21161        .await
21162        .expect("opening external file")
21163        .downcast::<Editor>()
21164        .expect("downcasted external file's open element to editor");
21165    pane.update(cx, |pane, cx| {
21166        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21167        open_editor.update(cx, |editor, cx| {
21168            assert_eq!(
21169                editor.display_text(cx),
21170                "pub mod external {}",
21171                "External file is open now",
21172            );
21173        });
21174        assert_eq!(open_editor, external_editor);
21175    });
21176    assert_language_servers_count(
21177        1,
21178        "Second, external, *.rs file should join the existing server",
21179        cx,
21180    );
21181
21182    pane.update_in(cx, |pane, window, cx| {
21183        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21184    })
21185    .await
21186    .unwrap();
21187    pane.update_in(cx, |pane, window, cx| {
21188        pane.navigate_backward(window, cx);
21189    });
21190    cx.run_until_parked();
21191    pane.update(cx, |pane, cx| {
21192        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21193        open_editor.update(cx, |editor, cx| {
21194            assert_eq!(
21195                editor.display_text(cx),
21196                "pub mod external {}",
21197                "External file is open now",
21198            );
21199        });
21200    });
21201    assert_language_servers_count(
21202        1,
21203        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21204        cx,
21205    );
21206
21207    cx.update(|_, cx| {
21208        workspace::reload(&workspace::Reload::default(), cx);
21209    });
21210    assert_language_servers_count(
21211        1,
21212        "After reloading the worktree with local and external files opened, only one project should be started",
21213        cx,
21214    );
21215}
21216
21217#[gpui::test]
21218async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21219    init_test(cx, |_| {});
21220
21221    let mut cx = EditorTestContext::new(cx).await;
21222    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21223    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21224
21225    // test cursor move to start of each line on tab
21226    // for `if`, `elif`, `else`, `while`, `with` and `for`
21227    cx.set_state(indoc! {"
21228        def main():
21229        ˇ    for item in items:
21230        ˇ        while item.active:
21231        ˇ            if item.value > 10:
21232        ˇ                continue
21233        ˇ            elif item.value < 0:
21234        ˇ                break
21235        ˇ            else:
21236        ˇ                with item.context() as ctx:
21237        ˇ                    yield count
21238        ˇ        else:
21239        ˇ            log('while else')
21240        ˇ    else:
21241        ˇ        log('for else')
21242    "});
21243    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21244    cx.assert_editor_state(indoc! {"
21245        def main():
21246            ˇfor item in items:
21247                ˇwhile item.active:
21248                    ˇif item.value > 10:
21249                        ˇcontinue
21250                    ˇelif item.value < 0:
21251                        ˇbreak
21252                    ˇelse:
21253                        ˇwith item.context() as ctx:
21254                            ˇyield count
21255                ˇelse:
21256                    ˇlog('while else')
21257            ˇelse:
21258                ˇlog('for else')
21259    "});
21260    // test relative indent is preserved when tab
21261    // for `if`, `elif`, `else`, `while`, `with` and `for`
21262    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21263    cx.assert_editor_state(indoc! {"
21264        def main():
21265                ˇfor item in items:
21266                    ˇwhile item.active:
21267                        ˇif item.value > 10:
21268                            ˇcontinue
21269                        ˇelif item.value < 0:
21270                            ˇbreak
21271                        ˇelse:
21272                            ˇwith item.context() as ctx:
21273                                ˇyield count
21274                    ˇelse:
21275                        ˇlog('while else')
21276                ˇelse:
21277                    ˇlog('for else')
21278    "});
21279
21280    // test cursor move to start of each line on tab
21281    // for `try`, `except`, `else`, `finally`, `match` and `def`
21282    cx.set_state(indoc! {"
21283        def main():
21284        ˇ    try:
21285        ˇ       fetch()
21286        ˇ    except ValueError:
21287        ˇ       handle_error()
21288        ˇ    else:
21289        ˇ        match value:
21290        ˇ            case _:
21291        ˇ    finally:
21292        ˇ        def status():
21293        ˇ            return 0
21294    "});
21295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21296    cx.assert_editor_state(indoc! {"
21297        def main():
21298            ˇtry:
21299                ˇfetch()
21300            ˇexcept ValueError:
21301                ˇhandle_error()
21302            ˇelse:
21303                ˇmatch value:
21304                    ˇcase _:
21305            ˇfinally:
21306                ˇdef status():
21307                    ˇreturn 0
21308    "});
21309    // test relative indent is preserved when tab
21310    // for `try`, `except`, `else`, `finally`, `match` and `def`
21311    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21312    cx.assert_editor_state(indoc! {"
21313        def main():
21314                ˇtry:
21315                    ˇfetch()
21316                ˇexcept ValueError:
21317                    ˇhandle_error()
21318                ˇelse:
21319                    ˇmatch value:
21320                        ˇcase _:
21321                ˇfinally:
21322                    ˇdef status():
21323                        ˇreturn 0
21324    "});
21325}
21326
21327#[gpui::test]
21328async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21329    init_test(cx, |_| {});
21330
21331    let mut cx = EditorTestContext::new(cx).await;
21332    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21333    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21334
21335    // test `else` auto outdents when typed inside `if` block
21336    cx.set_state(indoc! {"
21337        def main():
21338            if i == 2:
21339                return
21340                ˇ
21341    "});
21342    cx.update_editor(|editor, window, cx| {
21343        editor.handle_input("else:", window, cx);
21344    });
21345    cx.assert_editor_state(indoc! {"
21346        def main():
21347            if i == 2:
21348                return
21349            else:ˇ
21350    "});
21351
21352    // test `except` auto outdents when typed inside `try` block
21353    cx.set_state(indoc! {"
21354        def main():
21355            try:
21356                i = 2
21357                ˇ
21358    "});
21359    cx.update_editor(|editor, window, cx| {
21360        editor.handle_input("except:", window, cx);
21361    });
21362    cx.assert_editor_state(indoc! {"
21363        def main():
21364            try:
21365                i = 2
21366            except:ˇ
21367    "});
21368
21369    // test `else` auto outdents when typed inside `except` block
21370    cx.set_state(indoc! {"
21371        def main():
21372            try:
21373                i = 2
21374            except:
21375                j = 2
21376                ˇ
21377    "});
21378    cx.update_editor(|editor, window, cx| {
21379        editor.handle_input("else:", window, cx);
21380    });
21381    cx.assert_editor_state(indoc! {"
21382        def main():
21383            try:
21384                i = 2
21385            except:
21386                j = 2
21387            else:ˇ
21388    "});
21389
21390    // test `finally` auto outdents when typed inside `else` block
21391    cx.set_state(indoc! {"
21392        def main():
21393            try:
21394                i = 2
21395            except:
21396                j = 2
21397            else:
21398                k = 2
21399                ˇ
21400    "});
21401    cx.update_editor(|editor, window, cx| {
21402        editor.handle_input("finally:", window, cx);
21403    });
21404    cx.assert_editor_state(indoc! {"
21405        def main():
21406            try:
21407                i = 2
21408            except:
21409                j = 2
21410            else:
21411                k = 2
21412            finally:ˇ
21413    "});
21414
21415    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21416    // cx.set_state(indoc! {"
21417    //     def main():
21418    //         try:
21419    //             for i in range(n):
21420    //                 pass
21421    //             ˇ
21422    // "});
21423    // cx.update_editor(|editor, window, cx| {
21424    //     editor.handle_input("except:", window, cx);
21425    // });
21426    // cx.assert_editor_state(indoc! {"
21427    //     def main():
21428    //         try:
21429    //             for i in range(n):
21430    //                 pass
21431    //         except:ˇ
21432    // "});
21433
21434    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21435    // cx.set_state(indoc! {"
21436    //     def main():
21437    //         try:
21438    //             i = 2
21439    //         except:
21440    //             for i in range(n):
21441    //                 pass
21442    //             ˇ
21443    // "});
21444    // cx.update_editor(|editor, window, cx| {
21445    //     editor.handle_input("else:", window, cx);
21446    // });
21447    // cx.assert_editor_state(indoc! {"
21448    //     def main():
21449    //         try:
21450    //             i = 2
21451    //         except:
21452    //             for i in range(n):
21453    //                 pass
21454    //         else:ˇ
21455    // "});
21456
21457    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21458    // cx.set_state(indoc! {"
21459    //     def main():
21460    //         try:
21461    //             i = 2
21462    //         except:
21463    //             j = 2
21464    //         else:
21465    //             for i in range(n):
21466    //                 pass
21467    //             ˇ
21468    // "});
21469    // cx.update_editor(|editor, window, cx| {
21470    //     editor.handle_input("finally:", window, cx);
21471    // });
21472    // cx.assert_editor_state(indoc! {"
21473    //     def main():
21474    //         try:
21475    //             i = 2
21476    //         except:
21477    //             j = 2
21478    //         else:
21479    //             for i in range(n):
21480    //                 pass
21481    //         finally:ˇ
21482    // "});
21483
21484    // test `else` stays at correct indent when typed after `for` block
21485    cx.set_state(indoc! {"
21486        def main():
21487            for i in range(10):
21488                if i == 3:
21489                    break
21490            ˇ
21491    "});
21492    cx.update_editor(|editor, window, cx| {
21493        editor.handle_input("else:", window, cx);
21494    });
21495    cx.assert_editor_state(indoc! {"
21496        def main():
21497            for i in range(10):
21498                if i == 3:
21499                    break
21500            else:ˇ
21501    "});
21502
21503    // test does not outdent on typing after line with square brackets
21504    cx.set_state(indoc! {"
21505        def f() -> list[str]:
21506            ˇ
21507    "});
21508    cx.update_editor(|editor, window, cx| {
21509        editor.handle_input("a", window, cx);
21510    });
21511    cx.assert_editor_state(indoc! {"
21512        def f() -> list[str]:
2151321514    "});
21515}
21516
21517#[gpui::test]
21518async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21519    init_test(cx, |_| {});
21520    update_test_language_settings(cx, |settings| {
21521        settings.defaults.extend_comment_on_newline = Some(false);
21522    });
21523    let mut cx = EditorTestContext::new(cx).await;
21524    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21525    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21526
21527    // test correct indent after newline on comment
21528    cx.set_state(indoc! {"
21529        # COMMENT:ˇ
21530    "});
21531    cx.update_editor(|editor, window, cx| {
21532        editor.newline(&Newline, window, cx);
21533    });
21534    cx.assert_editor_state(indoc! {"
21535        # COMMENT:
21536        ˇ
21537    "});
21538
21539    // test correct indent after newline in brackets
21540    cx.set_state(indoc! {"
21541        {ˇ}
21542    "});
21543    cx.update_editor(|editor, window, cx| {
21544        editor.newline(&Newline, window, cx);
21545    });
21546    cx.run_until_parked();
21547    cx.assert_editor_state(indoc! {"
21548        {
21549            ˇ
21550        }
21551    "});
21552
21553    cx.set_state(indoc! {"
21554        (ˇ)
21555    "});
21556    cx.update_editor(|editor, window, cx| {
21557        editor.newline(&Newline, window, cx);
21558    });
21559    cx.run_until_parked();
21560    cx.assert_editor_state(indoc! {"
21561        (
21562            ˇ
21563        )
21564    "});
21565
21566    // do not indent after empty lists or dictionaries
21567    cx.set_state(indoc! {"
21568        a = []ˇ
21569    "});
21570    cx.update_editor(|editor, window, cx| {
21571        editor.newline(&Newline, window, cx);
21572    });
21573    cx.run_until_parked();
21574    cx.assert_editor_state(indoc! {"
21575        a = []
21576        ˇ
21577    "});
21578}
21579
21580fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21581    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21582    point..point
21583}
21584
21585#[track_caller]
21586fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21587    let (text, ranges) = marked_text_ranges(marked_text, true);
21588    assert_eq!(editor.text(cx), text);
21589    assert_eq!(
21590        editor.selections.ranges(cx),
21591        ranges,
21592        "Assert selections are {}",
21593        marked_text
21594    );
21595}
21596
21597pub fn handle_signature_help_request(
21598    cx: &mut EditorLspTestContext,
21599    mocked_response: lsp::SignatureHelp,
21600) -> impl Future<Output = ()> + use<> {
21601    let mut request =
21602        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21603            let mocked_response = mocked_response.clone();
21604            async move { Ok(Some(mocked_response)) }
21605        });
21606
21607    async move {
21608        request.next().await;
21609    }
21610}
21611
21612#[track_caller]
21613pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21614    cx.update_editor(|editor, _, _| {
21615        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21616            let entries = menu.entries.borrow();
21617            let entries = entries
21618                .iter()
21619                .map(|entry| entry.string.as_str())
21620                .collect::<Vec<_>>();
21621            assert_eq!(entries, expected);
21622        } else {
21623            panic!("Expected completions menu");
21624        }
21625    });
21626}
21627
21628/// Handle completion request passing a marked string specifying where the completion
21629/// should be triggered from using '|' character, what range should be replaced, and what completions
21630/// should be returned using '<' and '>' to delimit the range.
21631///
21632/// Also see `handle_completion_request_with_insert_and_replace`.
21633#[track_caller]
21634pub fn handle_completion_request(
21635    marked_string: &str,
21636    completions: Vec<&'static str>,
21637    is_incomplete: bool,
21638    counter: Arc<AtomicUsize>,
21639    cx: &mut EditorLspTestContext,
21640) -> impl Future<Output = ()> {
21641    let complete_from_marker: TextRangeMarker = '|'.into();
21642    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21643    let (_, mut marked_ranges) = marked_text_ranges_by(
21644        marked_string,
21645        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21646    );
21647
21648    let complete_from_position =
21649        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21650    let replace_range =
21651        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21652
21653    let mut request =
21654        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21655            let completions = completions.clone();
21656            counter.fetch_add(1, atomic::Ordering::Release);
21657            async move {
21658                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21659                assert_eq!(
21660                    params.text_document_position.position,
21661                    complete_from_position
21662                );
21663                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21664                    is_incomplete: is_incomplete,
21665                    item_defaults: None,
21666                    items: completions
21667                        .iter()
21668                        .map(|completion_text| lsp::CompletionItem {
21669                            label: completion_text.to_string(),
21670                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21671                                range: replace_range,
21672                                new_text: completion_text.to_string(),
21673                            })),
21674                            ..Default::default()
21675                        })
21676                        .collect(),
21677                })))
21678            }
21679        });
21680
21681    async move {
21682        request.next().await;
21683    }
21684}
21685
21686/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21687/// given instead, which also contains an `insert` range.
21688///
21689/// This function uses markers to define ranges:
21690/// - `|` marks the cursor position
21691/// - `<>` marks the replace range
21692/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21693pub fn handle_completion_request_with_insert_and_replace(
21694    cx: &mut EditorLspTestContext,
21695    marked_string: &str,
21696    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21697    counter: Arc<AtomicUsize>,
21698) -> impl Future<Output = ()> {
21699    let complete_from_marker: TextRangeMarker = '|'.into();
21700    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21701    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21702
21703    let (_, mut marked_ranges) = marked_text_ranges_by(
21704        marked_string,
21705        vec![
21706            complete_from_marker.clone(),
21707            replace_range_marker.clone(),
21708            insert_range_marker.clone(),
21709        ],
21710    );
21711
21712    let complete_from_position =
21713        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21714    let replace_range =
21715        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21716
21717    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21718        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21719        _ => lsp::Range {
21720            start: replace_range.start,
21721            end: complete_from_position,
21722        },
21723    };
21724
21725    let mut request =
21726        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21727            let completions = completions.clone();
21728            counter.fetch_add(1, atomic::Ordering::Release);
21729            async move {
21730                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21731                assert_eq!(
21732                    params.text_document_position.position, complete_from_position,
21733                    "marker `|` position doesn't match",
21734                );
21735                Ok(Some(lsp::CompletionResponse::Array(
21736                    completions
21737                        .iter()
21738                        .map(|(label, new_text)| lsp::CompletionItem {
21739                            label: label.to_string(),
21740                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21741                                lsp::InsertReplaceEdit {
21742                                    insert: insert_range,
21743                                    replace: replace_range,
21744                                    new_text: new_text.to_string(),
21745                                },
21746                            )),
21747                            ..Default::default()
21748                        })
21749                        .collect(),
21750                )))
21751            }
21752        });
21753
21754    async move {
21755        request.next().await;
21756    }
21757}
21758
21759fn handle_resolve_completion_request(
21760    cx: &mut EditorLspTestContext,
21761    edits: Option<Vec<(&'static str, &'static str)>>,
21762) -> impl Future<Output = ()> {
21763    let edits = edits.map(|edits| {
21764        edits
21765            .iter()
21766            .map(|(marked_string, new_text)| {
21767                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21768                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21769                lsp::TextEdit::new(replace_range, new_text.to_string())
21770            })
21771            .collect::<Vec<_>>()
21772    });
21773
21774    let mut request =
21775        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21776            let edits = edits.clone();
21777            async move {
21778                Ok(lsp::CompletionItem {
21779                    additional_text_edits: edits,
21780                    ..Default::default()
21781                })
21782            }
21783        });
21784
21785    async move {
21786        request.next().await;
21787    }
21788}
21789
21790pub(crate) fn update_test_language_settings(
21791    cx: &mut TestAppContext,
21792    f: impl Fn(&mut AllLanguageSettingsContent),
21793) {
21794    cx.update(|cx| {
21795        SettingsStore::update_global(cx, |store, cx| {
21796            store.update_user_settings::<AllLanguageSettings>(cx, f);
21797        });
21798    });
21799}
21800
21801pub(crate) fn update_test_project_settings(
21802    cx: &mut TestAppContext,
21803    f: impl Fn(&mut ProjectSettings),
21804) {
21805    cx.update(|cx| {
21806        SettingsStore::update_global(cx, |store, cx| {
21807            store.update_user_settings::<ProjectSettings>(cx, f);
21808        });
21809    });
21810}
21811
21812pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21813    cx.update(|cx| {
21814        assets::Assets.load_test_fonts(cx);
21815        let store = SettingsStore::test(cx);
21816        cx.set_global(store);
21817        theme::init(theme::LoadThemes::JustBase, cx);
21818        release_channel::init(SemanticVersion::default(), cx);
21819        client::init_settings(cx);
21820        language::init(cx);
21821        Project::init_settings(cx);
21822        workspace::init_settings(cx);
21823        crate::init(cx);
21824    });
21825
21826    update_test_language_settings(cx, f);
21827}
21828
21829#[track_caller]
21830fn assert_hunk_revert(
21831    not_reverted_text_with_selections: &str,
21832    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21833    expected_reverted_text_with_selections: &str,
21834    base_text: &str,
21835    cx: &mut EditorLspTestContext,
21836) {
21837    cx.set_state(not_reverted_text_with_selections);
21838    cx.set_head_text(base_text);
21839    cx.executor().run_until_parked();
21840
21841    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21842        let snapshot = editor.snapshot(window, cx);
21843        let reverted_hunk_statuses = snapshot
21844            .buffer_snapshot
21845            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21846            .map(|hunk| hunk.status().kind)
21847            .collect::<Vec<_>>();
21848
21849        editor.git_restore(&Default::default(), window, cx);
21850        reverted_hunk_statuses
21851    });
21852    cx.executor().run_until_parked();
21853    cx.assert_editor_state(expected_reverted_text_with_selections);
21854    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21855}
21856
21857#[gpui::test(iterations = 10)]
21858async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
21859    init_test(cx, |_| {});
21860
21861    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
21862    let counter = diagnostic_requests.clone();
21863
21864    let fs = FakeFs::new(cx.executor());
21865    fs.insert_tree(
21866        path!("/a"),
21867        json!({
21868            "first.rs": "fn main() { let a = 5; }",
21869            "second.rs": "// Test file",
21870        }),
21871    )
21872    .await;
21873
21874    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21876    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21877
21878    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21879    language_registry.add(rust_lang());
21880    let mut fake_servers = language_registry.register_fake_lsp(
21881        "Rust",
21882        FakeLspAdapter {
21883            capabilities: lsp::ServerCapabilities {
21884                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
21885                    lsp::DiagnosticOptions {
21886                        identifier: None,
21887                        inter_file_dependencies: true,
21888                        workspace_diagnostics: true,
21889                        work_done_progress_options: Default::default(),
21890                    },
21891                )),
21892                ..Default::default()
21893            },
21894            ..Default::default()
21895        },
21896    );
21897
21898    let editor = workspace
21899        .update(cx, |workspace, window, cx| {
21900            workspace.open_abs_path(
21901                PathBuf::from(path!("/a/first.rs")),
21902                OpenOptions::default(),
21903                window,
21904                cx,
21905            )
21906        })
21907        .unwrap()
21908        .await
21909        .unwrap()
21910        .downcast::<Editor>()
21911        .unwrap();
21912    let fake_server = fake_servers.next().await.unwrap();
21913    let mut first_request = fake_server
21914        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
21915            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
21916            let result_id = Some(new_result_id.to_string());
21917            assert_eq!(
21918                params.text_document.uri,
21919                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
21920            );
21921            async move {
21922                Ok(lsp::DocumentDiagnosticReportResult::Report(
21923                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
21924                        related_documents: None,
21925                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
21926                            items: Vec::new(),
21927                            result_id,
21928                        },
21929                    }),
21930                ))
21931            }
21932        });
21933
21934    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
21935        project.update(cx, |project, cx| {
21936            let buffer_id = editor
21937                .read(cx)
21938                .buffer()
21939                .read(cx)
21940                .as_singleton()
21941                .expect("created a singleton buffer")
21942                .read(cx)
21943                .remote_id();
21944            let buffer_result_id = project.lsp_store().read(cx).result_id(buffer_id, cx);
21945            assert_eq!(expected, buffer_result_id);
21946        });
21947    };
21948
21949    ensure_result_id(None, cx);
21950    cx.executor().advance_clock(Duration::from_millis(60));
21951    cx.executor().run_until_parked();
21952    assert_eq!(
21953        diagnostic_requests.load(atomic::Ordering::Acquire),
21954        1,
21955        "Opening file should trigger diagnostic request"
21956    );
21957    first_request
21958        .next()
21959        .await
21960        .expect("should have sent the first diagnostics pull request");
21961    ensure_result_id(Some("1".to_string()), cx);
21962
21963    // Editing should trigger diagnostics
21964    editor.update_in(cx, |editor, window, cx| {
21965        editor.handle_input("2", window, cx)
21966    });
21967    cx.executor().advance_clock(Duration::from_millis(60));
21968    cx.executor().run_until_parked();
21969    assert_eq!(
21970        diagnostic_requests.load(atomic::Ordering::Acquire),
21971        2,
21972        "Editing should trigger diagnostic request"
21973    );
21974    ensure_result_id(Some("2".to_string()), cx);
21975
21976    // Moving cursor should not trigger diagnostic request
21977    editor.update_in(cx, |editor, window, cx| {
21978        editor.change_selections(None, window, cx, |s| {
21979            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
21980        });
21981    });
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        2,
21987        "Cursor movement should not trigger diagnostic request"
21988    );
21989    ensure_result_id(Some("2".to_string()), cx);
21990    // Multiple rapid edits should be debounced
21991    for _ in 0..5 {
21992        editor.update_in(cx, |editor, window, cx| {
21993            editor.handle_input("x", window, cx)
21994        });
21995    }
21996    cx.executor().advance_clock(Duration::from_millis(60));
21997    cx.executor().run_until_parked();
21998
21999    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22000    assert!(
22001        final_requests <= 4,
22002        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22003    );
22004    ensure_result_id(Some(final_requests.to_string()), cx);
22005}
22006
22007#[gpui::test]
22008async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22009    // Regression test for issue #11671
22010    // Previously, adding a cursor after moving multiple cursors would reset
22011    // the cursor count instead of adding to the existing cursors.
22012    init_test(cx, |_| {});
22013    let mut cx = EditorTestContext::new(cx).await;
22014
22015    // Create a simple buffer with cursor at start
22016    cx.set_state(indoc! {"
22017        ˇaaaa
22018        bbbb
22019        cccc
22020        dddd
22021        eeee
22022        ffff
22023        gggg
22024        hhhh"});
22025
22026    // Add 2 cursors below (so we have 3 total)
22027    cx.update_editor(|editor, window, cx| {
22028        editor.add_selection_below(&Default::default(), window, cx);
22029        editor.add_selection_below(&Default::default(), window, cx);
22030    });
22031
22032    // Verify we have 3 cursors
22033    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22034    assert_eq!(
22035        initial_count, 3,
22036        "Should have 3 cursors after adding 2 below"
22037    );
22038
22039    // Move down one line
22040    cx.update_editor(|editor, window, cx| {
22041        editor.move_down(&MoveDown, window, cx);
22042    });
22043
22044    // Add another cursor below
22045    cx.update_editor(|editor, window, cx| {
22046        editor.add_selection_below(&Default::default(), window, cx);
22047    });
22048
22049    // Should now have 4 cursors (3 original + 1 new)
22050    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22051    assert_eq!(
22052        final_count, 4,
22053        "Should have 4 cursors after moving and adding another"
22054    );
22055}