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
 1911        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1912        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1913
 1914        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1915        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1916
 1917        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1918        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1919
 1920        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1921        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1922
 1923        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1924        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1925
 1926        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1927        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1928
 1929        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1930        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1931
 1932        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1933        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1934
 1935        editor.move_right(&MoveRight, window, cx);
 1936        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1937        assert_selection_ranges(
 1938            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1939            editor,
 1940            cx,
 1941        );
 1942
 1943        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1944        assert_selection_ranges(
 1945            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1946            editor,
 1947            cx,
 1948        );
 1949
 1950        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1951        assert_selection_ranges(
 1952            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1953            editor,
 1954            cx,
 1955        );
 1956    });
 1957}
 1958
 1959#[gpui::test]
 1960fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1961    init_test(cx, |_| {});
 1962
 1963    let editor = cx.add_window(|window, cx| {
 1964        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1965        build_editor(buffer, window, cx)
 1966    });
 1967
 1968    _ = editor.update(cx, |editor, window, cx| {
 1969        editor.set_wrap_width(Some(140.0.into()), cx);
 1970        assert_eq!(
 1971            editor.display_text(cx),
 1972            "use one::{\n    two::three::\n    four::five\n};"
 1973        );
 1974
 1975        editor.change_selections(None, window, cx, |s| {
 1976            s.select_display_ranges([
 1977                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1978            ]);
 1979        });
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_eq!(
 1983            editor.selections.display_ranges(cx),
 1984            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1985        );
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_eq!(
 1989            editor.selections.display_ranges(cx),
 1990            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1991        );
 1992
 1993        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1994        assert_eq!(
 1995            editor.selections.display_ranges(cx),
 1996            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1997        );
 1998
 1999        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2000        assert_eq!(
 2001            editor.selections.display_ranges(cx),
 2002            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2003        );
 2004
 2005        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2006        assert_eq!(
 2007            editor.selections.display_ranges(cx),
 2008            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2009        );
 2010
 2011        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2012        assert_eq!(
 2013            editor.selections.display_ranges(cx),
 2014            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2015        );
 2016    });
 2017}
 2018
 2019#[gpui::test]
 2020async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2021    init_test(cx, |_| {});
 2022    let mut cx = EditorTestContext::new(cx).await;
 2023
 2024    let line_height = cx.editor(|editor, window, _| {
 2025        editor
 2026            .style()
 2027            .unwrap()
 2028            .text
 2029            .line_height_in_pixels(window.rem_size())
 2030    });
 2031    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2032
 2033    cx.set_state(
 2034        &r#"ˇone
 2035        two
 2036
 2037        three
 2038        fourˇ
 2039        five
 2040
 2041        six"#
 2042            .unindent(),
 2043    );
 2044
 2045    cx.update_editor(|editor, window, cx| {
 2046        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2047    });
 2048    cx.assert_editor_state(
 2049        &r#"one
 2050        two
 2051        ˇ
 2052        three
 2053        four
 2054        five
 2055        ˇ
 2056        six"#
 2057            .unindent(),
 2058    );
 2059
 2060    cx.update_editor(|editor, window, cx| {
 2061        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2062    });
 2063    cx.assert_editor_state(
 2064        &r#"one
 2065        two
 2066
 2067        three
 2068        four
 2069        five
 2070        ˇ
 2071        sixˇ"#
 2072            .unindent(),
 2073    );
 2074
 2075    cx.update_editor(|editor, window, cx| {
 2076        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2077    });
 2078    cx.assert_editor_state(
 2079        &r#"one
 2080        two
 2081
 2082        three
 2083        four
 2084        five
 2085
 2086        sixˇ"#
 2087            .unindent(),
 2088    );
 2089
 2090    cx.update_editor(|editor, window, cx| {
 2091        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2092    });
 2093    cx.assert_editor_state(
 2094        &r#"one
 2095        two
 2096
 2097        three
 2098        four
 2099        five
 2100        ˇ
 2101        six"#
 2102            .unindent(),
 2103    );
 2104
 2105    cx.update_editor(|editor, window, cx| {
 2106        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2107    });
 2108    cx.assert_editor_state(
 2109        &r#"one
 2110        two
 2111        ˇ
 2112        three
 2113        four
 2114        five
 2115
 2116        six"#
 2117            .unindent(),
 2118    );
 2119
 2120    cx.update_editor(|editor, window, cx| {
 2121        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2122    });
 2123    cx.assert_editor_state(
 2124        &r#"ˇone
 2125        two
 2126
 2127        three
 2128        four
 2129        five
 2130
 2131        six"#
 2132            .unindent(),
 2133    );
 2134}
 2135
 2136#[gpui::test]
 2137async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2138    init_test(cx, |_| {});
 2139    let mut cx = EditorTestContext::new(cx).await;
 2140    let line_height = cx.editor(|editor, window, _| {
 2141        editor
 2142            .style()
 2143            .unwrap()
 2144            .text
 2145            .line_height_in_pixels(window.rem_size())
 2146    });
 2147    let window = cx.window;
 2148    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2149
 2150    cx.set_state(
 2151        r#"ˇone
 2152        two
 2153        three
 2154        four
 2155        five
 2156        six
 2157        seven
 2158        eight
 2159        nine
 2160        ten
 2161        "#,
 2162    );
 2163
 2164    cx.update_editor(|editor, window, cx| {
 2165        assert_eq!(
 2166            editor.snapshot(window, cx).scroll_position(),
 2167            gpui::Point::new(0., 0.)
 2168        );
 2169        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2170        assert_eq!(
 2171            editor.snapshot(window, cx).scroll_position(),
 2172            gpui::Point::new(0., 3.)
 2173        );
 2174        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2175        assert_eq!(
 2176            editor.snapshot(window, cx).scroll_position(),
 2177            gpui::Point::new(0., 6.)
 2178        );
 2179        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2180        assert_eq!(
 2181            editor.snapshot(window, cx).scroll_position(),
 2182            gpui::Point::new(0., 3.)
 2183        );
 2184
 2185        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2186        assert_eq!(
 2187            editor.snapshot(window, cx).scroll_position(),
 2188            gpui::Point::new(0., 1.)
 2189        );
 2190        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2191        assert_eq!(
 2192            editor.snapshot(window, cx).scroll_position(),
 2193            gpui::Point::new(0., 3.)
 2194        );
 2195    });
 2196}
 2197
 2198#[gpui::test]
 2199async fn test_autoscroll(cx: &mut TestAppContext) {
 2200    init_test(cx, |_| {});
 2201    let mut cx = EditorTestContext::new(cx).await;
 2202
 2203    let line_height = cx.update_editor(|editor, window, cx| {
 2204        editor.set_vertical_scroll_margin(2, cx);
 2205        editor
 2206            .style()
 2207            .unwrap()
 2208            .text
 2209            .line_height_in_pixels(window.rem_size())
 2210    });
 2211    let window = cx.window;
 2212    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2213
 2214    cx.set_state(
 2215        r#"ˇone
 2216            two
 2217            three
 2218            four
 2219            five
 2220            six
 2221            seven
 2222            eight
 2223            nine
 2224            ten
 2225        "#,
 2226    );
 2227    cx.update_editor(|editor, window, cx| {
 2228        assert_eq!(
 2229            editor.snapshot(window, cx).scroll_position(),
 2230            gpui::Point::new(0., 0.0)
 2231        );
 2232    });
 2233
 2234    // Add a cursor below the visible area. Since both cursors cannot fit
 2235    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2236    // allows the vertical scroll margin below that cursor.
 2237    cx.update_editor(|editor, window, cx| {
 2238        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2239            selections.select_ranges([
 2240                Point::new(0, 0)..Point::new(0, 0),
 2241                Point::new(6, 0)..Point::new(6, 0),
 2242            ]);
 2243        })
 2244    });
 2245    cx.update_editor(|editor, window, cx| {
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.0)
 2249        );
 2250    });
 2251
 2252    // Move down. The editor cursor scrolls down to track the newest cursor.
 2253    cx.update_editor(|editor, window, cx| {
 2254        editor.move_down(&Default::default(), window, cx);
 2255    });
 2256    cx.update_editor(|editor, window, cx| {
 2257        assert_eq!(
 2258            editor.snapshot(window, cx).scroll_position(),
 2259            gpui::Point::new(0., 4.0)
 2260        );
 2261    });
 2262
 2263    // Add a cursor above the visible area. Since both cursors fit on screen,
 2264    // the editor scrolls to show both.
 2265    cx.update_editor(|editor, window, cx| {
 2266        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2267            selections.select_ranges([
 2268                Point::new(1, 0)..Point::new(1, 0),
 2269                Point::new(6, 0)..Point::new(6, 0),
 2270            ]);
 2271        })
 2272    });
 2273    cx.update_editor(|editor, window, cx| {
 2274        assert_eq!(
 2275            editor.snapshot(window, cx).scroll_position(),
 2276            gpui::Point::new(0., 1.0)
 2277        );
 2278    });
 2279}
 2280
 2281#[gpui::test]
 2282async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2283    init_test(cx, |_| {});
 2284    let mut cx = EditorTestContext::new(cx).await;
 2285
 2286    let line_height = cx.editor(|editor, window, _cx| {
 2287        editor
 2288            .style()
 2289            .unwrap()
 2290            .text
 2291            .line_height_in_pixels(window.rem_size())
 2292    });
 2293    let window = cx.window;
 2294    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2295    cx.set_state(
 2296        &r#"
 2297        ˇone
 2298        two
 2299        threeˇ
 2300        four
 2301        five
 2302        six
 2303        seven
 2304        eight
 2305        nine
 2306        ten
 2307        "#
 2308        .unindent(),
 2309    );
 2310
 2311    cx.update_editor(|editor, window, cx| {
 2312        editor.move_page_down(&MovePageDown::default(), window, cx)
 2313    });
 2314    cx.assert_editor_state(
 2315        &r#"
 2316        one
 2317        two
 2318        three
 2319        ˇfour
 2320        five
 2321        sixˇ
 2322        seven
 2323        eight
 2324        nine
 2325        ten
 2326        "#
 2327        .unindent(),
 2328    );
 2329
 2330    cx.update_editor(|editor, window, cx| {
 2331        editor.move_page_down(&MovePageDown::default(), window, cx)
 2332    });
 2333    cx.assert_editor_state(
 2334        &r#"
 2335        one
 2336        two
 2337        three
 2338        four
 2339        five
 2340        six
 2341        ˇseven
 2342        eight
 2343        nineˇ
 2344        ten
 2345        "#
 2346        .unindent(),
 2347    );
 2348
 2349    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2350    cx.assert_editor_state(
 2351        &r#"
 2352        one
 2353        two
 2354        three
 2355        ˇfour
 2356        five
 2357        sixˇ
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2367    cx.assert_editor_state(
 2368        &r#"
 2369        ˇone
 2370        two
 2371        threeˇ
 2372        four
 2373        five
 2374        six
 2375        seven
 2376        eight
 2377        nine
 2378        ten
 2379        "#
 2380        .unindent(),
 2381    );
 2382
 2383    // Test select collapsing
 2384    cx.update_editor(|editor, window, cx| {
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386        editor.move_page_down(&MovePageDown::default(), window, cx);
 2387        editor.move_page_down(&MovePageDown::default(), window, cx);
 2388    });
 2389    cx.assert_editor_state(
 2390        &r#"
 2391        one
 2392        two
 2393        three
 2394        four
 2395        five
 2396        six
 2397        seven
 2398        eight
 2399        nine
 2400        ˇten
 2401        ˇ"#
 2402        .unindent(),
 2403    );
 2404}
 2405
 2406#[gpui::test]
 2407async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2408    init_test(cx, |_| {});
 2409    let mut cx = EditorTestContext::new(cx).await;
 2410    cx.set_state("one «two threeˇ» four");
 2411    cx.update_editor(|editor, window, cx| {
 2412        editor.delete_to_beginning_of_line(
 2413            &DeleteToBeginningOfLine {
 2414                stop_at_indent: false,
 2415            },
 2416            window,
 2417            cx,
 2418        );
 2419        assert_eq!(editor.text(cx), " four");
 2420    });
 2421}
 2422
 2423#[gpui::test]
 2424fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2425    init_test(cx, |_| {});
 2426
 2427    let editor = cx.add_window(|window, cx| {
 2428        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2429        build_editor(buffer.clone(), window, cx)
 2430    });
 2431
 2432    _ = editor.update(cx, |editor, window, cx| {
 2433        editor.change_selections(None, window, cx, |s| {
 2434            s.select_display_ranges([
 2435                // an empty selection - the preceding word fragment is deleted
 2436                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2437                // characters selected - they are deleted
 2438                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2439            ])
 2440        });
 2441        editor.delete_to_previous_word_start(
 2442            &DeleteToPreviousWordStart {
 2443                ignore_newlines: false,
 2444            },
 2445            window,
 2446            cx,
 2447        );
 2448        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2449    });
 2450
 2451    _ = editor.update(cx, |editor, window, cx| {
 2452        editor.change_selections(None, window, cx, |s| {
 2453            s.select_display_ranges([
 2454                // an empty selection - the following word fragment is deleted
 2455                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2456                // characters selected - they are deleted
 2457                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2458            ])
 2459        });
 2460        editor.delete_to_next_word_end(
 2461            &DeleteToNextWordEnd {
 2462                ignore_newlines: false,
 2463            },
 2464            window,
 2465            cx,
 2466        );
 2467        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2468    });
 2469}
 2470
 2471#[gpui::test]
 2472fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2473    init_test(cx, |_| {});
 2474
 2475    let editor = cx.add_window(|window, cx| {
 2476        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2477        build_editor(buffer.clone(), window, cx)
 2478    });
 2479    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2480        ignore_newlines: false,
 2481    };
 2482    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2483        ignore_newlines: true,
 2484    };
 2485
 2486    _ = editor.update(cx, |editor, window, cx| {
 2487        editor.change_selections(None, window, cx, |s| {
 2488            s.select_display_ranges([
 2489                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2490            ])
 2491        });
 2492        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2493        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2494        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2495        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2496        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2497        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2498        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2499        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2504    });
 2505}
 2506
 2507#[gpui::test]
 2508fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2509    init_test(cx, |_| {});
 2510
 2511    let editor = cx.add_window(|window, cx| {
 2512        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2513        build_editor(buffer.clone(), window, cx)
 2514    });
 2515    let del_to_next_word_end = DeleteToNextWordEnd {
 2516        ignore_newlines: false,
 2517    };
 2518    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2519        ignore_newlines: true,
 2520    };
 2521
 2522    _ = editor.update(cx, |editor, window, cx| {
 2523        editor.change_selections(None, window, cx, |s| {
 2524            s.select_display_ranges([
 2525                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2526            ])
 2527        });
 2528        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2529        assert_eq!(
 2530            editor.buffer.read(cx).read(cx).text(),
 2531            "one\n   two\nthree\n   four"
 2532        );
 2533        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2534        assert_eq!(
 2535            editor.buffer.read(cx).read(cx).text(),
 2536            "\n   two\nthree\n   four"
 2537        );
 2538        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2539        assert_eq!(
 2540            editor.buffer.read(cx).read(cx).text(),
 2541            "two\nthree\n   four"
 2542        );
 2543        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2544        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2545        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2547        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2549    });
 2550}
 2551
 2552#[gpui::test]
 2553fn test_newline(cx: &mut TestAppContext) {
 2554    init_test(cx, |_| {});
 2555
 2556    let editor = cx.add_window(|window, cx| {
 2557        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2558        build_editor(buffer.clone(), window, cx)
 2559    });
 2560
 2561    _ = editor.update(cx, |editor, window, cx| {
 2562        editor.change_selections(None, window, cx, |s| {
 2563            s.select_display_ranges([
 2564                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2565                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2566                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2567            ])
 2568        });
 2569
 2570        editor.newline(&Newline, window, cx);
 2571        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2572    });
 2573}
 2574
 2575#[gpui::test]
 2576fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2577    init_test(cx, |_| {});
 2578
 2579    let editor = cx.add_window(|window, cx| {
 2580        let buffer = MultiBuffer::build_simple(
 2581            "
 2582                a
 2583                b(
 2584                    X
 2585                )
 2586                c(
 2587                    X
 2588                )
 2589            "
 2590            .unindent()
 2591            .as_str(),
 2592            cx,
 2593        );
 2594        let mut editor = build_editor(buffer.clone(), window, cx);
 2595        editor.change_selections(None, window, cx, |s| {
 2596            s.select_ranges([
 2597                Point::new(2, 4)..Point::new(2, 5),
 2598                Point::new(5, 4)..Point::new(5, 5),
 2599            ])
 2600        });
 2601        editor
 2602    });
 2603
 2604    _ = editor.update(cx, |editor, window, cx| {
 2605        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2606        editor.buffer.update(cx, |buffer, cx| {
 2607            buffer.edit(
 2608                [
 2609                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2610                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2611                ],
 2612                None,
 2613                cx,
 2614            );
 2615            assert_eq!(
 2616                buffer.read(cx).text(),
 2617                "
 2618                    a
 2619                    b()
 2620                    c()
 2621                "
 2622                .unindent()
 2623            );
 2624        });
 2625        assert_eq!(
 2626            editor.selections.ranges(cx),
 2627            &[
 2628                Point::new(1, 2)..Point::new(1, 2),
 2629                Point::new(2, 2)..Point::new(2, 2),
 2630            ],
 2631        );
 2632
 2633        editor.newline(&Newline, window, cx);
 2634        assert_eq!(
 2635            editor.text(cx),
 2636            "
 2637                a
 2638                b(
 2639                )
 2640                c(
 2641                )
 2642            "
 2643            .unindent()
 2644        );
 2645
 2646        // The selections are moved after the inserted newlines
 2647        assert_eq!(
 2648            editor.selections.ranges(cx),
 2649            &[
 2650                Point::new(2, 0)..Point::new(2, 0),
 2651                Point::new(4, 0)..Point::new(4, 0),
 2652            ],
 2653        );
 2654    });
 2655}
 2656
 2657#[gpui::test]
 2658async fn test_newline_above(cx: &mut TestAppContext) {
 2659    init_test(cx, |settings| {
 2660        settings.defaults.tab_size = NonZeroU32::new(4)
 2661    });
 2662
 2663    let language = Arc::new(
 2664        Language::new(
 2665            LanguageConfig::default(),
 2666            Some(tree_sitter_rust::LANGUAGE.into()),
 2667        )
 2668        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2669        .unwrap(),
 2670    );
 2671
 2672    let mut cx = EditorTestContext::new(cx).await;
 2673    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2674    cx.set_state(indoc! {"
 2675        const a: ˇA = (
 2676 2677                «const_functionˇ»(ˇ),
 2678                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2679 2680        ˇ);ˇ
 2681    "});
 2682
 2683    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2684    cx.assert_editor_state(indoc! {"
 2685        ˇ
 2686        const a: A = (
 2687            ˇ
 2688            (
 2689                ˇ
 2690                ˇ
 2691                const_function(),
 2692                ˇ
 2693                ˇ
 2694                ˇ
 2695                ˇ
 2696                something_else,
 2697                ˇ
 2698            )
 2699            ˇ
 2700            ˇ
 2701        );
 2702    "});
 2703}
 2704
 2705#[gpui::test]
 2706async fn test_newline_below(cx: &mut TestAppContext) {
 2707    init_test(cx, |settings| {
 2708        settings.defaults.tab_size = NonZeroU32::new(4)
 2709    });
 2710
 2711    let language = Arc::new(
 2712        Language::new(
 2713            LanguageConfig::default(),
 2714            Some(tree_sitter_rust::LANGUAGE.into()),
 2715        )
 2716        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2717        .unwrap(),
 2718    );
 2719
 2720    let mut cx = EditorTestContext::new(cx).await;
 2721    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2722    cx.set_state(indoc! {"
 2723        const a: ˇA = (
 2724 2725                «const_functionˇ»(ˇ),
 2726                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2727 2728        ˇ);ˇ
 2729    "});
 2730
 2731    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2732    cx.assert_editor_state(indoc! {"
 2733        const a: A = (
 2734            ˇ
 2735            (
 2736                ˇ
 2737                const_function(),
 2738                ˇ
 2739                ˇ
 2740                something_else,
 2741                ˇ
 2742                ˇ
 2743                ˇ
 2744                ˇ
 2745            )
 2746            ˇ
 2747        );
 2748        ˇ
 2749        ˇ
 2750    "});
 2751}
 2752
 2753#[gpui::test]
 2754async fn test_newline_comments(cx: &mut TestAppContext) {
 2755    init_test(cx, |settings| {
 2756        settings.defaults.tab_size = NonZeroU32::new(4)
 2757    });
 2758
 2759    let language = Arc::new(Language::new(
 2760        LanguageConfig {
 2761            line_comments: vec!["// ".into()],
 2762            ..LanguageConfig::default()
 2763        },
 2764        None,
 2765    ));
 2766    {
 2767        let mut cx = EditorTestContext::new(cx).await;
 2768        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2769        cx.set_state(indoc! {"
 2770        // Fooˇ
 2771    "});
 2772
 2773        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2774        cx.assert_editor_state(indoc! {"
 2775        // Foo
 2776        // ˇ
 2777    "});
 2778        // Ensure that we add comment prefix when existing line contains space
 2779        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2780        cx.assert_editor_state(
 2781            indoc! {"
 2782        // Foo
 2783        //s
 2784        // ˇ
 2785    "}
 2786            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2787            .as_str(),
 2788        );
 2789        // Ensure that we add comment prefix when existing line does not contain space
 2790        cx.set_state(indoc! {"
 2791        // Foo
 2792        //ˇ
 2793    "});
 2794        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2795        cx.assert_editor_state(indoc! {"
 2796        // Foo
 2797        //
 2798        // ˇ
 2799    "});
 2800        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2801        cx.set_state(indoc! {"
 2802        ˇ// Foo
 2803    "});
 2804        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2805        cx.assert_editor_state(indoc! {"
 2806
 2807        ˇ// Foo
 2808    "});
 2809    }
 2810    // Ensure that comment continuations can be disabled.
 2811    update_test_language_settings(cx, |settings| {
 2812        settings.defaults.extend_comment_on_newline = Some(false);
 2813    });
 2814    let mut cx = EditorTestContext::new(cx).await;
 2815    cx.set_state(indoc! {"
 2816        // Fooˇ
 2817    "});
 2818    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2819    cx.assert_editor_state(indoc! {"
 2820        // Foo
 2821        ˇ
 2822    "});
 2823}
 2824
 2825#[gpui::test]
 2826async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2827    init_test(cx, |settings| {
 2828        settings.defaults.tab_size = NonZeroU32::new(4)
 2829    });
 2830
 2831    let language = Arc::new(Language::new(
 2832        LanguageConfig {
 2833            line_comments: vec!["// ".into(), "/// ".into()],
 2834            ..LanguageConfig::default()
 2835        },
 2836        None,
 2837    ));
 2838    {
 2839        let mut cx = EditorTestContext::new(cx).await;
 2840        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2841        cx.set_state(indoc! {"
 2842        //ˇ
 2843    "});
 2844        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2845        cx.assert_editor_state(indoc! {"
 2846        //
 2847        // ˇ
 2848    "});
 2849
 2850        cx.set_state(indoc! {"
 2851        ///ˇ
 2852    "});
 2853        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2854        cx.assert_editor_state(indoc! {"
 2855        ///
 2856        /// ˇ
 2857    "});
 2858    }
 2859}
 2860
 2861#[gpui::test]
 2862async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2863    init_test(cx, |settings| {
 2864        settings.defaults.tab_size = NonZeroU32::new(4)
 2865    });
 2866
 2867    let language = Arc::new(
 2868        Language::new(
 2869            LanguageConfig {
 2870                documentation: Some(language::DocumentationConfig {
 2871                    start: "/**".into(),
 2872                    end: "*/".into(),
 2873                    prefix: "* ".into(),
 2874                    tab_size: NonZeroU32::new(1).unwrap(),
 2875                }),
 2876
 2877                ..LanguageConfig::default()
 2878            },
 2879            Some(tree_sitter_rust::LANGUAGE.into()),
 2880        )
 2881        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2882        .unwrap(),
 2883    );
 2884
 2885    {
 2886        let mut cx = EditorTestContext::new(cx).await;
 2887        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2888        cx.set_state(indoc! {"
 2889        /**ˇ
 2890    "});
 2891
 2892        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2893        cx.assert_editor_state(indoc! {"
 2894        /**
 2895         * ˇ
 2896    "});
 2897        // Ensure that if cursor is before the comment start,
 2898        // we do not actually insert a comment prefix.
 2899        cx.set_state(indoc! {"
 2900        ˇ/**
 2901    "});
 2902        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2903        cx.assert_editor_state(indoc! {"
 2904
 2905        ˇ/**
 2906    "});
 2907        // Ensure that if cursor is between it doesn't add comment prefix.
 2908        cx.set_state(indoc! {"
 2909        /*ˇ*
 2910    "});
 2911        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2912        cx.assert_editor_state(indoc! {"
 2913        /*
 2914        ˇ*
 2915    "});
 2916        // Ensure that if suffix exists on same line after cursor it adds new line.
 2917        cx.set_state(indoc! {"
 2918        /**ˇ*/
 2919    "});
 2920        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2921        cx.assert_editor_state(indoc! {"
 2922        /**
 2923         * ˇ
 2924         */
 2925    "});
 2926        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2927        cx.set_state(indoc! {"
 2928        /**ˇ */
 2929    "});
 2930        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2931        cx.assert_editor_state(indoc! {"
 2932        /**
 2933         * ˇ
 2934         */
 2935    "});
 2936        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2937        cx.set_state(indoc! {"
 2938        /** ˇ*/
 2939    "});
 2940        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2941        cx.assert_editor_state(
 2942            indoc! {"
 2943        /**s
 2944         * ˇ
 2945         */
 2946    "}
 2947            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2948            .as_str(),
 2949        );
 2950        // Ensure that delimiter space is preserved when newline on already
 2951        // spaced delimiter.
 2952        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2953        cx.assert_editor_state(
 2954            indoc! {"
 2955        /**s
 2956         *s
 2957         * ˇ
 2958         */
 2959    "}
 2960            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2961            .as_str(),
 2962        );
 2963        // Ensure that delimiter space is preserved when space is not
 2964        // on existing delimiter.
 2965        cx.set_state(indoc! {"
 2966        /**
 2967 2968         */
 2969    "});
 2970        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2971        cx.assert_editor_state(indoc! {"
 2972        /**
 2973         *
 2974         * ˇ
 2975         */
 2976    "});
 2977        // Ensure that if suffix exists on same line after cursor it
 2978        // doesn't add extra new line if prefix is not on same line.
 2979        cx.set_state(indoc! {"
 2980        /**
 2981        ˇ*/
 2982    "});
 2983        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2984        cx.assert_editor_state(indoc! {"
 2985        /**
 2986
 2987        ˇ*/
 2988    "});
 2989        // Ensure that it detects suffix after existing prefix.
 2990        cx.set_state(indoc! {"
 2991        /**ˇ/
 2992    "});
 2993        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2994        cx.assert_editor_state(indoc! {"
 2995        /**
 2996        ˇ/
 2997    "});
 2998        // Ensure that if suffix exists on same line before
 2999        // cursor it does not add comment prefix.
 3000        cx.set_state(indoc! {"
 3001        /** */ˇ
 3002    "});
 3003        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3004        cx.assert_editor_state(indoc! {"
 3005        /** */
 3006        ˇ
 3007    "});
 3008        // Ensure that if suffix exists on same line before
 3009        // cursor it does not add comment prefix.
 3010        cx.set_state(indoc! {"
 3011        /**
 3012         *
 3013         */ˇ
 3014    "});
 3015        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3016        cx.assert_editor_state(indoc! {"
 3017        /**
 3018         *
 3019         */
 3020         ˇ
 3021    "});
 3022
 3023        // Ensure that inline comment followed by code
 3024        // doesn't add comment prefix on newline
 3025        cx.set_state(indoc! {"
 3026        /** */ textˇ
 3027    "});
 3028        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3029        cx.assert_editor_state(indoc! {"
 3030        /** */ text
 3031        ˇ
 3032    "});
 3033
 3034        // Ensure that text after comment end tag
 3035        // doesn't add comment prefix on newline
 3036        cx.set_state(indoc! {"
 3037        /**
 3038         *
 3039         */ˇtext
 3040    "});
 3041        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3042        cx.assert_editor_state(indoc! {"
 3043        /**
 3044         *
 3045         */
 3046         ˇtext
 3047    "});
 3048
 3049        // Ensure if not comment block it doesn't
 3050        // add comment prefix on newline
 3051        cx.set_state(indoc! {"
 3052        * textˇ
 3053    "});
 3054        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3055        cx.assert_editor_state(indoc! {"
 3056        * text
 3057        ˇ
 3058    "});
 3059    }
 3060    // Ensure that comment continuations can be disabled.
 3061    update_test_language_settings(cx, |settings| {
 3062        settings.defaults.extend_comment_on_newline = Some(false);
 3063    });
 3064    let mut cx = EditorTestContext::new(cx).await;
 3065    cx.set_state(indoc! {"
 3066        /**ˇ
 3067    "});
 3068    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3069    cx.assert_editor_state(indoc! {"
 3070        /**
 3071        ˇ
 3072    "});
 3073}
 3074
 3075#[gpui::test]
 3076fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3077    init_test(cx, |_| {});
 3078
 3079    let editor = cx.add_window(|window, cx| {
 3080        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3081        let mut editor = build_editor(buffer.clone(), window, cx);
 3082        editor.change_selections(None, window, cx, |s| {
 3083            s.select_ranges([3..4, 11..12, 19..20])
 3084        });
 3085        editor
 3086    });
 3087
 3088    _ = editor.update(cx, |editor, window, cx| {
 3089        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3090        editor.buffer.update(cx, |buffer, cx| {
 3091            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3092            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3093        });
 3094        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3095
 3096        editor.insert("Z", window, cx);
 3097        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3098
 3099        // The selections are moved after the inserted characters
 3100        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3101    });
 3102}
 3103
 3104#[gpui::test]
 3105async fn test_tab(cx: &mut TestAppContext) {
 3106    init_test(cx, |settings| {
 3107        settings.defaults.tab_size = NonZeroU32::new(3)
 3108    });
 3109
 3110    let mut cx = EditorTestContext::new(cx).await;
 3111    cx.set_state(indoc! {"
 3112        ˇabˇc
 3113        ˇ🏀ˇ🏀ˇefg
 3114 3115    "});
 3116    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3117    cx.assert_editor_state(indoc! {"
 3118           ˇab ˇc
 3119           ˇ🏀  ˇ🏀  ˇefg
 3120        d  ˇ
 3121    "});
 3122
 3123    cx.set_state(indoc! {"
 3124        a
 3125        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3126    "});
 3127    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3128    cx.assert_editor_state(indoc! {"
 3129        a
 3130           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3131    "});
 3132}
 3133
 3134#[gpui::test]
 3135async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3136    init_test(cx, |_| {});
 3137
 3138    let mut cx = EditorTestContext::new(cx).await;
 3139    let language = Arc::new(
 3140        Language::new(
 3141            LanguageConfig::default(),
 3142            Some(tree_sitter_rust::LANGUAGE.into()),
 3143        )
 3144        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3145        .unwrap(),
 3146    );
 3147    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3148
 3149    // test when all cursors are not at suggested indent
 3150    // then simply move to their suggested indent location
 3151    cx.set_state(indoc! {"
 3152        const a: B = (
 3153            c(
 3154        ˇ
 3155        ˇ    )
 3156        );
 3157    "});
 3158    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3159    cx.assert_editor_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162                ˇ
 3163            ˇ)
 3164        );
 3165    "});
 3166
 3167    // test cursor already at suggested indent not moving when
 3168    // other cursors are yet to reach their suggested indents
 3169    cx.set_state(indoc! {"
 3170        ˇ
 3171        const a: B = (
 3172            c(
 3173                d(
 3174        ˇ
 3175                )
 3176        ˇ
 3177        ˇ    )
 3178        );
 3179    "});
 3180    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182        ˇ
 3183        const a: B = (
 3184            c(
 3185                d(
 3186                    ˇ
 3187                )
 3188                ˇ
 3189            ˇ)
 3190        );
 3191    "});
 3192    // test when all cursors are at suggested indent then tab is inserted
 3193    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3194    cx.assert_editor_state(indoc! {"
 3195            ˇ
 3196        const a: B = (
 3197            c(
 3198                d(
 3199                        ˇ
 3200                )
 3201                    ˇ
 3202                ˇ)
 3203        );
 3204    "});
 3205
 3206    // test when current indent is less than suggested indent,
 3207    // we adjust line to match suggested indent and move cursor to it
 3208    //
 3209    // when no other cursor is at word boundary, all of them should move
 3210    cx.set_state(indoc! {"
 3211        const a: B = (
 3212            c(
 3213                d(
 3214        ˇ
 3215        ˇ   )
 3216        ˇ   )
 3217        );
 3218    "});
 3219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221        const a: B = (
 3222            c(
 3223                d(
 3224                    ˇ
 3225                ˇ)
 3226            ˇ)
 3227        );
 3228    "});
 3229
 3230    // test when current indent is less than suggested indent,
 3231    // we adjust line to match suggested indent and move cursor to it
 3232    //
 3233    // when some other cursor is at word boundary, it should not move
 3234    cx.set_state(indoc! {"
 3235        const a: B = (
 3236            c(
 3237                d(
 3238        ˇ
 3239        ˇ   )
 3240           ˇ)
 3241        );
 3242    "});
 3243    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3244    cx.assert_editor_state(indoc! {"
 3245        const a: B = (
 3246            c(
 3247                d(
 3248                    ˇ
 3249                ˇ)
 3250            ˇ)
 3251        );
 3252    "});
 3253
 3254    // test when current indent is more than suggested indent,
 3255    // we just move cursor to current indent instead of suggested indent
 3256    //
 3257    // when no other cursor is at word boundary, all of them should move
 3258    cx.set_state(indoc! {"
 3259        const a: B = (
 3260            c(
 3261                d(
 3262        ˇ
 3263        ˇ                )
 3264        ˇ   )
 3265        );
 3266    "});
 3267    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3268    cx.assert_editor_state(indoc! {"
 3269        const a: B = (
 3270            c(
 3271                d(
 3272                    ˇ
 3273                        ˇ)
 3274            ˇ)
 3275        );
 3276    "});
 3277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3278    cx.assert_editor_state(indoc! {"
 3279        const a: B = (
 3280            c(
 3281                d(
 3282                        ˇ
 3283                            ˇ)
 3284                ˇ)
 3285        );
 3286    "});
 3287
 3288    // test when current indent is more than suggested indent,
 3289    // we just move cursor to current indent instead of suggested indent
 3290    //
 3291    // when some other cursor is at word boundary, it doesn't move
 3292    cx.set_state(indoc! {"
 3293        const a: B = (
 3294            c(
 3295                d(
 3296        ˇ
 3297        ˇ                )
 3298            ˇ)
 3299        );
 3300    "});
 3301    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3302    cx.assert_editor_state(indoc! {"
 3303        const a: B = (
 3304            c(
 3305                d(
 3306                    ˇ
 3307                        ˇ)
 3308            ˇ)
 3309        );
 3310    "});
 3311
 3312    // handle auto-indent when there are multiple cursors on the same line
 3313    cx.set_state(indoc! {"
 3314        const a: B = (
 3315            c(
 3316        ˇ    ˇ
 3317        ˇ    )
 3318        );
 3319    "});
 3320    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3321    cx.assert_editor_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324                ˇ
 3325            ˇ)
 3326        );
 3327    "});
 3328}
 3329
 3330#[gpui::test]
 3331async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3332    init_test(cx, |settings| {
 3333        settings.defaults.tab_size = NonZeroU32::new(3)
 3334    });
 3335
 3336    let mut cx = EditorTestContext::new(cx).await;
 3337    cx.set_state(indoc! {"
 3338         ˇ
 3339        \t ˇ
 3340        \t  ˇ
 3341        \t   ˇ
 3342         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3343    "});
 3344
 3345    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3346    cx.assert_editor_state(indoc! {"
 3347           ˇ
 3348        \t   ˇ
 3349        \t   ˇ
 3350        \t      ˇ
 3351         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3352    "});
 3353}
 3354
 3355#[gpui::test]
 3356async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3357    init_test(cx, |settings| {
 3358        settings.defaults.tab_size = NonZeroU32::new(4)
 3359    });
 3360
 3361    let language = Arc::new(
 3362        Language::new(
 3363            LanguageConfig::default(),
 3364            Some(tree_sitter_rust::LANGUAGE.into()),
 3365        )
 3366        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3367        .unwrap(),
 3368    );
 3369
 3370    let mut cx = EditorTestContext::new(cx).await;
 3371    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3372    cx.set_state(indoc! {"
 3373        fn a() {
 3374            if b {
 3375        \t ˇc
 3376            }
 3377        }
 3378    "});
 3379
 3380    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3381    cx.assert_editor_state(indoc! {"
 3382        fn a() {
 3383            if b {
 3384                ˇc
 3385            }
 3386        }
 3387    "});
 3388}
 3389
 3390#[gpui::test]
 3391async fn test_indent_outdent(cx: &mut TestAppContext) {
 3392    init_test(cx, |settings| {
 3393        settings.defaults.tab_size = NonZeroU32::new(4);
 3394    });
 3395
 3396    let mut cx = EditorTestContext::new(cx).await;
 3397
 3398    cx.set_state(indoc! {"
 3399          «oneˇ» «twoˇ»
 3400        three
 3401         four
 3402    "});
 3403    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3404    cx.assert_editor_state(indoc! {"
 3405            «oneˇ» «twoˇ»
 3406        three
 3407         four
 3408    "});
 3409
 3410    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3411    cx.assert_editor_state(indoc! {"
 3412        «oneˇ» «twoˇ»
 3413        three
 3414         four
 3415    "});
 3416
 3417    // select across line ending
 3418    cx.set_state(indoc! {"
 3419        one two
 3420        t«hree
 3421        ˇ» four
 3422    "});
 3423    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3424    cx.assert_editor_state(indoc! {"
 3425        one two
 3426            t«hree
 3427        ˇ» four
 3428    "});
 3429
 3430    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3431    cx.assert_editor_state(indoc! {"
 3432        one two
 3433        t«hree
 3434        ˇ» four
 3435    "});
 3436
 3437    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3438    cx.set_state(indoc! {"
 3439        one two
 3440        ˇthree
 3441            four
 3442    "});
 3443    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3444    cx.assert_editor_state(indoc! {"
 3445        one two
 3446            ˇthree
 3447            four
 3448    "});
 3449
 3450    cx.set_state(indoc! {"
 3451        one two
 3452        ˇ    three
 3453            four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457        one two
 3458        ˇthree
 3459            four
 3460    "});
 3461}
 3462
 3463#[gpui::test]
 3464async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3465    init_test(cx, |settings| {
 3466        settings.defaults.hard_tabs = Some(true);
 3467    });
 3468
 3469    let mut cx = EditorTestContext::new(cx).await;
 3470
 3471    // select two ranges on one line
 3472    cx.set_state(indoc! {"
 3473        «oneˇ» «twoˇ»
 3474        three
 3475        four
 3476    "});
 3477    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        \t«oneˇ» «twoˇ»
 3480        three
 3481        four
 3482    "});
 3483    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3484    cx.assert_editor_state(indoc! {"
 3485        \t\t«oneˇ» «twoˇ»
 3486        three
 3487        four
 3488    "});
 3489    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3490    cx.assert_editor_state(indoc! {"
 3491        \t«oneˇ» «twoˇ»
 3492        three
 3493        four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        «oneˇ» «twoˇ»
 3498        three
 3499        four
 3500    "});
 3501
 3502    // select across a line ending
 3503    cx.set_state(indoc! {"
 3504        one two
 3505        t«hree
 3506        ˇ»four
 3507    "});
 3508    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3509    cx.assert_editor_state(indoc! {"
 3510        one two
 3511        \tt«hree
 3512        ˇ»four
 3513    "});
 3514    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3515    cx.assert_editor_state(indoc! {"
 3516        one two
 3517        \t\tt«hree
 3518        ˇ»four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523        \tt«hree
 3524        ˇ»four
 3525    "});
 3526    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3527    cx.assert_editor_state(indoc! {"
 3528        one two
 3529        t«hree
 3530        ˇ»four
 3531    "});
 3532
 3533    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3534    cx.set_state(indoc! {"
 3535        one two
 3536        ˇthree
 3537        four
 3538    "});
 3539    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3540    cx.assert_editor_state(indoc! {"
 3541        one two
 3542        ˇthree
 3543        four
 3544    "});
 3545    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3546    cx.assert_editor_state(indoc! {"
 3547        one two
 3548        \tˇthree
 3549        four
 3550    "});
 3551    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3552    cx.assert_editor_state(indoc! {"
 3553        one two
 3554        ˇthree
 3555        four
 3556    "});
 3557}
 3558
 3559#[gpui::test]
 3560fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3561    init_test(cx, |settings| {
 3562        settings.languages.extend([
 3563            (
 3564                "TOML".into(),
 3565                LanguageSettingsContent {
 3566                    tab_size: NonZeroU32::new(2),
 3567                    ..Default::default()
 3568                },
 3569            ),
 3570            (
 3571                "Rust".into(),
 3572                LanguageSettingsContent {
 3573                    tab_size: NonZeroU32::new(4),
 3574                    ..Default::default()
 3575                },
 3576            ),
 3577        ]);
 3578    });
 3579
 3580    let toml_language = Arc::new(Language::new(
 3581        LanguageConfig {
 3582            name: "TOML".into(),
 3583            ..Default::default()
 3584        },
 3585        None,
 3586    ));
 3587    let rust_language = Arc::new(Language::new(
 3588        LanguageConfig {
 3589            name: "Rust".into(),
 3590            ..Default::default()
 3591        },
 3592        None,
 3593    ));
 3594
 3595    let toml_buffer =
 3596        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3597    let rust_buffer =
 3598        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3599    let multibuffer = cx.new(|cx| {
 3600        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3601        multibuffer.push_excerpts(
 3602            toml_buffer.clone(),
 3603            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3604            cx,
 3605        );
 3606        multibuffer.push_excerpts(
 3607            rust_buffer.clone(),
 3608            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3609            cx,
 3610        );
 3611        multibuffer
 3612    });
 3613
 3614    cx.add_window(|window, cx| {
 3615        let mut editor = build_editor(multibuffer, window, cx);
 3616
 3617        assert_eq!(
 3618            editor.text(cx),
 3619            indoc! {"
 3620                a = 1
 3621                b = 2
 3622
 3623                const c: usize = 3;
 3624            "}
 3625        );
 3626
 3627        select_ranges(
 3628            &mut editor,
 3629            indoc! {"
 3630                «aˇ» = 1
 3631                b = 2
 3632
 3633                «const c:ˇ» usize = 3;
 3634            "},
 3635            window,
 3636            cx,
 3637        );
 3638
 3639        editor.tab(&Tab, window, cx);
 3640        assert_text_with_selections(
 3641            &mut editor,
 3642            indoc! {"
 3643                  «aˇ» = 1
 3644                b = 2
 3645
 3646                    «const c:ˇ» usize = 3;
 3647            "},
 3648            cx,
 3649        );
 3650        editor.backtab(&Backtab, window, cx);
 3651        assert_text_with_selections(
 3652            &mut editor,
 3653            indoc! {"
 3654                «aˇ» = 1
 3655                b = 2
 3656
 3657                «const c:ˇ» usize = 3;
 3658            "},
 3659            cx,
 3660        );
 3661
 3662        editor
 3663    });
 3664}
 3665
 3666#[gpui::test]
 3667async fn test_backspace(cx: &mut TestAppContext) {
 3668    init_test(cx, |_| {});
 3669
 3670    let mut cx = EditorTestContext::new(cx).await;
 3671
 3672    // Basic backspace
 3673    cx.set_state(indoc! {"
 3674        onˇe two three
 3675        fou«rˇ» five six
 3676        seven «ˇeight nine
 3677        »ten
 3678    "});
 3679    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3680    cx.assert_editor_state(indoc! {"
 3681        oˇe two three
 3682        fouˇ five six
 3683        seven ˇten
 3684    "});
 3685
 3686    // Test backspace inside and around indents
 3687    cx.set_state(indoc! {"
 3688        zero
 3689            ˇone
 3690                ˇtwo
 3691            ˇ ˇ ˇ  three
 3692        ˇ  ˇ  four
 3693    "});
 3694    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3695    cx.assert_editor_state(indoc! {"
 3696        zero
 3697        ˇone
 3698            ˇtwo
 3699        ˇ  threeˇ  four
 3700    "});
 3701}
 3702
 3703#[gpui::test]
 3704async fn test_delete(cx: &mut TestAppContext) {
 3705    init_test(cx, |_| {});
 3706
 3707    let mut cx = EditorTestContext::new(cx).await;
 3708    cx.set_state(indoc! {"
 3709        onˇe two three
 3710        fou«rˇ» five six
 3711        seven «ˇeight nine
 3712        »ten
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        onˇ two three
 3717        fouˇ five six
 3718        seven ˇten
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723fn test_delete_line(cx: &mut TestAppContext) {
 3724    init_test(cx, |_| {});
 3725
 3726    let editor = cx.add_window(|window, cx| {
 3727        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3728        build_editor(buffer, window, cx)
 3729    });
 3730    _ = editor.update(cx, |editor, window, cx| {
 3731        editor.change_selections(None, window, cx, |s| {
 3732            s.select_display_ranges([
 3733                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3734                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3735                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3736            ])
 3737        });
 3738        editor.delete_line(&DeleteLine, window, cx);
 3739        assert_eq!(editor.display_text(cx), "ghi");
 3740        assert_eq!(
 3741            editor.selections.display_ranges(cx),
 3742            vec![
 3743                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3744                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3745            ]
 3746        );
 3747    });
 3748
 3749    let editor = cx.add_window(|window, cx| {
 3750        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3751        build_editor(buffer, window, cx)
 3752    });
 3753    _ = editor.update(cx, |editor, window, cx| {
 3754        editor.change_selections(None, window, cx, |s| {
 3755            s.select_display_ranges([
 3756                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3757            ])
 3758        });
 3759        editor.delete_line(&DeleteLine, window, cx);
 3760        assert_eq!(editor.display_text(cx), "ghi\n");
 3761        assert_eq!(
 3762            editor.selections.display_ranges(cx),
 3763            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3764        );
 3765    });
 3766}
 3767
 3768#[gpui::test]
 3769fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3770    init_test(cx, |_| {});
 3771
 3772    cx.add_window(|window, cx| {
 3773        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3774        let mut editor = build_editor(buffer.clone(), window, cx);
 3775        let buffer = buffer.read(cx).as_singleton().unwrap();
 3776
 3777        assert_eq!(
 3778            editor.selections.ranges::<Point>(cx),
 3779            &[Point::new(0, 0)..Point::new(0, 0)]
 3780        );
 3781
 3782        // When on single line, replace newline at end by space
 3783        editor.join_lines(&JoinLines, window, cx);
 3784        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 3)..Point::new(0, 3)]
 3788        );
 3789
 3790        // When multiple lines are selected, remove newlines that are spanned by the selection
 3791        editor.change_selections(None, window, cx, |s| {
 3792            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3793        });
 3794        editor.join_lines(&JoinLines, window, cx);
 3795        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3796        assert_eq!(
 3797            editor.selections.ranges::<Point>(cx),
 3798            &[Point::new(0, 11)..Point::new(0, 11)]
 3799        );
 3800
 3801        // Undo should be transactional
 3802        editor.undo(&Undo, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 5)..Point::new(2, 2)]
 3807        );
 3808
 3809        // When joining an empty line don't insert a space
 3810        editor.change_selections(None, window, cx, |s| {
 3811            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3812        });
 3813        editor.join_lines(&JoinLines, window, cx);
 3814        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3815        assert_eq!(
 3816            editor.selections.ranges::<Point>(cx),
 3817            [Point::new(2, 3)..Point::new(2, 3)]
 3818        );
 3819
 3820        // We can remove trailing newlines
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We don't blow up on the last line
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // reset to test indentation
 3837        editor.buffer.update(cx, |buffer, cx| {
 3838            buffer.edit(
 3839                [
 3840                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3841                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3842                ],
 3843                None,
 3844                cx,
 3845            )
 3846        });
 3847
 3848        // We remove any leading spaces
 3849        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3850        editor.change_selections(None, window, cx, |s| {
 3851            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3852        });
 3853        editor.join_lines(&JoinLines, window, cx);
 3854        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3855
 3856        // We don't insert a space for a line containing only spaces
 3857        editor.join_lines(&JoinLines, window, cx);
 3858        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3859
 3860        // We ignore any leading tabs
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3863
 3864        editor
 3865    });
 3866}
 3867
 3868#[gpui::test]
 3869fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3870    init_test(cx, |_| {});
 3871
 3872    cx.add_window(|window, cx| {
 3873        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3874        let mut editor = build_editor(buffer.clone(), window, cx);
 3875        let buffer = buffer.read(cx).as_singleton().unwrap();
 3876
 3877        editor.change_selections(None, window, cx, |s| {
 3878            s.select_ranges([
 3879                Point::new(0, 2)..Point::new(1, 1),
 3880                Point::new(1, 2)..Point::new(1, 2),
 3881                Point::new(3, 1)..Point::new(3, 2),
 3882            ])
 3883        });
 3884
 3885        editor.join_lines(&JoinLines, window, cx);
 3886        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3887
 3888        assert_eq!(
 3889            editor.selections.ranges::<Point>(cx),
 3890            [
 3891                Point::new(0, 7)..Point::new(0, 7),
 3892                Point::new(1, 3)..Point::new(1, 3)
 3893            ]
 3894        );
 3895        editor
 3896    });
 3897}
 3898
 3899#[gpui::test]
 3900async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3901    init_test(cx, |_| {});
 3902
 3903    let mut cx = EditorTestContext::new(cx).await;
 3904
 3905    let diff_base = r#"
 3906        Line 0
 3907        Line 1
 3908        Line 2
 3909        Line 3
 3910        "#
 3911    .unindent();
 3912
 3913    cx.set_state(
 3914        &r#"
 3915        ˇLine 0
 3916        Line 1
 3917        Line 2
 3918        Line 3
 3919        "#
 3920        .unindent(),
 3921    );
 3922
 3923    cx.set_head_text(&diff_base);
 3924    executor.run_until_parked();
 3925
 3926    // Join lines
 3927    cx.update_editor(|editor, window, cx| {
 3928        editor.join_lines(&JoinLines, window, cx);
 3929    });
 3930    executor.run_until_parked();
 3931
 3932    cx.assert_editor_state(
 3933        &r#"
 3934        Line 0ˇ Line 1
 3935        Line 2
 3936        Line 3
 3937        "#
 3938        .unindent(),
 3939    );
 3940    // Join again
 3941    cx.update_editor(|editor, window, cx| {
 3942        editor.join_lines(&JoinLines, window, cx);
 3943    });
 3944    executor.run_until_parked();
 3945
 3946    cx.assert_editor_state(
 3947        &r#"
 3948        Line 0 Line 1ˇ Line 2
 3949        Line 3
 3950        "#
 3951        .unindent(),
 3952    );
 3953}
 3954
 3955#[gpui::test]
 3956async fn test_custom_newlines_cause_no_false_positive_diffs(
 3957    executor: BackgroundExecutor,
 3958    cx: &mut TestAppContext,
 3959) {
 3960    init_test(cx, |_| {});
 3961    let mut cx = EditorTestContext::new(cx).await;
 3962    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3963    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3964    executor.run_until_parked();
 3965
 3966    cx.update_editor(|editor, window, cx| {
 3967        let snapshot = editor.snapshot(window, cx);
 3968        assert_eq!(
 3969            snapshot
 3970                .buffer_snapshot
 3971                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3972                .collect::<Vec<_>>(),
 3973            Vec::new(),
 3974            "Should not have any diffs for files with custom newlines"
 3975        );
 3976    });
 3977}
 3978
 3979#[gpui::test]
 3980async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3981    init_test(cx, |_| {});
 3982
 3983    let mut cx = EditorTestContext::new(cx).await;
 3984
 3985    // Test sort_lines_case_insensitive()
 3986    cx.set_state(indoc! {"
 3987        «z
 3988        y
 3989        x
 3990        Z
 3991        Y
 3992        Xˇ»
 3993    "});
 3994    cx.update_editor(|e, window, cx| {
 3995        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3996    });
 3997    cx.assert_editor_state(indoc! {"
 3998        «x
 3999        X
 4000        y
 4001        Y
 4002        z
 4003        Zˇ»
 4004    "});
 4005
 4006    // Test reverse_lines()
 4007    cx.set_state(indoc! {"
 4008        «5
 4009        4
 4010        3
 4011        2
 4012        1ˇ»
 4013    "});
 4014    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4015    cx.assert_editor_state(indoc! {"
 4016        «1
 4017        2
 4018        3
 4019        4
 4020        5ˇ»
 4021    "});
 4022
 4023    // Skip testing shuffle_line()
 4024
 4025    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 4026    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 4027
 4028    // Don't manipulate when cursor is on single line, but expand the selection
 4029    cx.set_state(indoc! {"
 4030        ddˇdd
 4031        ccc
 4032        bb
 4033        a
 4034    "});
 4035    cx.update_editor(|e, window, cx| {
 4036        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4037    });
 4038    cx.assert_editor_state(indoc! {"
 4039        «ddddˇ»
 4040        ccc
 4041        bb
 4042        a
 4043    "});
 4044
 4045    // Basic manipulate case
 4046    // Start selection moves to column 0
 4047    // End of selection shrinks to fit shorter line
 4048    cx.set_state(indoc! {"
 4049        dd«d
 4050        ccc
 4051        bb
 4052        aaaaaˇ»
 4053    "});
 4054    cx.update_editor(|e, window, cx| {
 4055        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4056    });
 4057    cx.assert_editor_state(indoc! {"
 4058        «aaaaa
 4059        bb
 4060        ccc
 4061        dddˇ»
 4062    "});
 4063
 4064    // Manipulate case with newlines
 4065    cx.set_state(indoc! {"
 4066        dd«d
 4067        ccc
 4068
 4069        bb
 4070        aaaaa
 4071
 4072        ˇ»
 4073    "});
 4074    cx.update_editor(|e, window, cx| {
 4075        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4076    });
 4077    cx.assert_editor_state(indoc! {"
 4078        «
 4079
 4080        aaaaa
 4081        bb
 4082        ccc
 4083        dddˇ»
 4084
 4085    "});
 4086
 4087    // Adding new line
 4088    cx.set_state(indoc! {"
 4089        aa«a
 4090        bbˇ»b
 4091    "});
 4092    cx.update_editor(|e, window, cx| {
 4093        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4094    });
 4095    cx.assert_editor_state(indoc! {"
 4096        «aaa
 4097        bbb
 4098        added_lineˇ»
 4099    "});
 4100
 4101    // Removing line
 4102    cx.set_state(indoc! {"
 4103        aa«a
 4104        bbbˇ»
 4105    "});
 4106    cx.update_editor(|e, window, cx| {
 4107        e.manipulate_lines(window, cx, |lines| {
 4108            lines.pop();
 4109        })
 4110    });
 4111    cx.assert_editor_state(indoc! {"
 4112        «aaaˇ»
 4113    "});
 4114
 4115    // Removing all lines
 4116    cx.set_state(indoc! {"
 4117        aa«a
 4118        bbbˇ»
 4119    "});
 4120    cx.update_editor(|e, window, cx| {
 4121        e.manipulate_lines(window, cx, |lines| {
 4122            lines.drain(..);
 4123        })
 4124    });
 4125    cx.assert_editor_state(indoc! {"
 4126        ˇ
 4127    "});
 4128}
 4129
 4130#[gpui::test]
 4131async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4132    init_test(cx, |_| {});
 4133
 4134    let mut cx = EditorTestContext::new(cx).await;
 4135
 4136    // Consider continuous selection as single selection
 4137    cx.set_state(indoc! {"
 4138        Aaa«aa
 4139        cˇ»c«c
 4140        bb
 4141        aaaˇ»aa
 4142    "});
 4143    cx.update_editor(|e, window, cx| {
 4144        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4145    });
 4146    cx.assert_editor_state(indoc! {"
 4147        «Aaaaa
 4148        ccc
 4149        bb
 4150        aaaaaˇ»
 4151    "});
 4152
 4153    cx.set_state(indoc! {"
 4154        Aaa«aa
 4155        cˇ»c«c
 4156        bb
 4157        aaaˇ»aa
 4158    "});
 4159    cx.update_editor(|e, window, cx| {
 4160        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4161    });
 4162    cx.assert_editor_state(indoc! {"
 4163        «Aaaaa
 4164        ccc
 4165        bbˇ»
 4166    "});
 4167
 4168    // Consider non continuous selection as distinct dedup operations
 4169    cx.set_state(indoc! {"
 4170        «aaaaa
 4171        bb
 4172        aaaaa
 4173        aaaaaˇ»
 4174
 4175        aaa«aaˇ»
 4176    "});
 4177    cx.update_editor(|e, window, cx| {
 4178        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4179    });
 4180    cx.assert_editor_state(indoc! {"
 4181        «aaaaa
 4182        bbˇ»
 4183
 4184        «aaaaaˇ»
 4185    "});
 4186}
 4187
 4188#[gpui::test]
 4189async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4190    init_test(cx, |_| {});
 4191
 4192    let mut cx = EditorTestContext::new(cx).await;
 4193
 4194    cx.set_state(indoc! {"
 4195        «Aaa
 4196        aAa
 4197        Aaaˇ»
 4198    "});
 4199    cx.update_editor(|e, window, cx| {
 4200        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4201    });
 4202    cx.assert_editor_state(indoc! {"
 4203        «Aaa
 4204        aAaˇ»
 4205    "});
 4206
 4207    cx.set_state(indoc! {"
 4208        «Aaa
 4209        aAa
 4210        aaAˇ»
 4211    "});
 4212    cx.update_editor(|e, window, cx| {
 4213        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4214    });
 4215    cx.assert_editor_state(indoc! {"
 4216        «Aaaˇ»
 4217    "});
 4218}
 4219
 4220#[gpui::test]
 4221async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4222    init_test(cx, |_| {});
 4223
 4224    let mut cx = EditorTestContext::new(cx).await;
 4225
 4226    // Manipulate with multiple selections on a single line
 4227    cx.set_state(indoc! {"
 4228        dd«dd
 4229        cˇ»c«c
 4230        bb
 4231        aaaˇ»aa
 4232    "});
 4233    cx.update_editor(|e, window, cx| {
 4234        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4235    });
 4236    cx.assert_editor_state(indoc! {"
 4237        «aaaaa
 4238        bb
 4239        ccc
 4240        ddddˇ»
 4241    "});
 4242
 4243    // Manipulate with multiple disjoin selections
 4244    cx.set_state(indoc! {"
 4245 4246        4
 4247        3
 4248        2
 4249        1ˇ»
 4250
 4251        dd«dd
 4252        ccc
 4253        bb
 4254        aaaˇ»aa
 4255    "});
 4256    cx.update_editor(|e, window, cx| {
 4257        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4258    });
 4259    cx.assert_editor_state(indoc! {"
 4260        «1
 4261        2
 4262        3
 4263        4
 4264        5ˇ»
 4265
 4266        «aaaaa
 4267        bb
 4268        ccc
 4269        ddddˇ»
 4270    "});
 4271
 4272    // Adding lines on each selection
 4273    cx.set_state(indoc! {"
 4274 4275        1ˇ»
 4276
 4277        bb«bb
 4278        aaaˇ»aa
 4279    "});
 4280    cx.update_editor(|e, window, cx| {
 4281        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4282    });
 4283    cx.assert_editor_state(indoc! {"
 4284        «2
 4285        1
 4286        added lineˇ»
 4287
 4288        «bbbb
 4289        aaaaa
 4290        added lineˇ»
 4291    "});
 4292
 4293    // Removing lines on each selection
 4294    cx.set_state(indoc! {"
 4295 4296        1ˇ»
 4297
 4298        bb«bb
 4299        aaaˇ»aa
 4300    "});
 4301    cx.update_editor(|e, window, cx| {
 4302        e.manipulate_lines(window, cx, |lines| {
 4303            lines.pop();
 4304        })
 4305    });
 4306    cx.assert_editor_state(indoc! {"
 4307        «2ˇ»
 4308
 4309        «bbbbˇ»
 4310    "});
 4311}
 4312
 4313#[gpui::test]
 4314async fn test_toggle_case(cx: &mut TestAppContext) {
 4315    init_test(cx, |_| {});
 4316
 4317    let mut cx = EditorTestContext::new(cx).await;
 4318
 4319    // If all lower case -> upper case
 4320    cx.set_state(indoc! {"
 4321        «hello worldˇ»
 4322    "});
 4323    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4324    cx.assert_editor_state(indoc! {"
 4325        «HELLO WORLDˇ»
 4326    "});
 4327
 4328    // If all upper case -> lower case
 4329    cx.set_state(indoc! {"
 4330        «HELLO WORLDˇ»
 4331    "});
 4332    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4333    cx.assert_editor_state(indoc! {"
 4334        «hello worldˇ»
 4335    "});
 4336
 4337    // If any upper case characters are identified -> lower case
 4338    // This matches JetBrains IDEs
 4339    cx.set_state(indoc! {"
 4340        «hEllo worldˇ»
 4341    "});
 4342    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4343    cx.assert_editor_state(indoc! {"
 4344        «hello worldˇ»
 4345    "});
 4346}
 4347
 4348#[gpui::test]
 4349async fn test_manipulate_text(cx: &mut TestAppContext) {
 4350    init_test(cx, |_| {});
 4351
 4352    let mut cx = EditorTestContext::new(cx).await;
 4353
 4354    // Test convert_to_upper_case()
 4355    cx.set_state(indoc! {"
 4356        «hello worldˇ»
 4357    "});
 4358    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4359    cx.assert_editor_state(indoc! {"
 4360        «HELLO WORLDˇ»
 4361    "});
 4362
 4363    // Test convert_to_lower_case()
 4364    cx.set_state(indoc! {"
 4365        «HELLO WORLDˇ»
 4366    "});
 4367    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4368    cx.assert_editor_state(indoc! {"
 4369        «hello worldˇ»
 4370    "});
 4371
 4372    // Test multiple line, single selection case
 4373    cx.set_state(indoc! {"
 4374        «The quick brown
 4375        fox jumps over
 4376        the lazy dogˇ»
 4377    "});
 4378    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4379    cx.assert_editor_state(indoc! {"
 4380        «The Quick Brown
 4381        Fox Jumps Over
 4382        The Lazy Dogˇ»
 4383    "});
 4384
 4385    // Test multiple line, single selection case
 4386    cx.set_state(indoc! {"
 4387        «The quick brown
 4388        fox jumps over
 4389        the lazy dogˇ»
 4390    "});
 4391    cx.update_editor(|e, window, cx| {
 4392        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4393    });
 4394    cx.assert_editor_state(indoc! {"
 4395        «TheQuickBrown
 4396        FoxJumpsOver
 4397        TheLazyDogˇ»
 4398    "});
 4399
 4400    // From here on out, test more complex cases of manipulate_text()
 4401
 4402    // Test no selection case - should affect words cursors are in
 4403    // Cursor at beginning, middle, and end of word
 4404    cx.set_state(indoc! {"
 4405        ˇhello big beauˇtiful worldˇ
 4406    "});
 4407    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4408    cx.assert_editor_state(indoc! {"
 4409        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4410    "});
 4411
 4412    // Test multiple selections on a single line and across multiple lines
 4413    cx.set_state(indoc! {"
 4414        «Theˇ» quick «brown
 4415        foxˇ» jumps «overˇ»
 4416        the «lazyˇ» dog
 4417    "});
 4418    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4419    cx.assert_editor_state(indoc! {"
 4420        «THEˇ» quick «BROWN
 4421        FOXˇ» jumps «OVERˇ»
 4422        the «LAZYˇ» dog
 4423    "});
 4424
 4425    // Test case where text length grows
 4426    cx.set_state(indoc! {"
 4427        «tschüߡ»
 4428    "});
 4429    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4430    cx.assert_editor_state(indoc! {"
 4431        «TSCHÜSSˇ»
 4432    "});
 4433
 4434    // Test to make sure we don't crash when text shrinks
 4435    cx.set_state(indoc! {"
 4436        aaa_bbbˇ
 4437    "});
 4438    cx.update_editor(|e, window, cx| {
 4439        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4440    });
 4441    cx.assert_editor_state(indoc! {"
 4442        «aaaBbbˇ»
 4443    "});
 4444
 4445    // Test to make sure we all aware of the fact that each word can grow and shrink
 4446    // Final selections should be aware of this fact
 4447    cx.set_state(indoc! {"
 4448        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4449    "});
 4450    cx.update_editor(|e, window, cx| {
 4451        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4452    });
 4453    cx.assert_editor_state(indoc! {"
 4454        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4455    "});
 4456
 4457    cx.set_state(indoc! {"
 4458        «hElLo, WoRld!ˇ»
 4459    "});
 4460    cx.update_editor(|e, window, cx| {
 4461        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4462    });
 4463    cx.assert_editor_state(indoc! {"
 4464        «HeLlO, wOrLD!ˇ»
 4465    "});
 4466}
 4467
 4468#[gpui::test]
 4469fn test_duplicate_line(cx: &mut TestAppContext) {
 4470    init_test(cx, |_| {});
 4471
 4472    let editor = cx.add_window(|window, cx| {
 4473        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4474        build_editor(buffer, window, cx)
 4475    });
 4476    _ = editor.update(cx, |editor, window, cx| {
 4477        editor.change_selections(None, window, cx, |s| {
 4478            s.select_display_ranges([
 4479                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4480                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4481                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4482                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4483            ])
 4484        });
 4485        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4486        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4487        assert_eq!(
 4488            editor.selections.display_ranges(cx),
 4489            vec![
 4490                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4491                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4492                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4493                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4494            ]
 4495        );
 4496    });
 4497
 4498    let editor = cx.add_window(|window, cx| {
 4499        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4500        build_editor(buffer, window, cx)
 4501    });
 4502    _ = editor.update(cx, |editor, window, cx| {
 4503        editor.change_selections(None, window, cx, |s| {
 4504            s.select_display_ranges([
 4505                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4506                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4507            ])
 4508        });
 4509        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4510        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4511        assert_eq!(
 4512            editor.selections.display_ranges(cx),
 4513            vec![
 4514                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4515                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4516            ]
 4517        );
 4518    });
 4519
 4520    // With `move_upwards` the selections stay in place, except for
 4521    // the lines inserted above them
 4522    let editor = cx.add_window(|window, cx| {
 4523        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4524        build_editor(buffer, window, cx)
 4525    });
 4526    _ = editor.update(cx, |editor, window, cx| {
 4527        editor.change_selections(None, window, cx, |s| {
 4528            s.select_display_ranges([
 4529                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4530                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4531                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4532                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4533            ])
 4534        });
 4535        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4536        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4537        assert_eq!(
 4538            editor.selections.display_ranges(cx),
 4539            vec![
 4540                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4541                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4542                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4543                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4544            ]
 4545        );
 4546    });
 4547
 4548    let editor = cx.add_window(|window, cx| {
 4549        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4550        build_editor(buffer, window, cx)
 4551    });
 4552    _ = editor.update(cx, |editor, window, cx| {
 4553        editor.change_selections(None, window, cx, |s| {
 4554            s.select_display_ranges([
 4555                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4556                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4557            ])
 4558        });
 4559        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4560        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4561        assert_eq!(
 4562            editor.selections.display_ranges(cx),
 4563            vec![
 4564                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4565                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4566            ]
 4567        );
 4568    });
 4569
 4570    let editor = cx.add_window(|window, cx| {
 4571        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4572        build_editor(buffer, window, cx)
 4573    });
 4574    _ = editor.update(cx, |editor, window, cx| {
 4575        editor.change_selections(None, window, cx, |s| {
 4576            s.select_display_ranges([
 4577                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4578                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4579            ])
 4580        });
 4581        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4582        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4583        assert_eq!(
 4584            editor.selections.display_ranges(cx),
 4585            vec![
 4586                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4587                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4588            ]
 4589        );
 4590    });
 4591}
 4592
 4593#[gpui::test]
 4594fn test_move_line_up_down(cx: &mut TestAppContext) {
 4595    init_test(cx, |_| {});
 4596
 4597    let editor = cx.add_window(|window, cx| {
 4598        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4599        build_editor(buffer, window, cx)
 4600    });
 4601    _ = editor.update(cx, |editor, window, cx| {
 4602        editor.fold_creases(
 4603            vec![
 4604                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4605                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4606                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4607            ],
 4608            true,
 4609            window,
 4610            cx,
 4611        );
 4612        editor.change_selections(None, window, cx, |s| {
 4613            s.select_display_ranges([
 4614                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4615                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4616                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4617                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4618            ])
 4619        });
 4620        assert_eq!(
 4621            editor.display_text(cx),
 4622            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4623        );
 4624
 4625        editor.move_line_up(&MoveLineUp, window, cx);
 4626        assert_eq!(
 4627            editor.display_text(cx),
 4628            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4629        );
 4630        assert_eq!(
 4631            editor.selections.display_ranges(cx),
 4632            vec![
 4633                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4634                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4635                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4636                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4637            ]
 4638        );
 4639    });
 4640
 4641    _ = editor.update(cx, |editor, window, cx| {
 4642        editor.move_line_down(&MoveLineDown, window, cx);
 4643        assert_eq!(
 4644            editor.display_text(cx),
 4645            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4646        );
 4647        assert_eq!(
 4648            editor.selections.display_ranges(cx),
 4649            vec![
 4650                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4651                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4652                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4653                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4654            ]
 4655        );
 4656    });
 4657
 4658    _ = editor.update(cx, |editor, window, cx| {
 4659        editor.move_line_down(&MoveLineDown, window, cx);
 4660        assert_eq!(
 4661            editor.display_text(cx),
 4662            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4663        );
 4664        assert_eq!(
 4665            editor.selections.display_ranges(cx),
 4666            vec![
 4667                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4668                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4669                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4670                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4671            ]
 4672        );
 4673    });
 4674
 4675    _ = editor.update(cx, |editor, window, cx| {
 4676        editor.move_line_up(&MoveLineUp, window, cx);
 4677        assert_eq!(
 4678            editor.display_text(cx),
 4679            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4680        );
 4681        assert_eq!(
 4682            editor.selections.display_ranges(cx),
 4683            vec![
 4684                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4685                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4686                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4687                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4688            ]
 4689        );
 4690    });
 4691}
 4692
 4693#[gpui::test]
 4694fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4695    init_test(cx, |_| {});
 4696
 4697    let editor = cx.add_window(|window, cx| {
 4698        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4699        build_editor(buffer, window, cx)
 4700    });
 4701    _ = editor.update(cx, |editor, window, cx| {
 4702        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4703        editor.insert_blocks(
 4704            [BlockProperties {
 4705                style: BlockStyle::Fixed,
 4706                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4707                height: Some(1),
 4708                render: Arc::new(|_| div().into_any()),
 4709                priority: 0,
 4710                render_in_minimap: true,
 4711            }],
 4712            Some(Autoscroll::fit()),
 4713            cx,
 4714        );
 4715        editor.change_selections(None, window, cx, |s| {
 4716            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4717        });
 4718        editor.move_line_down(&MoveLineDown, window, cx);
 4719    });
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4724    init_test(cx, |_| {});
 4725
 4726    let mut cx = EditorTestContext::new(cx).await;
 4727    cx.set_state(
 4728        &"
 4729            ˇzero
 4730            one
 4731            two
 4732            three
 4733            four
 4734            five
 4735        "
 4736        .unindent(),
 4737    );
 4738
 4739    // Create a four-line block that replaces three lines of text.
 4740    cx.update_editor(|editor, window, cx| {
 4741        let snapshot = editor.snapshot(window, cx);
 4742        let snapshot = &snapshot.buffer_snapshot;
 4743        let placement = BlockPlacement::Replace(
 4744            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4745        );
 4746        editor.insert_blocks(
 4747            [BlockProperties {
 4748                placement,
 4749                height: Some(4),
 4750                style: BlockStyle::Sticky,
 4751                render: Arc::new(|_| gpui::div().into_any_element()),
 4752                priority: 0,
 4753                render_in_minimap: true,
 4754            }],
 4755            None,
 4756            cx,
 4757        );
 4758    });
 4759
 4760    // Move down so that the cursor touches the block.
 4761    cx.update_editor(|editor, window, cx| {
 4762        editor.move_down(&Default::default(), window, cx);
 4763    });
 4764    cx.assert_editor_state(
 4765        &"
 4766            zero
 4767            «one
 4768            two
 4769            threeˇ»
 4770            four
 4771            five
 4772        "
 4773        .unindent(),
 4774    );
 4775
 4776    // Move down past the block.
 4777    cx.update_editor(|editor, window, cx| {
 4778        editor.move_down(&Default::default(), window, cx);
 4779    });
 4780    cx.assert_editor_state(
 4781        &"
 4782            zero
 4783            one
 4784            two
 4785            three
 4786            ˇfour
 4787            five
 4788        "
 4789        .unindent(),
 4790    );
 4791}
 4792
 4793#[gpui::test]
 4794fn test_transpose(cx: &mut TestAppContext) {
 4795    init_test(cx, |_| {});
 4796
 4797    _ = cx.add_window(|window, cx| {
 4798        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4799        editor.set_style(EditorStyle::default(), window, cx);
 4800        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4801        editor.transpose(&Default::default(), window, cx);
 4802        assert_eq!(editor.text(cx), "bac");
 4803        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4804
 4805        editor.transpose(&Default::default(), window, cx);
 4806        assert_eq!(editor.text(cx), "bca");
 4807        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4808
 4809        editor.transpose(&Default::default(), window, cx);
 4810        assert_eq!(editor.text(cx), "bac");
 4811        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4812
 4813        editor
 4814    });
 4815
 4816    _ = cx.add_window(|window, cx| {
 4817        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4818        editor.set_style(EditorStyle::default(), window, cx);
 4819        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4820        editor.transpose(&Default::default(), window, cx);
 4821        assert_eq!(editor.text(cx), "acb\nde");
 4822        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4823
 4824        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4825        editor.transpose(&Default::default(), window, cx);
 4826        assert_eq!(editor.text(cx), "acbd\ne");
 4827        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4828
 4829        editor.transpose(&Default::default(), window, cx);
 4830        assert_eq!(editor.text(cx), "acbde\n");
 4831        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4832
 4833        editor.transpose(&Default::default(), window, cx);
 4834        assert_eq!(editor.text(cx), "acbd\ne");
 4835        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4836
 4837        editor
 4838    });
 4839
 4840    _ = cx.add_window(|window, cx| {
 4841        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4842        editor.set_style(EditorStyle::default(), window, cx);
 4843        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4844        editor.transpose(&Default::default(), window, cx);
 4845        assert_eq!(editor.text(cx), "bacd\ne");
 4846        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4847
 4848        editor.transpose(&Default::default(), window, cx);
 4849        assert_eq!(editor.text(cx), "bcade\n");
 4850        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4851
 4852        editor.transpose(&Default::default(), window, cx);
 4853        assert_eq!(editor.text(cx), "bcda\ne");
 4854        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4855
 4856        editor.transpose(&Default::default(), window, cx);
 4857        assert_eq!(editor.text(cx), "bcade\n");
 4858        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4859
 4860        editor.transpose(&Default::default(), window, cx);
 4861        assert_eq!(editor.text(cx), "bcaed\n");
 4862        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4863
 4864        editor
 4865    });
 4866
 4867    _ = cx.add_window(|window, cx| {
 4868        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4869        editor.set_style(EditorStyle::default(), window, cx);
 4870        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4871        editor.transpose(&Default::default(), window, cx);
 4872        assert_eq!(editor.text(cx), "🏀🍐✋");
 4873        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4874
 4875        editor.transpose(&Default::default(), window, cx);
 4876        assert_eq!(editor.text(cx), "🏀✋🍐");
 4877        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4878
 4879        editor.transpose(&Default::default(), window, cx);
 4880        assert_eq!(editor.text(cx), "🏀🍐✋");
 4881        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4882
 4883        editor
 4884    });
 4885}
 4886
 4887#[gpui::test]
 4888async fn test_rewrap(cx: &mut TestAppContext) {
 4889    init_test(cx, |settings| {
 4890        settings.languages.extend([
 4891            (
 4892                "Markdown".into(),
 4893                LanguageSettingsContent {
 4894                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4895                    ..Default::default()
 4896                },
 4897            ),
 4898            (
 4899                "Plain Text".into(),
 4900                LanguageSettingsContent {
 4901                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4902                    ..Default::default()
 4903                },
 4904            ),
 4905        ])
 4906    });
 4907
 4908    let mut cx = EditorTestContext::new(cx).await;
 4909
 4910    let language_with_c_comments = Arc::new(Language::new(
 4911        LanguageConfig {
 4912            line_comments: vec!["// ".into()],
 4913            ..LanguageConfig::default()
 4914        },
 4915        None,
 4916    ));
 4917    let language_with_pound_comments = Arc::new(Language::new(
 4918        LanguageConfig {
 4919            line_comments: vec!["# ".into()],
 4920            ..LanguageConfig::default()
 4921        },
 4922        None,
 4923    ));
 4924    let markdown_language = Arc::new(Language::new(
 4925        LanguageConfig {
 4926            name: "Markdown".into(),
 4927            ..LanguageConfig::default()
 4928        },
 4929        None,
 4930    ));
 4931    let language_with_doc_comments = Arc::new(Language::new(
 4932        LanguageConfig {
 4933            line_comments: vec!["// ".into(), "/// ".into()],
 4934            ..LanguageConfig::default()
 4935        },
 4936        Some(tree_sitter_rust::LANGUAGE.into()),
 4937    ));
 4938
 4939    let plaintext_language = Arc::new(Language::new(
 4940        LanguageConfig {
 4941            name: "Plain Text".into(),
 4942            ..LanguageConfig::default()
 4943        },
 4944        None,
 4945    ));
 4946
 4947    assert_rewrap(
 4948        indoc! {"
 4949            // ˇ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.
 4950        "},
 4951        indoc! {"
 4952            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4953            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4954            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4955            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4956            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4957            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4958            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4959            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4960            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4961            // porttitor id. Aliquam id accumsan eros.
 4962        "},
 4963        language_with_c_comments.clone(),
 4964        &mut cx,
 4965    );
 4966
 4967    // Test that rewrapping works inside of a selection
 4968    assert_rewrap(
 4969        indoc! {"
 4970            «// 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.ˇ»
 4971        "},
 4972        indoc! {"
 4973            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4974            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4975            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4976            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4977            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4978            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4979            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4980            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4981            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4982            // porttitor id. Aliquam id accumsan eros.ˇ»
 4983        "},
 4984        language_with_c_comments.clone(),
 4985        &mut cx,
 4986    );
 4987
 4988    // Test that cursors that expand to the same region are collapsed.
 4989    assert_rewrap(
 4990        indoc! {"
 4991            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4992            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4993            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4994            // ˇ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.
 4995        "},
 4996        indoc! {"
 4997            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4998            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4999            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 5000            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5001            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5002            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5003            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5004            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5005            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5006            // porttitor id. Aliquam id accumsan eros.
 5007        "},
 5008        language_with_c_comments.clone(),
 5009        &mut cx,
 5010    );
 5011
 5012    // Test that non-contiguous selections are treated separately.
 5013    assert_rewrap(
 5014        indoc! {"
 5015            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5016            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5017            //
 5018            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5019            // ˇ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.
 5020        "},
 5021        indoc! {"
 5022            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5023            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5024            // auctor, eu lacinia sapien scelerisque.
 5025            //
 5026            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5027            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5028            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5029            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5030            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5031            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5032            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5033        "},
 5034        language_with_c_comments.clone(),
 5035        &mut cx,
 5036    );
 5037
 5038    // Test that different comment prefixes are supported.
 5039    assert_rewrap(
 5040        indoc! {"
 5041            # ˇ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.
 5042        "},
 5043        indoc! {"
 5044            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5045            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5046            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5047            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5048            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5049            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5050            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5051            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5052            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5053            # accumsan eros.
 5054        "},
 5055        language_with_pound_comments.clone(),
 5056        &mut cx,
 5057    );
 5058
 5059    // Test that rewrapping is ignored outside of comments in most languages.
 5060    assert_rewrap(
 5061        indoc! {"
 5062            /// Adds two numbers.
 5063            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5064            fn add(a: u32, b: u32) -> u32 {
 5065                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ˇ
 5066            }
 5067        "},
 5068        indoc! {"
 5069            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5070            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5071            fn add(a: u32, b: u32) -> u32 {
 5072                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ˇ
 5073            }
 5074        "},
 5075        language_with_doc_comments.clone(),
 5076        &mut cx,
 5077    );
 5078
 5079    // Test that rewrapping works in Markdown and Plain Text languages.
 5080    assert_rewrap(
 5081        indoc! {"
 5082            # Hello
 5083
 5084            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.
 5085        "},
 5086        indoc! {"
 5087            # Hello
 5088
 5089            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5090            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5091            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5092            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5093            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5094            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5095            Integer sit amet scelerisque nisi.
 5096        "},
 5097        markdown_language,
 5098        &mut cx,
 5099    );
 5100
 5101    assert_rewrap(
 5102        indoc! {"
 5103            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.
 5104        "},
 5105        indoc! {"
 5106            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5107            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5108            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5109            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5110            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5111            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5112            Integer sit amet scelerisque nisi.
 5113        "},
 5114        plaintext_language,
 5115        &mut cx,
 5116    );
 5117
 5118    // Test rewrapping unaligned comments in a selection.
 5119    assert_rewrap(
 5120        indoc! {"
 5121            fn foo() {
 5122                if true {
 5123            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5124            // Praesent semper egestas tellus id dignissim.ˇ»
 5125                    do_something();
 5126                } else {
 5127                    //
 5128                }
 5129            }
 5130        "},
 5131        indoc! {"
 5132            fn foo() {
 5133                if true {
 5134            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5135                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5136                    // egestas tellus id dignissim.ˇ»
 5137                    do_something();
 5138                } else {
 5139                    //
 5140                }
 5141            }
 5142        "},
 5143        language_with_doc_comments.clone(),
 5144        &mut cx,
 5145    );
 5146
 5147    assert_rewrap(
 5148        indoc! {"
 5149            fn foo() {
 5150                if true {
 5151            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5152            // Praesent semper egestas tellus id dignissim.»
 5153                    do_something();
 5154                } else {
 5155                    //
 5156                }
 5157
 5158            }
 5159        "},
 5160        indoc! {"
 5161            fn foo() {
 5162                if true {
 5163            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5164                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5165                    // egestas tellus id dignissim.»
 5166                    do_something();
 5167                } else {
 5168                    //
 5169                }
 5170
 5171            }
 5172        "},
 5173        language_with_doc_comments.clone(),
 5174        &mut cx,
 5175    );
 5176
 5177    #[track_caller]
 5178    fn assert_rewrap(
 5179        unwrapped_text: &str,
 5180        wrapped_text: &str,
 5181        language: Arc<Language>,
 5182        cx: &mut EditorTestContext,
 5183    ) {
 5184        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5185        cx.set_state(unwrapped_text);
 5186        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5187        cx.assert_editor_state(wrapped_text);
 5188    }
 5189}
 5190
 5191#[gpui::test]
 5192async fn test_hard_wrap(cx: &mut TestAppContext) {
 5193    init_test(cx, |_| {});
 5194    let mut cx = EditorTestContext::new(cx).await;
 5195
 5196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5197    cx.update_editor(|editor, _, cx| {
 5198        editor.set_hard_wrap(Some(14), cx);
 5199    });
 5200
 5201    cx.set_state(indoc!(
 5202        "
 5203        one two three ˇ
 5204        "
 5205    ));
 5206    cx.simulate_input("four");
 5207    cx.run_until_parked();
 5208
 5209    cx.assert_editor_state(indoc!(
 5210        "
 5211        one two three
 5212        fourˇ
 5213        "
 5214    ));
 5215
 5216    cx.update_editor(|editor, window, cx| {
 5217        editor.newline(&Default::default(), window, cx);
 5218    });
 5219    cx.run_until_parked();
 5220    cx.assert_editor_state(indoc!(
 5221        "
 5222        one two three
 5223        four
 5224        ˇ
 5225        "
 5226    ));
 5227
 5228    cx.simulate_input("five");
 5229    cx.run_until_parked();
 5230    cx.assert_editor_state(indoc!(
 5231        "
 5232        one two three
 5233        four
 5234        fiveˇ
 5235        "
 5236    ));
 5237
 5238    cx.update_editor(|editor, window, cx| {
 5239        editor.newline(&Default::default(), window, cx);
 5240    });
 5241    cx.run_until_parked();
 5242    cx.simulate_input("# ");
 5243    cx.run_until_parked();
 5244    cx.assert_editor_state(indoc!(
 5245        "
 5246        one two three
 5247        four
 5248        five
 5249        # ˇ
 5250        "
 5251    ));
 5252
 5253    cx.update_editor(|editor, window, cx| {
 5254        editor.newline(&Default::default(), window, cx);
 5255    });
 5256    cx.run_until_parked();
 5257    cx.assert_editor_state(indoc!(
 5258        "
 5259        one two three
 5260        four
 5261        five
 5262        #\x20
 5263 5264        "
 5265    ));
 5266
 5267    cx.simulate_input(" 6");
 5268    cx.run_until_parked();
 5269    cx.assert_editor_state(indoc!(
 5270        "
 5271        one two three
 5272        four
 5273        five
 5274        #
 5275        # 6ˇ
 5276        "
 5277    ));
 5278}
 5279
 5280#[gpui::test]
 5281async fn test_clipboard(cx: &mut TestAppContext) {
 5282    init_test(cx, |_| {});
 5283
 5284    let mut cx = EditorTestContext::new(cx).await;
 5285
 5286    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5287    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5288    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5289
 5290    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5291    cx.set_state("two ˇfour ˇsix ˇ");
 5292    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5293    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5294
 5295    // Paste again but with only two cursors. Since the number of cursors doesn't
 5296    // match the number of slices in the clipboard, the entire clipboard text
 5297    // is pasted at each cursor.
 5298    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5299    cx.update_editor(|e, window, cx| {
 5300        e.handle_input("( ", window, cx);
 5301        e.paste(&Paste, window, cx);
 5302        e.handle_input(") ", window, cx);
 5303    });
 5304    cx.assert_editor_state(
 5305        &([
 5306            "( one✅ ",
 5307            "three ",
 5308            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5309            "three ",
 5310            "five ) ˇ",
 5311        ]
 5312        .join("\n")),
 5313    );
 5314
 5315    // Cut with three selections, one of which is full-line.
 5316    cx.set_state(indoc! {"
 5317        1«2ˇ»3
 5318        4ˇ567
 5319        «8ˇ»9"});
 5320    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5321    cx.assert_editor_state(indoc! {"
 5322        1ˇ3
 5323        ˇ9"});
 5324
 5325    // Paste with three selections, noticing how the copied selection that was full-line
 5326    // gets inserted before the second cursor.
 5327    cx.set_state(indoc! {"
 5328        1ˇ3
 5329 5330        «oˇ»ne"});
 5331    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5332    cx.assert_editor_state(indoc! {"
 5333        12ˇ3
 5334        4567
 5335 5336        8ˇne"});
 5337
 5338    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5339    cx.set_state(indoc! {"
 5340        The quick brown
 5341        fox juˇmps over
 5342        the lazy dog"});
 5343    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5344    assert_eq!(
 5345        cx.read_from_clipboard()
 5346            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5347        Some("fox jumps over\n".to_string())
 5348    );
 5349
 5350    // Paste with three selections, noticing how the copied full-line selection is inserted
 5351    // before the empty selections but replaces the selection that is non-empty.
 5352    cx.set_state(indoc! {"
 5353        Tˇhe quick brown
 5354        «foˇ»x jumps over
 5355        tˇhe lazy dog"});
 5356    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5357    cx.assert_editor_state(indoc! {"
 5358        fox jumps over
 5359        Tˇhe quick brown
 5360        fox jumps over
 5361        ˇx jumps over
 5362        fox jumps over
 5363        tˇhe lazy dog"});
 5364}
 5365
 5366#[gpui::test]
 5367async fn test_copy_trim(cx: &mut TestAppContext) {
 5368    init_test(cx, |_| {});
 5369
 5370    let mut cx = EditorTestContext::new(cx).await;
 5371    cx.set_state(
 5372        r#"            «for selection in selections.iter() {
 5373            let mut start = selection.start;
 5374            let mut end = selection.end;
 5375            let is_entire_line = selection.is_empty();
 5376            if is_entire_line {
 5377                start = Point::new(start.row, 0);ˇ»
 5378                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5379            }
 5380        "#,
 5381    );
 5382    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5383    assert_eq!(
 5384        cx.read_from_clipboard()
 5385            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5386        Some(
 5387            "for selection in selections.iter() {
 5388            let mut start = selection.start;
 5389            let mut end = selection.end;
 5390            let is_entire_line = selection.is_empty();
 5391            if is_entire_line {
 5392                start = Point::new(start.row, 0);"
 5393                .to_string()
 5394        ),
 5395        "Regular copying preserves all indentation selected",
 5396    );
 5397    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5398    assert_eq!(
 5399        cx.read_from_clipboard()
 5400            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5401        Some(
 5402            "for selection in selections.iter() {
 5403let mut start = selection.start;
 5404let mut end = selection.end;
 5405let is_entire_line = selection.is_empty();
 5406if is_entire_line {
 5407    start = Point::new(start.row, 0);"
 5408                .to_string()
 5409        ),
 5410        "Copying with stripping should strip all leading whitespaces"
 5411    );
 5412
 5413    cx.set_state(
 5414        r#"       «     for selection in selections.iter() {
 5415            let mut start = selection.start;
 5416            let mut end = selection.end;
 5417            let is_entire_line = selection.is_empty();
 5418            if is_entire_line {
 5419                start = Point::new(start.row, 0);ˇ»
 5420                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5421            }
 5422        "#,
 5423    );
 5424    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5425    assert_eq!(
 5426        cx.read_from_clipboard()
 5427            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5428        Some(
 5429            "     for selection in selections.iter() {
 5430            let mut start = selection.start;
 5431            let mut end = selection.end;
 5432            let is_entire_line = selection.is_empty();
 5433            if is_entire_line {
 5434                start = Point::new(start.row, 0);"
 5435                .to_string()
 5436        ),
 5437        "Regular copying preserves all indentation selected",
 5438    );
 5439    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5440    assert_eq!(
 5441        cx.read_from_clipboard()
 5442            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5443        Some(
 5444            "for selection in selections.iter() {
 5445let mut start = selection.start;
 5446let mut end = selection.end;
 5447let is_entire_line = selection.is_empty();
 5448if is_entire_line {
 5449    start = Point::new(start.row, 0);"
 5450                .to_string()
 5451        ),
 5452        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5453    );
 5454
 5455    cx.set_state(
 5456        r#"       «ˇ     for selection in selections.iter() {
 5457            let mut start = selection.start;
 5458            let mut end = selection.end;
 5459            let is_entire_line = selection.is_empty();
 5460            if is_entire_line {
 5461                start = Point::new(start.row, 0);»
 5462                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5463            }
 5464        "#,
 5465    );
 5466    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5467    assert_eq!(
 5468        cx.read_from_clipboard()
 5469            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5470        Some(
 5471            "     for selection in selections.iter() {
 5472            let mut start = selection.start;
 5473            let mut end = selection.end;
 5474            let is_entire_line = selection.is_empty();
 5475            if is_entire_line {
 5476                start = Point::new(start.row, 0);"
 5477                .to_string()
 5478        ),
 5479        "Regular copying for reverse selection works the same",
 5480    );
 5481    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5482    assert_eq!(
 5483        cx.read_from_clipboard()
 5484            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5485        Some(
 5486            "for selection in selections.iter() {
 5487let mut start = selection.start;
 5488let mut end = selection.end;
 5489let is_entire_line = selection.is_empty();
 5490if is_entire_line {
 5491    start = Point::new(start.row, 0);"
 5492                .to_string()
 5493        ),
 5494        "Copying with stripping for reverse selection works the same"
 5495    );
 5496
 5497    cx.set_state(
 5498        r#"            for selection «in selections.iter() {
 5499            let mut start = selection.start;
 5500            let mut end = selection.end;
 5501            let is_entire_line = selection.is_empty();
 5502            if is_entire_line {
 5503                start = Point::new(start.row, 0);ˇ»
 5504                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5505            }
 5506        "#,
 5507    );
 5508    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5509    assert_eq!(
 5510        cx.read_from_clipboard()
 5511            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5512        Some(
 5513            "in selections.iter() {
 5514            let mut start = selection.start;
 5515            let mut end = selection.end;
 5516            let is_entire_line = selection.is_empty();
 5517            if is_entire_line {
 5518                start = Point::new(start.row, 0);"
 5519                .to_string()
 5520        ),
 5521        "When selecting past the indent, the copying works as usual",
 5522    );
 5523    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5524    assert_eq!(
 5525        cx.read_from_clipboard()
 5526            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5527        Some(
 5528            "in selections.iter() {
 5529            let mut start = selection.start;
 5530            let mut end = selection.end;
 5531            let is_entire_line = selection.is_empty();
 5532            if is_entire_line {
 5533                start = Point::new(start.row, 0);"
 5534                .to_string()
 5535        ),
 5536        "When selecting past the indent, nothing is trimmed"
 5537    );
 5538
 5539    cx.set_state(
 5540        r#"            «for selection in selections.iter() {
 5541            let mut start = selection.start;
 5542
 5543            let mut end = selection.end;
 5544            let is_entire_line = selection.is_empty();
 5545            if is_entire_line {
 5546                start = Point::new(start.row, 0);
 5547ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5548            }
 5549        "#,
 5550    );
 5551    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5552    assert_eq!(
 5553        cx.read_from_clipboard()
 5554            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5555        Some(
 5556            "for selection in selections.iter() {
 5557let mut start = selection.start;
 5558
 5559let mut end = selection.end;
 5560let is_entire_line = selection.is_empty();
 5561if is_entire_line {
 5562    start = Point::new(start.row, 0);
 5563"
 5564            .to_string()
 5565        ),
 5566        "Copying with stripping should ignore empty lines"
 5567    );
 5568}
 5569
 5570#[gpui::test]
 5571async fn test_paste_multiline(cx: &mut TestAppContext) {
 5572    init_test(cx, |_| {});
 5573
 5574    let mut cx = EditorTestContext::new(cx).await;
 5575    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5576
 5577    // Cut an indented block, without the leading whitespace.
 5578    cx.set_state(indoc! {"
 5579        const a: B = (
 5580            c(),
 5581            «d(
 5582                e,
 5583                f
 5584            )ˇ»
 5585        );
 5586    "});
 5587    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5588    cx.assert_editor_state(indoc! {"
 5589        const a: B = (
 5590            c(),
 5591            ˇ
 5592        );
 5593    "});
 5594
 5595    // Paste it at the same position.
 5596    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5597    cx.assert_editor_state(indoc! {"
 5598        const a: B = (
 5599            c(),
 5600            d(
 5601                e,
 5602                f
 5603 5604        );
 5605    "});
 5606
 5607    // Paste it at a line with a lower indent level.
 5608    cx.set_state(indoc! {"
 5609        ˇ
 5610        const a: B = (
 5611            c(),
 5612        );
 5613    "});
 5614    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5615    cx.assert_editor_state(indoc! {"
 5616        d(
 5617            e,
 5618            f
 5619 5620        const a: B = (
 5621            c(),
 5622        );
 5623    "});
 5624
 5625    // Cut an indented block, with the leading whitespace.
 5626    cx.set_state(indoc! {"
 5627        const a: B = (
 5628            c(),
 5629        «    d(
 5630                e,
 5631                f
 5632            )
 5633        ˇ»);
 5634    "});
 5635    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5636    cx.assert_editor_state(indoc! {"
 5637        const a: B = (
 5638            c(),
 5639        ˇ);
 5640    "});
 5641
 5642    // Paste it at the same position.
 5643    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5644    cx.assert_editor_state(indoc! {"
 5645        const a: B = (
 5646            c(),
 5647            d(
 5648                e,
 5649                f
 5650            )
 5651        ˇ);
 5652    "});
 5653
 5654    // Paste it at a line with a higher indent level.
 5655    cx.set_state(indoc! {"
 5656        const a: B = (
 5657            c(),
 5658            d(
 5659                e,
 5660 5661            )
 5662        );
 5663    "});
 5664    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5665    cx.assert_editor_state(indoc! {"
 5666        const a: B = (
 5667            c(),
 5668            d(
 5669                e,
 5670                f    d(
 5671                    e,
 5672                    f
 5673                )
 5674        ˇ
 5675            )
 5676        );
 5677    "});
 5678
 5679    // Copy an indented block, starting mid-line
 5680    cx.set_state(indoc! {"
 5681        const a: B = (
 5682            c(),
 5683            somethin«g(
 5684                e,
 5685                f
 5686            )ˇ»
 5687        );
 5688    "});
 5689    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5690
 5691    // Paste it on a line with a lower indent level
 5692    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5693    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5694    cx.assert_editor_state(indoc! {"
 5695        const a: B = (
 5696            c(),
 5697            something(
 5698                e,
 5699                f
 5700            )
 5701        );
 5702        g(
 5703            e,
 5704            f
 5705"});
 5706}
 5707
 5708#[gpui::test]
 5709async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5710    init_test(cx, |_| {});
 5711
 5712    cx.write_to_clipboard(ClipboardItem::new_string(
 5713        "    d(\n        e\n    );\n".into(),
 5714    ));
 5715
 5716    let mut cx = EditorTestContext::new(cx).await;
 5717    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5718
 5719    cx.set_state(indoc! {"
 5720        fn a() {
 5721            b();
 5722            if c() {
 5723                ˇ
 5724            }
 5725        }
 5726    "});
 5727
 5728    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5729    cx.assert_editor_state(indoc! {"
 5730        fn a() {
 5731            b();
 5732            if c() {
 5733                d(
 5734                    e
 5735                );
 5736        ˇ
 5737            }
 5738        }
 5739    "});
 5740
 5741    cx.set_state(indoc! {"
 5742        fn a() {
 5743            b();
 5744            ˇ
 5745        }
 5746    "});
 5747
 5748    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5749    cx.assert_editor_state(indoc! {"
 5750        fn a() {
 5751            b();
 5752            d(
 5753                e
 5754            );
 5755        ˇ
 5756        }
 5757    "});
 5758}
 5759
 5760#[gpui::test]
 5761fn test_select_all(cx: &mut TestAppContext) {
 5762    init_test(cx, |_| {});
 5763
 5764    let editor = cx.add_window(|window, cx| {
 5765        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5766        build_editor(buffer, window, cx)
 5767    });
 5768    _ = editor.update(cx, |editor, window, cx| {
 5769        editor.select_all(&SelectAll, window, cx);
 5770        assert_eq!(
 5771            editor.selections.display_ranges(cx),
 5772            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5773        );
 5774    });
 5775}
 5776
 5777#[gpui::test]
 5778fn test_select_line(cx: &mut TestAppContext) {
 5779    init_test(cx, |_| {});
 5780
 5781    let editor = cx.add_window(|window, cx| {
 5782        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5783        build_editor(buffer, window, cx)
 5784    });
 5785    _ = editor.update(cx, |editor, window, cx| {
 5786        editor.change_selections(None, window, cx, |s| {
 5787            s.select_display_ranges([
 5788                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5790                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5791                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5792            ])
 5793        });
 5794        editor.select_line(&SelectLine, window, cx);
 5795        assert_eq!(
 5796            editor.selections.display_ranges(cx),
 5797            vec![
 5798                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5799                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5800            ]
 5801        );
 5802    });
 5803
 5804    _ = editor.update(cx, |editor, window, cx| {
 5805        editor.select_line(&SelectLine, window, cx);
 5806        assert_eq!(
 5807            editor.selections.display_ranges(cx),
 5808            vec![
 5809                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5810                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5811            ]
 5812        );
 5813    });
 5814
 5815    _ = editor.update(cx, |editor, window, cx| {
 5816        editor.select_line(&SelectLine, window, cx);
 5817        assert_eq!(
 5818            editor.selections.display_ranges(cx),
 5819            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5820        );
 5821    });
 5822}
 5823
 5824#[gpui::test]
 5825async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5826    init_test(cx, |_| {});
 5827    let mut cx = EditorTestContext::new(cx).await;
 5828
 5829    #[track_caller]
 5830    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5831        cx.set_state(initial_state);
 5832        cx.update_editor(|e, window, cx| {
 5833            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5834        });
 5835        cx.assert_editor_state(expected_state);
 5836    }
 5837
 5838    // Selection starts and ends at the middle of lines, left-to-right
 5839    test(
 5840        &mut cx,
 5841        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5842        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5843    );
 5844    // Same thing, right-to-left
 5845    test(
 5846        &mut cx,
 5847        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5848        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5849    );
 5850
 5851    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5852    test(
 5853        &mut cx,
 5854        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5855        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5856    );
 5857    // Same thing, right-to-left
 5858    test(
 5859        &mut cx,
 5860        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5861        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5862    );
 5863
 5864    // Whole buffer, left-to-right, last line ends with newline
 5865    test(
 5866        &mut cx,
 5867        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5868        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5869    );
 5870    // Same thing, right-to-left
 5871    test(
 5872        &mut cx,
 5873        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5874        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5875    );
 5876
 5877    // Starts at the end of a line, ends at the start of another
 5878    test(
 5879        &mut cx,
 5880        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5881        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5882    );
 5883}
 5884
 5885#[gpui::test]
 5886async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5887    init_test(cx, |_| {});
 5888
 5889    let editor = cx.add_window(|window, cx| {
 5890        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5891        build_editor(buffer, window, cx)
 5892    });
 5893
 5894    // setup
 5895    _ = editor.update(cx, |editor, window, cx| {
 5896        editor.fold_creases(
 5897            vec![
 5898                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5899                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5900                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5901            ],
 5902            true,
 5903            window,
 5904            cx,
 5905        );
 5906        assert_eq!(
 5907            editor.display_text(cx),
 5908            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5909        );
 5910    });
 5911
 5912    _ = editor.update(cx, |editor, window, cx| {
 5913        editor.change_selections(None, window, cx, |s| {
 5914            s.select_display_ranges([
 5915                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5916                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5917                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5918                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5919            ])
 5920        });
 5921        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5922        assert_eq!(
 5923            editor.display_text(cx),
 5924            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5925        );
 5926    });
 5927    EditorTestContext::for_editor(editor, cx)
 5928        .await
 5929        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5930
 5931    _ = editor.update(cx, |editor, window, cx| {
 5932        editor.change_selections(None, window, cx, |s| {
 5933            s.select_display_ranges([
 5934                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5935            ])
 5936        });
 5937        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5938        assert_eq!(
 5939            editor.display_text(cx),
 5940            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5941        );
 5942        assert_eq!(
 5943            editor.selections.display_ranges(cx),
 5944            [
 5945                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5946                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5947                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5948                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5949                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5950                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5951                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5952            ]
 5953        );
 5954    });
 5955    EditorTestContext::for_editor(editor, cx)
 5956        .await
 5957        .assert_editor_state(
 5958            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5959        );
 5960}
 5961
 5962#[gpui::test]
 5963async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5964    init_test(cx, |_| {});
 5965
 5966    let mut cx = EditorTestContext::new(cx).await;
 5967
 5968    cx.set_state(indoc!(
 5969        r#"abc
 5970           defˇghi
 5971
 5972           jk
 5973           nlmo
 5974           "#
 5975    ));
 5976
 5977    cx.update_editor(|editor, window, cx| {
 5978        editor.add_selection_above(&Default::default(), window, cx);
 5979    });
 5980
 5981    cx.assert_editor_state(indoc!(
 5982        r#"abcˇ
 5983           defˇghi
 5984
 5985           jk
 5986           nlmo
 5987           "#
 5988    ));
 5989
 5990    cx.update_editor(|editor, window, cx| {
 5991        editor.add_selection_above(&Default::default(), window, cx);
 5992    });
 5993
 5994    cx.assert_editor_state(indoc!(
 5995        r#"abcˇ
 5996            defˇghi
 5997
 5998            jk
 5999            nlmo
 6000            "#
 6001    ));
 6002
 6003    cx.update_editor(|editor, window, cx| {
 6004        editor.add_selection_below(&Default::default(), window, cx);
 6005    });
 6006
 6007    cx.assert_editor_state(indoc!(
 6008        r#"abc
 6009           defˇghi
 6010
 6011           jk
 6012           nlmo
 6013           "#
 6014    ));
 6015
 6016    cx.update_editor(|editor, window, cx| {
 6017        editor.undo_selection(&Default::default(), window, cx);
 6018    });
 6019
 6020    cx.assert_editor_state(indoc!(
 6021        r#"abcˇ
 6022           defˇghi
 6023
 6024           jk
 6025           nlmo
 6026           "#
 6027    ));
 6028
 6029    cx.update_editor(|editor, window, cx| {
 6030        editor.redo_selection(&Default::default(), window, cx);
 6031    });
 6032
 6033    cx.assert_editor_state(indoc!(
 6034        r#"abc
 6035           defˇghi
 6036
 6037           jk
 6038           nlmo
 6039           "#
 6040    ));
 6041
 6042    cx.update_editor(|editor, window, cx| {
 6043        editor.add_selection_below(&Default::default(), window, cx);
 6044    });
 6045
 6046    cx.assert_editor_state(indoc!(
 6047        r#"abc
 6048           defˇghi
 6049           ˇ
 6050           jk
 6051           nlmo
 6052           "#
 6053    ));
 6054
 6055    cx.update_editor(|editor, window, cx| {
 6056        editor.add_selection_below(&Default::default(), window, cx);
 6057    });
 6058
 6059    cx.assert_editor_state(indoc!(
 6060        r#"abc
 6061           defˇghi
 6062           ˇ
 6063           jkˇ
 6064           nlmo
 6065           "#
 6066    ));
 6067
 6068    cx.update_editor(|editor, window, cx| {
 6069        editor.add_selection_below(&Default::default(), window, cx);
 6070    });
 6071
 6072    cx.assert_editor_state(indoc!(
 6073        r#"abc
 6074           defˇghi
 6075           ˇ
 6076           jkˇ
 6077           nlmˇo
 6078           "#
 6079    ));
 6080
 6081    cx.update_editor(|editor, window, cx| {
 6082        editor.add_selection_below(&Default::default(), window, cx);
 6083    });
 6084
 6085    cx.assert_editor_state(indoc!(
 6086        r#"abc
 6087           defˇghi
 6088           ˇ
 6089           jkˇ
 6090           nlmˇo
 6091           ˇ"#
 6092    ));
 6093
 6094    // change selections
 6095    cx.set_state(indoc!(
 6096        r#"abc
 6097           def«ˇg»hi
 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«ˇg»hi
 6111
 6112           jk
 6113           nlm«ˇo»
 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«ˇg»hi
 6124
 6125           jk
 6126           nlm«ˇo»
 6127           "#
 6128    ));
 6129
 6130    cx.update_editor(|editor, window, cx| {
 6131        editor.add_selection_above(&Default::default(), window, cx);
 6132    });
 6133
 6134    cx.assert_editor_state(indoc!(
 6135        r#"abc
 6136           def«ˇg»hi
 6137
 6138           jk
 6139           nlmo
 6140           "#
 6141    ));
 6142
 6143    cx.update_editor(|editor, window, cx| {
 6144        editor.add_selection_above(&Default::default(), window, cx);
 6145    });
 6146
 6147    cx.assert_editor_state(indoc!(
 6148        r#"abc
 6149           def«ˇg»hi
 6150
 6151           jk
 6152           nlmo
 6153           "#
 6154    ));
 6155
 6156    // Change selections again
 6157    cx.set_state(indoc!(
 6158        r#"a«bc
 6159           defgˇ»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#"a«bcˇ»
 6172           d«efgˇ»hi
 6173
 6174           j«kˇ»
 6175           nlmo
 6176           "#
 6177    ));
 6178
 6179    cx.update_editor(|editor, window, cx| {
 6180        editor.add_selection_below(&Default::default(), window, cx);
 6181    });
 6182    cx.assert_editor_state(indoc!(
 6183        r#"a«bcˇ»
 6184           d«efgˇ»hi
 6185
 6186           j«kˇ»
 6187           n«lmoˇ»
 6188           "#
 6189    ));
 6190    cx.update_editor(|editor, window, cx| {
 6191        editor.add_selection_above(&Default::default(), window, cx);
 6192    });
 6193
 6194    cx.assert_editor_state(indoc!(
 6195        r#"a«bcˇ»
 6196           d«efgˇ»hi
 6197
 6198           j«kˇ»
 6199           nlmo
 6200           "#
 6201    ));
 6202
 6203    // Change selections again
 6204    cx.set_state(indoc!(
 6205        r#"abc
 6206           d«ˇefghi
 6207
 6208           jk
 6209           nlm»o
 6210           "#
 6211    ));
 6212
 6213    cx.update_editor(|editor, window, cx| {
 6214        editor.add_selection_above(&Default::default(), window, cx);
 6215    });
 6216
 6217    cx.assert_editor_state(indoc!(
 6218        r#"a«ˇbc»
 6219           d«ˇef»ghi
 6220
 6221           j«ˇk»
 6222           n«ˇlm»o
 6223           "#
 6224    ));
 6225
 6226    cx.update_editor(|editor, window, cx| {
 6227        editor.add_selection_below(&Default::default(), window, cx);
 6228    });
 6229
 6230    cx.assert_editor_state(indoc!(
 6231        r#"abc
 6232           d«ˇef»ghi
 6233
 6234           j«ˇk»
 6235           n«ˇlm»o
 6236           "#
 6237    ));
 6238}
 6239
 6240#[gpui::test]
 6241async fn test_select_next(cx: &mut TestAppContext) {
 6242    init_test(cx, |_| {});
 6243
 6244    let mut cx = EditorTestContext::new(cx).await;
 6245    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6246
 6247    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6248        .unwrap();
 6249    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6250
 6251    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6252        .unwrap();
 6253    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6254
 6255    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6256    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6257
 6258    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6259    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6260
 6261    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6262        .unwrap();
 6263    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6264
 6265    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6266        .unwrap();
 6267    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6268
 6269    // Test selection direction should be preserved
 6270    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6271
 6272    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6273        .unwrap();
 6274    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6275}
 6276
 6277#[gpui::test]
 6278async fn test_select_all_matches(cx: &mut TestAppContext) {
 6279    init_test(cx, |_| {});
 6280
 6281    let mut cx = EditorTestContext::new(cx).await;
 6282
 6283    // Test caret-only selections
 6284    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6285    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6286        .unwrap();
 6287    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6288
 6289    // Test left-to-right selections
 6290    cx.set_state("abc\n«abcˇ»\nabc");
 6291    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6292        .unwrap();
 6293    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6294
 6295    // Test right-to-left selections
 6296    cx.set_state("abc\n«ˇabc»\nabc");
 6297    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6298        .unwrap();
 6299    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6300
 6301    // Test selecting whitespace with caret selection
 6302    cx.set_state("abc\nˇ   abc\nabc");
 6303    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6304        .unwrap();
 6305    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6306
 6307    // Test selecting whitespace with left-to-right selection
 6308    cx.set_state("abc\n«ˇ  »abc\nabc");
 6309    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6310        .unwrap();
 6311    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6312
 6313    // Test no matches with right-to-left selection
 6314    cx.set_state("abc\n«  ˇ»abc\nabc");
 6315    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6316        .unwrap();
 6317    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6318}
 6319
 6320#[gpui::test]
 6321async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6322    init_test(cx, |_| {});
 6323
 6324    let mut cx = EditorTestContext::new(cx).await;
 6325
 6326    let large_body_1 = "\nd".repeat(200);
 6327    let large_body_2 = "\ne".repeat(200);
 6328
 6329    cx.set_state(&format!(
 6330        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6331    ));
 6332    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6333        let scroll_position = editor.scroll_position(cx);
 6334        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6335        scroll_position
 6336    });
 6337
 6338    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6339        .unwrap();
 6340    cx.assert_editor_state(&format!(
 6341        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6342    ));
 6343    let scroll_position_after_selection =
 6344        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6345    assert_eq!(
 6346        initial_scroll_position, scroll_position_after_selection,
 6347        "Scroll position should not change after selecting all matches"
 6348    );
 6349}
 6350
 6351#[gpui::test]
 6352async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6353    init_test(cx, |_| {});
 6354
 6355    let mut cx = EditorLspTestContext::new_rust(
 6356        lsp::ServerCapabilities {
 6357            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6358            ..Default::default()
 6359        },
 6360        cx,
 6361    )
 6362    .await;
 6363
 6364    cx.set_state(indoc! {"
 6365        line 1
 6366        line 2
 6367        linˇe 3
 6368        line 4
 6369        line 5
 6370    "});
 6371
 6372    // Make an edit
 6373    cx.update_editor(|editor, window, cx| {
 6374        editor.handle_input("X", window, cx);
 6375    });
 6376
 6377    // Move cursor to a different position
 6378    cx.update_editor(|editor, window, cx| {
 6379        editor.change_selections(None, window, cx, |s| {
 6380            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6381        });
 6382    });
 6383
 6384    cx.assert_editor_state(indoc! {"
 6385        line 1
 6386        line 2
 6387        linXe 3
 6388        line 4
 6389        liˇne 5
 6390    "});
 6391
 6392    cx.lsp
 6393        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6394            Ok(Some(vec![lsp::TextEdit::new(
 6395                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6396                "PREFIX ".to_string(),
 6397            )]))
 6398        });
 6399
 6400    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6401        .unwrap()
 6402        .await
 6403        .unwrap();
 6404
 6405    cx.assert_editor_state(indoc! {"
 6406        PREFIX line 1
 6407        line 2
 6408        linXe 3
 6409        line 4
 6410        liˇne 5
 6411    "});
 6412
 6413    // Undo formatting
 6414    cx.update_editor(|editor, window, cx| {
 6415        editor.undo(&Default::default(), window, cx);
 6416    });
 6417
 6418    // Verify cursor moved back to position after edit
 6419    cx.assert_editor_state(indoc! {"
 6420        line 1
 6421        line 2
 6422        linXˇe 3
 6423        line 4
 6424        line 5
 6425    "});
 6426}
 6427
 6428#[gpui::test]
 6429async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6430    init_test(cx, |_| {});
 6431
 6432    let mut cx = EditorTestContext::new(cx).await;
 6433
 6434    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6435    cx.update_editor(|editor, window, cx| {
 6436        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6437    });
 6438
 6439    cx.set_state(indoc! {"
 6440        line 1
 6441        line 2
 6442        linˇe 3
 6443        line 4
 6444        line 5
 6445        line 6
 6446        line 7
 6447        line 8
 6448        line 9
 6449        line 10
 6450    "});
 6451
 6452    let snapshot = cx.buffer_snapshot();
 6453    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6454
 6455    cx.update(|_, cx| {
 6456        provider.update(cx, |provider, _| {
 6457            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6458                id: None,
 6459                edits: vec![(edit_position..edit_position, "X".into())],
 6460                edit_preview: None,
 6461            }))
 6462        })
 6463    });
 6464
 6465    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6466    cx.update_editor(|editor, window, cx| {
 6467        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6468    });
 6469
 6470    cx.assert_editor_state(indoc! {"
 6471        line 1
 6472        line 2
 6473        lineXˇ 3
 6474        line 4
 6475        line 5
 6476        line 6
 6477        line 7
 6478        line 8
 6479        line 9
 6480        line 10
 6481    "});
 6482
 6483    cx.update_editor(|editor, window, cx| {
 6484        editor.change_selections(None, window, cx, |s| {
 6485            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6486        });
 6487    });
 6488
 6489    cx.assert_editor_state(indoc! {"
 6490        line 1
 6491        line 2
 6492        lineX 3
 6493        line 4
 6494        line 5
 6495        line 6
 6496        line 7
 6497        line 8
 6498        line 9
 6499        liˇne 10
 6500    "});
 6501
 6502    cx.update_editor(|editor, window, cx| {
 6503        editor.undo(&Default::default(), window, cx);
 6504    });
 6505
 6506    cx.assert_editor_state(indoc! {"
 6507        line 1
 6508        line 2
 6509        lineˇ 3
 6510        line 4
 6511        line 5
 6512        line 6
 6513        line 7
 6514        line 8
 6515        line 9
 6516        line 10
 6517    "});
 6518}
 6519
 6520#[gpui::test]
 6521async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6522    init_test(cx, |_| {});
 6523
 6524    let mut cx = EditorTestContext::new(cx).await;
 6525    cx.set_state(
 6526        r#"let foo = 2;
 6527lˇet foo = 2;
 6528let fooˇ = 2;
 6529let foo = 2;
 6530let foo = ˇ2;"#,
 6531    );
 6532
 6533    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6534        .unwrap();
 6535    cx.assert_editor_state(
 6536        r#"let foo = 2;
 6537«letˇ» foo = 2;
 6538let «fooˇ» = 2;
 6539let foo = 2;
 6540let foo = «2ˇ»;"#,
 6541    );
 6542
 6543    // noop for multiple selections with different contents
 6544    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6545        .unwrap();
 6546    cx.assert_editor_state(
 6547        r#"let foo = 2;
 6548«letˇ» foo = 2;
 6549let «fooˇ» = 2;
 6550let foo = 2;
 6551let foo = «2ˇ»;"#,
 6552    );
 6553
 6554    // Test last selection direction should be preserved
 6555    cx.set_state(
 6556        r#"let foo = 2;
 6557let foo = 2;
 6558let «fooˇ» = 2;
 6559let «ˇfoo» = 2;
 6560let foo = 2;"#,
 6561    );
 6562
 6563    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6564        .unwrap();
 6565    cx.assert_editor_state(
 6566        r#"let foo = 2;
 6567let foo = 2;
 6568let «fooˇ» = 2;
 6569let «ˇfoo» = 2;
 6570let «ˇfoo» = 2;"#,
 6571    );
 6572}
 6573
 6574#[gpui::test]
 6575async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6576    init_test(cx, |_| {});
 6577
 6578    let mut cx =
 6579        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6580
 6581    cx.assert_editor_state(indoc! {"
 6582        ˇbbb
 6583        ccc
 6584
 6585        bbb
 6586        ccc
 6587        "});
 6588    cx.dispatch_action(SelectPrevious::default());
 6589    cx.assert_editor_state(indoc! {"
 6590                «bbbˇ»
 6591                ccc
 6592
 6593                bbb
 6594                ccc
 6595                "});
 6596    cx.dispatch_action(SelectPrevious::default());
 6597    cx.assert_editor_state(indoc! {"
 6598                «bbbˇ»
 6599                ccc
 6600
 6601                «bbbˇ»
 6602                ccc
 6603                "});
 6604}
 6605
 6606#[gpui::test]
 6607async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6608    init_test(cx, |_| {});
 6609
 6610    let mut cx = EditorTestContext::new(cx).await;
 6611    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6612
 6613    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6614        .unwrap();
 6615    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6616
 6617    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6618        .unwrap();
 6619    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6620
 6621    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6622    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6623
 6624    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6625    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6626
 6627    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6628        .unwrap();
 6629    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6630
 6631    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6632        .unwrap();
 6633    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6634}
 6635
 6636#[gpui::test]
 6637async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6638    init_test(cx, |_| {});
 6639
 6640    let mut cx = EditorTestContext::new(cx).await;
 6641    cx.set_state("");
 6642
 6643    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6644        .unwrap();
 6645    cx.assert_editor_state("«aˇ»");
 6646    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6647        .unwrap();
 6648    cx.assert_editor_state("«aˇ»");
 6649}
 6650
 6651#[gpui::test]
 6652async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6653    init_test(cx, |_| {});
 6654
 6655    let mut cx = EditorTestContext::new(cx).await;
 6656    cx.set_state(
 6657        r#"let foo = 2;
 6658lˇet foo = 2;
 6659let fooˇ = 2;
 6660let foo = 2;
 6661let foo = ˇ2;"#,
 6662    );
 6663
 6664    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6665        .unwrap();
 6666    cx.assert_editor_state(
 6667        r#"let foo = 2;
 6668«letˇ» foo = 2;
 6669let «fooˇ» = 2;
 6670let foo = 2;
 6671let foo = «2ˇ»;"#,
 6672    );
 6673
 6674    // noop for multiple selections with different contents
 6675    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6676        .unwrap();
 6677    cx.assert_editor_state(
 6678        r#"let foo = 2;
 6679«letˇ» foo = 2;
 6680let «fooˇ» = 2;
 6681let foo = 2;
 6682let foo = «2ˇ»;"#,
 6683    );
 6684}
 6685
 6686#[gpui::test]
 6687async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6688    init_test(cx, |_| {});
 6689
 6690    let mut cx = EditorTestContext::new(cx).await;
 6691    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6692
 6693    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6694        .unwrap();
 6695    // selection direction is preserved
 6696    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6697
 6698    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6699        .unwrap();
 6700    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6701
 6702    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6703    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6704
 6705    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6706    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6707
 6708    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6709        .unwrap();
 6710    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6711
 6712    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6713        .unwrap();
 6714    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6715}
 6716
 6717#[gpui::test]
 6718async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6719    init_test(cx, |_| {});
 6720
 6721    let language = Arc::new(Language::new(
 6722        LanguageConfig::default(),
 6723        Some(tree_sitter_rust::LANGUAGE.into()),
 6724    ));
 6725
 6726    let text = r#"
 6727        use mod1::mod2::{mod3, mod4};
 6728
 6729        fn fn_1(param1: bool, param2: &str) {
 6730            let var1 = "text";
 6731        }
 6732    "#
 6733    .unindent();
 6734
 6735    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6736    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6737    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6738
 6739    editor
 6740        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6741        .await;
 6742
 6743    editor.update_in(cx, |editor, window, cx| {
 6744        editor.change_selections(None, window, cx, |s| {
 6745            s.select_display_ranges([
 6746                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6747                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6748                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6749            ]);
 6750        });
 6751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6752    });
 6753    editor.update(cx, |editor, cx| {
 6754        assert_text_with_selections(
 6755            editor,
 6756            indoc! {r#"
 6757                use mod1::mod2::{mod3, «mod4ˇ»};
 6758
 6759                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6760                    let var1 = "«ˇtext»";
 6761                }
 6762            "#},
 6763            cx,
 6764        );
 6765    });
 6766
 6767    editor.update_in(cx, |editor, window, cx| {
 6768        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6769    });
 6770    editor.update(cx, |editor, cx| {
 6771        assert_text_with_selections(
 6772            editor,
 6773            indoc! {r#"
 6774                use mod1::mod2::«{mod3, mod4}ˇ»;
 6775
 6776                «ˇfn fn_1(param1: bool, param2: &str) {
 6777                    let var1 = "text";
 6778 6779            "#},
 6780            cx,
 6781        );
 6782    });
 6783
 6784    editor.update_in(cx, |editor, window, cx| {
 6785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6786    });
 6787    assert_eq!(
 6788        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6789        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6790    );
 6791
 6792    // Trying to expand the selected syntax node one more time has no effect.
 6793    editor.update_in(cx, |editor, window, cx| {
 6794        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6795    });
 6796    assert_eq!(
 6797        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6798        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6799    );
 6800
 6801    editor.update_in(cx, |editor, window, cx| {
 6802        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6803    });
 6804    editor.update(cx, |editor, cx| {
 6805        assert_text_with_selections(
 6806            editor,
 6807            indoc! {r#"
 6808                use mod1::mod2::«{mod3, mod4}ˇ»;
 6809
 6810                «ˇfn fn_1(param1: bool, param2: &str) {
 6811                    let var1 = "text";
 6812 6813            "#},
 6814            cx,
 6815        );
 6816    });
 6817
 6818    editor.update_in(cx, |editor, window, cx| {
 6819        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6820    });
 6821    editor.update(cx, |editor, cx| {
 6822        assert_text_with_selections(
 6823            editor,
 6824            indoc! {r#"
 6825                use mod1::mod2::{mod3, «mod4ˇ»};
 6826
 6827                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6828                    let var1 = "«ˇtext»";
 6829                }
 6830            "#},
 6831            cx,
 6832        );
 6833    });
 6834
 6835    editor.update_in(cx, |editor, window, cx| {
 6836        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6837    });
 6838    editor.update(cx, |editor, cx| {
 6839        assert_text_with_selections(
 6840            editor,
 6841            indoc! {r#"
 6842                use mod1::mod2::{mod3, mo«ˇ»d4};
 6843
 6844                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6845                    let var1 = "te«ˇ»xt";
 6846                }
 6847            "#},
 6848            cx,
 6849        );
 6850    });
 6851
 6852    // Trying to shrink the selected syntax node one more time has no effect.
 6853    editor.update_in(cx, |editor, window, cx| {
 6854        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6855    });
 6856    editor.update_in(cx, |editor, _, cx| {
 6857        assert_text_with_selections(
 6858            editor,
 6859            indoc! {r#"
 6860                use mod1::mod2::{mod3, mo«ˇ»d4};
 6861
 6862                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6863                    let var1 = "te«ˇ»xt";
 6864                }
 6865            "#},
 6866            cx,
 6867        );
 6868    });
 6869
 6870    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6871    // a fold.
 6872    editor.update_in(cx, |editor, window, cx| {
 6873        editor.fold_creases(
 6874            vec![
 6875                Crease::simple(
 6876                    Point::new(0, 21)..Point::new(0, 24),
 6877                    FoldPlaceholder::test(),
 6878                ),
 6879                Crease::simple(
 6880                    Point::new(3, 20)..Point::new(3, 22),
 6881                    FoldPlaceholder::test(),
 6882                ),
 6883            ],
 6884            true,
 6885            window,
 6886            cx,
 6887        );
 6888        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6889    });
 6890    editor.update(cx, |editor, cx| {
 6891        assert_text_with_selections(
 6892            editor,
 6893            indoc! {r#"
 6894                use mod1::mod2::«{mod3, mod4}ˇ»;
 6895
 6896                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6897                    let var1 = "«ˇtext»";
 6898                }
 6899            "#},
 6900            cx,
 6901        );
 6902    });
 6903}
 6904
 6905#[gpui::test]
 6906async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6907    init_test(cx, |_| {});
 6908
 6909    let language = Arc::new(Language::new(
 6910        LanguageConfig::default(),
 6911        Some(tree_sitter_rust::LANGUAGE.into()),
 6912    ));
 6913
 6914    let text = "let a = 2;";
 6915
 6916    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6917    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6918    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6919
 6920    editor
 6921        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6922        .await;
 6923
 6924    // Test case 1: Cursor at end of word
 6925    editor.update_in(cx, |editor, window, cx| {
 6926        editor.change_selections(None, window, cx, |s| {
 6927            s.select_display_ranges([
 6928                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6929            ]);
 6930        });
 6931    });
 6932    editor.update(cx, |editor, cx| {
 6933        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6934    });
 6935    editor.update_in(cx, |editor, window, cx| {
 6936        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6937    });
 6938    editor.update(cx, |editor, cx| {
 6939        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 6940    });
 6941    editor.update_in(cx, |editor, window, cx| {
 6942        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6943    });
 6944    editor.update(cx, |editor, cx| {
 6945        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6946    });
 6947
 6948    // Test case 2: Cursor at end of statement
 6949    editor.update_in(cx, |editor, window, cx| {
 6950        editor.change_selections(None, window, cx, |s| {
 6951            s.select_display_ranges([
 6952                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 6953            ]);
 6954        });
 6955    });
 6956    editor.update(cx, |editor, cx| {
 6957        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 6958    });
 6959    editor.update_in(cx, |editor, window, cx| {
 6960        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6961    });
 6962    editor.update(cx, |editor, cx| {
 6963        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6964    });
 6965}
 6966
 6967#[gpui::test]
 6968async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6969    init_test(cx, |_| {});
 6970
 6971    let language = Arc::new(Language::new(
 6972        LanguageConfig::default(),
 6973        Some(tree_sitter_rust::LANGUAGE.into()),
 6974    ));
 6975
 6976    let text = r#"
 6977        use mod1::mod2::{mod3, mod4};
 6978
 6979        fn fn_1(param1: bool, param2: &str) {
 6980            let var1 = "hello world";
 6981        }
 6982    "#
 6983    .unindent();
 6984
 6985    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6986    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6987    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6988
 6989    editor
 6990        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6991        .await;
 6992
 6993    // Test 1: Cursor on a letter of a string word
 6994    editor.update_in(cx, |editor, window, cx| {
 6995        editor.change_selections(None, window, cx, |s| {
 6996            s.select_display_ranges([
 6997                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6998            ]);
 6999        });
 7000    });
 7001    editor.update_in(cx, |editor, window, cx| {
 7002        assert_text_with_selections(
 7003            editor,
 7004            indoc! {r#"
 7005                use mod1::mod2::{mod3, mod4};
 7006
 7007                fn fn_1(param1: bool, param2: &str) {
 7008                    let var1 = "hˇello world";
 7009                }
 7010            "#},
 7011            cx,
 7012        );
 7013        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7014        assert_text_with_selections(
 7015            editor,
 7016            indoc! {r#"
 7017                use mod1::mod2::{mod3, mod4};
 7018
 7019                fn fn_1(param1: bool, param2: &str) {
 7020                    let var1 = "«ˇhello» world";
 7021                }
 7022            "#},
 7023            cx,
 7024        );
 7025    });
 7026
 7027    // Test 2: Partial selection within a word
 7028    editor.update_in(cx, |editor, window, cx| {
 7029        editor.change_selections(None, window, cx, |s| {
 7030            s.select_display_ranges([
 7031                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7032            ]);
 7033        });
 7034    });
 7035    editor.update_in(cx, |editor, window, cx| {
 7036        assert_text_with_selections(
 7037            editor,
 7038            indoc! {r#"
 7039                use mod1::mod2::{mod3, mod4};
 7040
 7041                fn fn_1(param1: bool, param2: &str) {
 7042                    let var1 = "h«elˇ»lo world";
 7043                }
 7044            "#},
 7045            cx,
 7046        );
 7047        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7048        assert_text_with_selections(
 7049            editor,
 7050            indoc! {r#"
 7051                use mod1::mod2::{mod3, mod4};
 7052
 7053                fn fn_1(param1: bool, param2: &str) {
 7054                    let var1 = "«ˇhello» world";
 7055                }
 7056            "#},
 7057            cx,
 7058        );
 7059    });
 7060
 7061    // Test 3: Complete word already selected
 7062    editor.update_in(cx, |editor, window, cx| {
 7063        editor.change_selections(None, window, cx, |s| {
 7064            s.select_display_ranges([
 7065                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7066            ]);
 7067        });
 7068    });
 7069    editor.update_in(cx, |editor, window, cx| {
 7070        assert_text_with_selections(
 7071            editor,
 7072            indoc! {r#"
 7073                use mod1::mod2::{mod3, mod4};
 7074
 7075                fn fn_1(param1: bool, param2: &str) {
 7076                    let var1 = "«helloˇ» world";
 7077                }
 7078            "#},
 7079            cx,
 7080        );
 7081        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7082        assert_text_with_selections(
 7083            editor,
 7084            indoc! {r#"
 7085                use mod1::mod2::{mod3, mod4};
 7086
 7087                fn fn_1(param1: bool, param2: &str) {
 7088                    let var1 = "«hello worldˇ»";
 7089                }
 7090            "#},
 7091            cx,
 7092        );
 7093    });
 7094
 7095    // Test 4: Selection spanning across words
 7096    editor.update_in(cx, |editor, window, cx| {
 7097        editor.change_selections(None, window, cx, |s| {
 7098            s.select_display_ranges([
 7099                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7100            ]);
 7101        });
 7102    });
 7103    editor.update_in(cx, |editor, window, cx| {
 7104        assert_text_with_selections(
 7105            editor,
 7106            indoc! {r#"
 7107                use mod1::mod2::{mod3, mod4};
 7108
 7109                fn fn_1(param1: bool, param2: &str) {
 7110                    let var1 = "hel«lo woˇ»rld";
 7111                }
 7112            "#},
 7113            cx,
 7114        );
 7115        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7116        assert_text_with_selections(
 7117            editor,
 7118            indoc! {r#"
 7119                use mod1::mod2::{mod3, mod4};
 7120
 7121                fn fn_1(param1: bool, param2: &str) {
 7122                    let var1 = "«ˇhello world»";
 7123                }
 7124            "#},
 7125            cx,
 7126        );
 7127    });
 7128
 7129    // Test 5: Expansion beyond string
 7130    editor.update_in(cx, |editor, window, cx| {
 7131        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7132        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7133        assert_text_with_selections(
 7134            editor,
 7135            indoc! {r#"
 7136                use mod1::mod2::{mod3, mod4};
 7137
 7138                fn fn_1(param1: bool, param2: &str) {
 7139                    «ˇlet var1 = "hello world";»
 7140                }
 7141            "#},
 7142            cx,
 7143        );
 7144    });
 7145}
 7146
 7147#[gpui::test]
 7148async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    let base_text = r#"
 7152        impl A {
 7153            // this is an uncommitted comment
 7154
 7155            fn b() {
 7156                c();
 7157            }
 7158
 7159            // this is another uncommitted comment
 7160
 7161            fn d() {
 7162                // e
 7163                // f
 7164            }
 7165        }
 7166
 7167        fn g() {
 7168            // h
 7169        }
 7170    "#
 7171    .unindent();
 7172
 7173    let text = r#"
 7174        ˇimpl A {
 7175
 7176            fn b() {
 7177                c();
 7178            }
 7179
 7180            fn d() {
 7181                // e
 7182                // f
 7183            }
 7184        }
 7185
 7186        fn g() {
 7187            // h
 7188        }
 7189    "#
 7190    .unindent();
 7191
 7192    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7193    cx.set_state(&text);
 7194    cx.set_head_text(&base_text);
 7195    cx.update_editor(|editor, window, cx| {
 7196        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7197    });
 7198
 7199    cx.assert_state_with_diff(
 7200        "
 7201        ˇimpl A {
 7202      -     // this is an uncommitted comment
 7203
 7204            fn b() {
 7205                c();
 7206            }
 7207
 7208      -     // this is another uncommitted comment
 7209      -
 7210            fn d() {
 7211                // e
 7212                // f
 7213            }
 7214        }
 7215
 7216        fn g() {
 7217            // h
 7218        }
 7219    "
 7220        .unindent(),
 7221    );
 7222
 7223    let expected_display_text = "
 7224        impl A {
 7225            // this is an uncommitted comment
 7226
 7227            fn b() {
 7228 7229            }
 7230
 7231            // this is another uncommitted comment
 7232
 7233            fn d() {
 7234 7235            }
 7236        }
 7237
 7238        fn g() {
 7239 7240        }
 7241        "
 7242    .unindent();
 7243
 7244    cx.update_editor(|editor, window, cx| {
 7245        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7246        assert_eq!(editor.display_text(cx), expected_display_text);
 7247    });
 7248}
 7249
 7250#[gpui::test]
 7251async fn test_autoindent(cx: &mut TestAppContext) {
 7252    init_test(cx, |_| {});
 7253
 7254    let language = Arc::new(
 7255        Language::new(
 7256            LanguageConfig {
 7257                brackets: BracketPairConfig {
 7258                    pairs: vec![
 7259                        BracketPair {
 7260                            start: "{".to_string(),
 7261                            end: "}".to_string(),
 7262                            close: false,
 7263                            surround: false,
 7264                            newline: true,
 7265                        },
 7266                        BracketPair {
 7267                            start: "(".to_string(),
 7268                            end: ")".to_string(),
 7269                            close: false,
 7270                            surround: false,
 7271                            newline: true,
 7272                        },
 7273                    ],
 7274                    ..Default::default()
 7275                },
 7276                ..Default::default()
 7277            },
 7278            Some(tree_sitter_rust::LANGUAGE.into()),
 7279        )
 7280        .with_indents_query(
 7281            r#"
 7282                (_ "(" ")" @end) @indent
 7283                (_ "{" "}" @end) @indent
 7284            "#,
 7285        )
 7286        .unwrap(),
 7287    );
 7288
 7289    let text = "fn a() {}";
 7290
 7291    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7293    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7294    editor
 7295        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7296        .await;
 7297
 7298    editor.update_in(cx, |editor, window, cx| {
 7299        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7300        editor.newline(&Newline, window, cx);
 7301        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7302        assert_eq!(
 7303            editor.selections.ranges(cx),
 7304            &[
 7305                Point::new(1, 4)..Point::new(1, 4),
 7306                Point::new(3, 4)..Point::new(3, 4),
 7307                Point::new(5, 0)..Point::new(5, 0)
 7308            ]
 7309        );
 7310    });
 7311}
 7312
 7313#[gpui::test]
 7314async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7315    init_test(cx, |_| {});
 7316
 7317    {
 7318        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7319        cx.set_state(indoc! {"
 7320            impl A {
 7321
 7322                fn b() {}
 7323
 7324            «fn c() {
 7325
 7326            }ˇ»
 7327            }
 7328        "});
 7329
 7330        cx.update_editor(|editor, window, cx| {
 7331            editor.autoindent(&Default::default(), window, cx);
 7332        });
 7333
 7334        cx.assert_editor_state(indoc! {"
 7335            impl A {
 7336
 7337                fn b() {}
 7338
 7339                «fn c() {
 7340
 7341                }ˇ»
 7342            }
 7343        "});
 7344    }
 7345
 7346    {
 7347        let mut cx = EditorTestContext::new_multibuffer(
 7348            cx,
 7349            [indoc! { "
 7350                impl A {
 7351                «
 7352                // a
 7353                fn b(){}
 7354                »
 7355                «
 7356                    }
 7357                    fn c(){}
 7358                »
 7359            "}],
 7360        );
 7361
 7362        let buffer = cx.update_editor(|editor, _, cx| {
 7363            let buffer = editor.buffer().update(cx, |buffer, _| {
 7364                buffer.all_buffers().iter().next().unwrap().clone()
 7365            });
 7366            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7367            buffer
 7368        });
 7369
 7370        cx.run_until_parked();
 7371        cx.update_editor(|editor, window, cx| {
 7372            editor.select_all(&Default::default(), window, cx);
 7373            editor.autoindent(&Default::default(), window, cx)
 7374        });
 7375        cx.run_until_parked();
 7376
 7377        cx.update(|_, cx| {
 7378            assert_eq!(
 7379                buffer.read(cx).text(),
 7380                indoc! { "
 7381                    impl A {
 7382
 7383                        // a
 7384                        fn b(){}
 7385
 7386
 7387                    }
 7388                    fn c(){}
 7389
 7390                " }
 7391            )
 7392        });
 7393    }
 7394}
 7395
 7396#[gpui::test]
 7397async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7398    init_test(cx, |_| {});
 7399
 7400    let mut cx = EditorTestContext::new(cx).await;
 7401
 7402    let language = Arc::new(Language::new(
 7403        LanguageConfig {
 7404            brackets: BracketPairConfig {
 7405                pairs: vec![
 7406                    BracketPair {
 7407                        start: "{".to_string(),
 7408                        end: "}".to_string(),
 7409                        close: true,
 7410                        surround: true,
 7411                        newline: true,
 7412                    },
 7413                    BracketPair {
 7414                        start: "(".to_string(),
 7415                        end: ")".to_string(),
 7416                        close: true,
 7417                        surround: true,
 7418                        newline: true,
 7419                    },
 7420                    BracketPair {
 7421                        start: "/*".to_string(),
 7422                        end: " */".to_string(),
 7423                        close: true,
 7424                        surround: true,
 7425                        newline: true,
 7426                    },
 7427                    BracketPair {
 7428                        start: "[".to_string(),
 7429                        end: "]".to_string(),
 7430                        close: false,
 7431                        surround: false,
 7432                        newline: true,
 7433                    },
 7434                    BracketPair {
 7435                        start: "\"".to_string(),
 7436                        end: "\"".to_string(),
 7437                        close: true,
 7438                        surround: true,
 7439                        newline: false,
 7440                    },
 7441                    BracketPair {
 7442                        start: "<".to_string(),
 7443                        end: ">".to_string(),
 7444                        close: false,
 7445                        surround: true,
 7446                        newline: true,
 7447                    },
 7448                ],
 7449                ..Default::default()
 7450            },
 7451            autoclose_before: "})]".to_string(),
 7452            ..Default::default()
 7453        },
 7454        Some(tree_sitter_rust::LANGUAGE.into()),
 7455    ));
 7456
 7457    cx.language_registry().add(language.clone());
 7458    cx.update_buffer(|buffer, cx| {
 7459        buffer.set_language(Some(language), cx);
 7460    });
 7461
 7462    cx.set_state(
 7463        &r#"
 7464            🏀ˇ
 7465            εˇ
 7466            ❤️ˇ
 7467        "#
 7468        .unindent(),
 7469    );
 7470
 7471    // autoclose multiple nested brackets at multiple cursors
 7472    cx.update_editor(|editor, window, cx| {
 7473        editor.handle_input("{", window, cx);
 7474        editor.handle_input("{", window, cx);
 7475        editor.handle_input("{", window, cx);
 7476    });
 7477    cx.assert_editor_state(
 7478        &"
 7479            🏀{{{ˇ}}}
 7480            ε{{{ˇ}}}
 7481            ❤️{{{ˇ}}}
 7482        "
 7483        .unindent(),
 7484    );
 7485
 7486    // insert a different closing bracket
 7487    cx.update_editor(|editor, window, cx| {
 7488        editor.handle_input(")", window, cx);
 7489    });
 7490    cx.assert_editor_state(
 7491        &"
 7492            🏀{{{)ˇ}}}
 7493            ε{{{)ˇ}}}
 7494            ❤️{{{)ˇ}}}
 7495        "
 7496        .unindent(),
 7497    );
 7498
 7499    // skip over the auto-closed brackets when typing a closing bracket
 7500    cx.update_editor(|editor, window, cx| {
 7501        editor.move_right(&MoveRight, window, cx);
 7502        editor.handle_input("}", window, cx);
 7503        editor.handle_input("}", window, cx);
 7504        editor.handle_input("}", window, cx);
 7505    });
 7506    cx.assert_editor_state(
 7507        &"
 7508            🏀{{{)}}}}ˇ
 7509            ε{{{)}}}}ˇ
 7510            ❤️{{{)}}}}ˇ
 7511        "
 7512        .unindent(),
 7513    );
 7514
 7515    // autoclose multi-character pairs
 7516    cx.set_state(
 7517        &"
 7518            ˇ
 7519            ˇ
 7520        "
 7521        .unindent(),
 7522    );
 7523    cx.update_editor(|editor, window, cx| {
 7524        editor.handle_input("/", window, cx);
 7525        editor.handle_input("*", window, cx);
 7526    });
 7527    cx.assert_editor_state(
 7528        &"
 7529            /*ˇ */
 7530            /*ˇ */
 7531        "
 7532        .unindent(),
 7533    );
 7534
 7535    // one cursor autocloses a multi-character pair, one cursor
 7536    // does not autoclose.
 7537    cx.set_state(
 7538        &"
 7539 7540            ˇ
 7541        "
 7542        .unindent(),
 7543    );
 7544    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7545    cx.assert_editor_state(
 7546        &"
 7547            /*ˇ */
 7548 7549        "
 7550        .unindent(),
 7551    );
 7552
 7553    // Don't autoclose if the next character isn't whitespace and isn't
 7554    // listed in the language's "autoclose_before" section.
 7555    cx.set_state("ˇa b");
 7556    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7557    cx.assert_editor_state("{ˇa b");
 7558
 7559    // Don't autoclose if `close` is false for the bracket pair
 7560    cx.set_state("ˇ");
 7561    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7562    cx.assert_editor_state("");
 7563
 7564    // Surround with brackets if text is selected
 7565    cx.set_state("«aˇ» b");
 7566    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7567    cx.assert_editor_state("{«aˇ»} b");
 7568
 7569    // Autoclose when not immediately after a word character
 7570    cx.set_state("a ˇ");
 7571    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7572    cx.assert_editor_state("a \"ˇ\"");
 7573
 7574    // Autoclose pair where the start and end characters are the same
 7575    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7576    cx.assert_editor_state("a \"\"ˇ");
 7577
 7578    // Don't autoclose when immediately after a word character
 7579    cx.set_state("");
 7580    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7581    cx.assert_editor_state("a\"ˇ");
 7582
 7583    // Do autoclose when after a non-word character
 7584    cx.set_state("");
 7585    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7586    cx.assert_editor_state("{\"ˇ\"");
 7587
 7588    // Non identical pairs autoclose regardless of preceding character
 7589    cx.set_state("");
 7590    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7591    cx.assert_editor_state("a{ˇ}");
 7592
 7593    // Don't autoclose pair if autoclose is disabled
 7594    cx.set_state("ˇ");
 7595    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7596    cx.assert_editor_state("");
 7597
 7598    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7599    cx.set_state("«aˇ» b");
 7600    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7601    cx.assert_editor_state("<«aˇ»> b");
 7602}
 7603
 7604#[gpui::test]
 7605async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7606    init_test(cx, |settings| {
 7607        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7608    });
 7609
 7610    let mut cx = EditorTestContext::new(cx).await;
 7611
 7612    let language = Arc::new(Language::new(
 7613        LanguageConfig {
 7614            brackets: BracketPairConfig {
 7615                pairs: vec![
 7616                    BracketPair {
 7617                        start: "{".to_string(),
 7618                        end: "}".to_string(),
 7619                        close: true,
 7620                        surround: true,
 7621                        newline: true,
 7622                    },
 7623                    BracketPair {
 7624                        start: "(".to_string(),
 7625                        end: ")".to_string(),
 7626                        close: true,
 7627                        surround: true,
 7628                        newline: true,
 7629                    },
 7630                    BracketPair {
 7631                        start: "[".to_string(),
 7632                        end: "]".to_string(),
 7633                        close: false,
 7634                        surround: false,
 7635                        newline: true,
 7636                    },
 7637                ],
 7638                ..Default::default()
 7639            },
 7640            autoclose_before: "})]".to_string(),
 7641            ..Default::default()
 7642        },
 7643        Some(tree_sitter_rust::LANGUAGE.into()),
 7644    ));
 7645
 7646    cx.language_registry().add(language.clone());
 7647    cx.update_buffer(|buffer, cx| {
 7648        buffer.set_language(Some(language), cx);
 7649    });
 7650
 7651    cx.set_state(
 7652        &"
 7653            ˇ
 7654            ˇ
 7655            ˇ
 7656        "
 7657        .unindent(),
 7658    );
 7659
 7660    // ensure only matching closing brackets are skipped over
 7661    cx.update_editor(|editor, window, cx| {
 7662        editor.handle_input("}", window, cx);
 7663        editor.move_left(&MoveLeft, window, cx);
 7664        editor.handle_input(")", window, cx);
 7665        editor.move_left(&MoveLeft, window, cx);
 7666    });
 7667    cx.assert_editor_state(
 7668        &"
 7669            ˇ)}
 7670            ˇ)}
 7671            ˇ)}
 7672        "
 7673        .unindent(),
 7674    );
 7675
 7676    // skip-over closing brackets at multiple cursors
 7677    cx.update_editor(|editor, window, cx| {
 7678        editor.handle_input(")", window, cx);
 7679        editor.handle_input("}", window, cx);
 7680    });
 7681    cx.assert_editor_state(
 7682        &"
 7683            )}ˇ
 7684            )}ˇ
 7685            )}ˇ
 7686        "
 7687        .unindent(),
 7688    );
 7689
 7690    // ignore non-close brackets
 7691    cx.update_editor(|editor, window, cx| {
 7692        editor.handle_input("]", window, cx);
 7693        editor.move_left(&MoveLeft, window, cx);
 7694        editor.handle_input("]", window, cx);
 7695    });
 7696    cx.assert_editor_state(
 7697        &"
 7698            )}]ˇ]
 7699            )}]ˇ]
 7700            )}]ˇ]
 7701        "
 7702        .unindent(),
 7703    );
 7704}
 7705
 7706#[gpui::test]
 7707async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7708    init_test(cx, |_| {});
 7709
 7710    let mut cx = EditorTestContext::new(cx).await;
 7711
 7712    let html_language = Arc::new(
 7713        Language::new(
 7714            LanguageConfig {
 7715                name: "HTML".into(),
 7716                brackets: BracketPairConfig {
 7717                    pairs: vec![
 7718                        BracketPair {
 7719                            start: "<".into(),
 7720                            end: ">".into(),
 7721                            close: true,
 7722                            ..Default::default()
 7723                        },
 7724                        BracketPair {
 7725                            start: "{".into(),
 7726                            end: "}".into(),
 7727                            close: true,
 7728                            ..Default::default()
 7729                        },
 7730                        BracketPair {
 7731                            start: "(".into(),
 7732                            end: ")".into(),
 7733                            close: true,
 7734                            ..Default::default()
 7735                        },
 7736                    ],
 7737                    ..Default::default()
 7738                },
 7739                autoclose_before: "})]>".into(),
 7740                ..Default::default()
 7741            },
 7742            Some(tree_sitter_html::LANGUAGE.into()),
 7743        )
 7744        .with_injection_query(
 7745            r#"
 7746            (script_element
 7747                (raw_text) @injection.content
 7748                (#set! injection.language "javascript"))
 7749            "#,
 7750        )
 7751        .unwrap(),
 7752    );
 7753
 7754    let javascript_language = Arc::new(Language::new(
 7755        LanguageConfig {
 7756            name: "JavaScript".into(),
 7757            brackets: BracketPairConfig {
 7758                pairs: vec![
 7759                    BracketPair {
 7760                        start: "/*".into(),
 7761                        end: " */".into(),
 7762                        close: true,
 7763                        ..Default::default()
 7764                    },
 7765                    BracketPair {
 7766                        start: "{".into(),
 7767                        end: "}".into(),
 7768                        close: true,
 7769                        ..Default::default()
 7770                    },
 7771                    BracketPair {
 7772                        start: "(".into(),
 7773                        end: ")".into(),
 7774                        close: true,
 7775                        ..Default::default()
 7776                    },
 7777                ],
 7778                ..Default::default()
 7779            },
 7780            autoclose_before: "})]>".into(),
 7781            ..Default::default()
 7782        },
 7783        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7784    ));
 7785
 7786    cx.language_registry().add(html_language.clone());
 7787    cx.language_registry().add(javascript_language.clone());
 7788
 7789    cx.update_buffer(|buffer, cx| {
 7790        buffer.set_language(Some(html_language), cx);
 7791    });
 7792
 7793    cx.set_state(
 7794        &r#"
 7795            <body>ˇ
 7796                <script>
 7797                    var x = 1;ˇ
 7798                </script>
 7799            </body>ˇ
 7800        "#
 7801        .unindent(),
 7802    );
 7803
 7804    // Precondition: different languages are active at different locations.
 7805    cx.update_editor(|editor, window, cx| {
 7806        let snapshot = editor.snapshot(window, cx);
 7807        let cursors = editor.selections.ranges::<usize>(cx);
 7808        let languages = cursors
 7809            .iter()
 7810            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7811            .collect::<Vec<_>>();
 7812        assert_eq!(
 7813            languages,
 7814            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7815        );
 7816    });
 7817
 7818    // Angle brackets autoclose in HTML, but not JavaScript.
 7819    cx.update_editor(|editor, window, cx| {
 7820        editor.handle_input("<", window, cx);
 7821        editor.handle_input("a", window, cx);
 7822    });
 7823    cx.assert_editor_state(
 7824        &r#"
 7825            <body><aˇ>
 7826                <script>
 7827                    var x = 1;<aˇ
 7828                </script>
 7829            </body><aˇ>
 7830        "#
 7831        .unindent(),
 7832    );
 7833
 7834    // Curly braces and parens autoclose in both HTML and JavaScript.
 7835    cx.update_editor(|editor, window, cx| {
 7836        editor.handle_input(" b=", window, cx);
 7837        editor.handle_input("{", window, cx);
 7838        editor.handle_input("c", window, cx);
 7839        editor.handle_input("(", window, cx);
 7840    });
 7841    cx.assert_editor_state(
 7842        &r#"
 7843            <body><a b={c(ˇ)}>
 7844                <script>
 7845                    var x = 1;<a b={c(ˇ)}
 7846                </script>
 7847            </body><a b={c(ˇ)}>
 7848        "#
 7849        .unindent(),
 7850    );
 7851
 7852    // Brackets that were already autoclosed are skipped.
 7853    cx.update_editor(|editor, window, cx| {
 7854        editor.handle_input(")", window, cx);
 7855        editor.handle_input("d", window, cx);
 7856        editor.handle_input("}", window, cx);
 7857    });
 7858    cx.assert_editor_state(
 7859        &r#"
 7860            <body><a b={c()d}ˇ>
 7861                <script>
 7862                    var x = 1;<a b={c()d}ˇ
 7863                </script>
 7864            </body><a b={c()d}ˇ>
 7865        "#
 7866        .unindent(),
 7867    );
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.handle_input(">", window, cx);
 7870    });
 7871    cx.assert_editor_state(
 7872        &r#"
 7873            <body><a b={c()d}>ˇ
 7874                <script>
 7875                    var x = 1;<a b={c()d}>ˇ
 7876                </script>
 7877            </body><a b={c()d}>ˇ
 7878        "#
 7879        .unindent(),
 7880    );
 7881
 7882    // Reset
 7883    cx.set_state(
 7884        &r#"
 7885            <body>ˇ
 7886                <script>
 7887                    var x = 1;ˇ
 7888                </script>
 7889            </body>ˇ
 7890        "#
 7891        .unindent(),
 7892    );
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.handle_input("<", window, cx);
 7896    });
 7897    cx.assert_editor_state(
 7898        &r#"
 7899            <body><ˇ>
 7900                <script>
 7901                    var x = 1;<ˇ
 7902                </script>
 7903            </body><ˇ>
 7904        "#
 7905        .unindent(),
 7906    );
 7907
 7908    // When backspacing, the closing angle brackets are removed.
 7909    cx.update_editor(|editor, window, cx| {
 7910        editor.backspace(&Backspace, window, cx);
 7911    });
 7912    cx.assert_editor_state(
 7913        &r#"
 7914            <body>ˇ
 7915                <script>
 7916                    var x = 1;ˇ
 7917                </script>
 7918            </body>ˇ
 7919        "#
 7920        .unindent(),
 7921    );
 7922
 7923    // Block comments autoclose in JavaScript, but not HTML.
 7924    cx.update_editor(|editor, window, cx| {
 7925        editor.handle_input("/", window, cx);
 7926        editor.handle_input("*", window, cx);
 7927    });
 7928    cx.assert_editor_state(
 7929        &r#"
 7930            <body>/*ˇ
 7931                <script>
 7932                    var x = 1;/*ˇ */
 7933                </script>
 7934            </body>/*ˇ
 7935        "#
 7936        .unindent(),
 7937    );
 7938}
 7939
 7940#[gpui::test]
 7941async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7942    init_test(cx, |_| {});
 7943
 7944    let mut cx = EditorTestContext::new(cx).await;
 7945
 7946    let rust_language = Arc::new(
 7947        Language::new(
 7948            LanguageConfig {
 7949                name: "Rust".into(),
 7950                brackets: serde_json::from_value(json!([
 7951                    { "start": "{", "end": "}", "close": true, "newline": true },
 7952                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7953                ]))
 7954                .unwrap(),
 7955                autoclose_before: "})]>".into(),
 7956                ..Default::default()
 7957            },
 7958            Some(tree_sitter_rust::LANGUAGE.into()),
 7959        )
 7960        .with_override_query("(string_literal) @string")
 7961        .unwrap(),
 7962    );
 7963
 7964    cx.language_registry().add(rust_language.clone());
 7965    cx.update_buffer(|buffer, cx| {
 7966        buffer.set_language(Some(rust_language), cx);
 7967    });
 7968
 7969    cx.set_state(
 7970        &r#"
 7971            let x = ˇ
 7972        "#
 7973        .unindent(),
 7974    );
 7975
 7976    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7977    cx.update_editor(|editor, window, cx| {
 7978        editor.handle_input("\"", window, cx);
 7979    });
 7980    cx.assert_editor_state(
 7981        &r#"
 7982            let x = "ˇ"
 7983        "#
 7984        .unindent(),
 7985    );
 7986
 7987    // Inserting another quotation mark. The cursor moves across the existing
 7988    // automatically-inserted quotation mark.
 7989    cx.update_editor(|editor, window, cx| {
 7990        editor.handle_input("\"", window, cx);
 7991    });
 7992    cx.assert_editor_state(
 7993        &r#"
 7994            let x = ""ˇ
 7995        "#
 7996        .unindent(),
 7997    );
 7998
 7999    // Reset
 8000    cx.set_state(
 8001        &r#"
 8002            let x = ˇ
 8003        "#
 8004        .unindent(),
 8005    );
 8006
 8007    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8008    cx.update_editor(|editor, window, cx| {
 8009        editor.handle_input("\"", window, cx);
 8010        editor.handle_input(" ", window, cx);
 8011        editor.move_left(&Default::default(), window, cx);
 8012        editor.handle_input("\\", window, cx);
 8013        editor.handle_input("\"", window, cx);
 8014    });
 8015    cx.assert_editor_state(
 8016        &r#"
 8017            let x = "\"ˇ "
 8018        "#
 8019        .unindent(),
 8020    );
 8021
 8022    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8023    // mark. Nothing is inserted.
 8024    cx.update_editor(|editor, window, cx| {
 8025        editor.move_right(&Default::default(), window, cx);
 8026        editor.handle_input("\"", window, cx);
 8027    });
 8028    cx.assert_editor_state(
 8029        &r#"
 8030            let x = "\" "ˇ
 8031        "#
 8032        .unindent(),
 8033    );
 8034}
 8035
 8036#[gpui::test]
 8037async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8038    init_test(cx, |_| {});
 8039
 8040    let language = Arc::new(Language::new(
 8041        LanguageConfig {
 8042            brackets: BracketPairConfig {
 8043                pairs: vec![
 8044                    BracketPair {
 8045                        start: "{".to_string(),
 8046                        end: "}".to_string(),
 8047                        close: true,
 8048                        surround: true,
 8049                        newline: true,
 8050                    },
 8051                    BracketPair {
 8052                        start: "/* ".to_string(),
 8053                        end: "*/".to_string(),
 8054                        close: true,
 8055                        surround: true,
 8056                        ..Default::default()
 8057                    },
 8058                ],
 8059                ..Default::default()
 8060            },
 8061            ..Default::default()
 8062        },
 8063        Some(tree_sitter_rust::LANGUAGE.into()),
 8064    ));
 8065
 8066    let text = r#"
 8067        a
 8068        b
 8069        c
 8070    "#
 8071    .unindent();
 8072
 8073    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8074    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8075    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8076    editor
 8077        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8078        .await;
 8079
 8080    editor.update_in(cx, |editor, window, cx| {
 8081        editor.change_selections(None, window, cx, |s| {
 8082            s.select_display_ranges([
 8083                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8084                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8085                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8086            ])
 8087        });
 8088
 8089        editor.handle_input("{", window, cx);
 8090        editor.handle_input("{", window, cx);
 8091        editor.handle_input("{", window, cx);
 8092        assert_eq!(
 8093            editor.text(cx),
 8094            "
 8095                {{{a}}}
 8096                {{{b}}}
 8097                {{{c}}}
 8098            "
 8099            .unindent()
 8100        );
 8101        assert_eq!(
 8102            editor.selections.display_ranges(cx),
 8103            [
 8104                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8105                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8106                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8107            ]
 8108        );
 8109
 8110        editor.undo(&Undo, window, cx);
 8111        editor.undo(&Undo, window, cx);
 8112        editor.undo(&Undo, window, cx);
 8113        assert_eq!(
 8114            editor.text(cx),
 8115            "
 8116                a
 8117                b
 8118                c
 8119            "
 8120            .unindent()
 8121        );
 8122        assert_eq!(
 8123            editor.selections.display_ranges(cx),
 8124            [
 8125                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8126                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8127                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8128            ]
 8129        );
 8130
 8131        // Ensure inserting the first character of a multi-byte bracket pair
 8132        // doesn't surround the selections with the bracket.
 8133        editor.handle_input("/", window, cx);
 8134        assert_eq!(
 8135            editor.text(cx),
 8136            "
 8137                /
 8138                /
 8139                /
 8140            "
 8141            .unindent()
 8142        );
 8143        assert_eq!(
 8144            editor.selections.display_ranges(cx),
 8145            [
 8146                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8147                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8148                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8149            ]
 8150        );
 8151
 8152        editor.undo(&Undo, window, cx);
 8153        assert_eq!(
 8154            editor.text(cx),
 8155            "
 8156                a
 8157                b
 8158                c
 8159            "
 8160            .unindent()
 8161        );
 8162        assert_eq!(
 8163            editor.selections.display_ranges(cx),
 8164            [
 8165                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8166                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8167                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8168            ]
 8169        );
 8170
 8171        // Ensure inserting the last character of a multi-byte bracket pair
 8172        // doesn't surround the selections with the bracket.
 8173        editor.handle_input("*", window, cx);
 8174        assert_eq!(
 8175            editor.text(cx),
 8176            "
 8177                *
 8178                *
 8179                *
 8180            "
 8181            .unindent()
 8182        );
 8183        assert_eq!(
 8184            editor.selections.display_ranges(cx),
 8185            [
 8186                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8187                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8188                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8189            ]
 8190        );
 8191    });
 8192}
 8193
 8194#[gpui::test]
 8195async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8196    init_test(cx, |_| {});
 8197
 8198    let language = Arc::new(Language::new(
 8199        LanguageConfig {
 8200            brackets: BracketPairConfig {
 8201                pairs: vec![BracketPair {
 8202                    start: "{".to_string(),
 8203                    end: "}".to_string(),
 8204                    close: true,
 8205                    surround: true,
 8206                    newline: true,
 8207                }],
 8208                ..Default::default()
 8209            },
 8210            autoclose_before: "}".to_string(),
 8211            ..Default::default()
 8212        },
 8213        Some(tree_sitter_rust::LANGUAGE.into()),
 8214    ));
 8215
 8216    let text = r#"
 8217        a
 8218        b
 8219        c
 8220    "#
 8221    .unindent();
 8222
 8223    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8224    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8225    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8226    editor
 8227        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8228        .await;
 8229
 8230    editor.update_in(cx, |editor, window, cx| {
 8231        editor.change_selections(None, window, cx, |s| {
 8232            s.select_ranges([
 8233                Point::new(0, 1)..Point::new(0, 1),
 8234                Point::new(1, 1)..Point::new(1, 1),
 8235                Point::new(2, 1)..Point::new(2, 1),
 8236            ])
 8237        });
 8238
 8239        editor.handle_input("{", window, cx);
 8240        editor.handle_input("{", window, cx);
 8241        editor.handle_input("_", window, cx);
 8242        assert_eq!(
 8243            editor.text(cx),
 8244            "
 8245                a{{_}}
 8246                b{{_}}
 8247                c{{_}}
 8248            "
 8249            .unindent()
 8250        );
 8251        assert_eq!(
 8252            editor.selections.ranges::<Point>(cx),
 8253            [
 8254                Point::new(0, 4)..Point::new(0, 4),
 8255                Point::new(1, 4)..Point::new(1, 4),
 8256                Point::new(2, 4)..Point::new(2, 4)
 8257            ]
 8258        );
 8259
 8260        editor.backspace(&Default::default(), window, cx);
 8261        editor.backspace(&Default::default(), window, cx);
 8262        assert_eq!(
 8263            editor.text(cx),
 8264            "
 8265                a{}
 8266                b{}
 8267                c{}
 8268            "
 8269            .unindent()
 8270        );
 8271        assert_eq!(
 8272            editor.selections.ranges::<Point>(cx),
 8273            [
 8274                Point::new(0, 2)..Point::new(0, 2),
 8275                Point::new(1, 2)..Point::new(1, 2),
 8276                Point::new(2, 2)..Point::new(2, 2)
 8277            ]
 8278        );
 8279
 8280        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8281        assert_eq!(
 8282            editor.text(cx),
 8283            "
 8284                a
 8285                b
 8286                c
 8287            "
 8288            .unindent()
 8289        );
 8290        assert_eq!(
 8291            editor.selections.ranges::<Point>(cx),
 8292            [
 8293                Point::new(0, 1)..Point::new(0, 1),
 8294                Point::new(1, 1)..Point::new(1, 1),
 8295                Point::new(2, 1)..Point::new(2, 1)
 8296            ]
 8297        );
 8298    });
 8299}
 8300
 8301#[gpui::test]
 8302async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8303    init_test(cx, |settings| {
 8304        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8305    });
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308
 8309    let language = Arc::new(Language::new(
 8310        LanguageConfig {
 8311            brackets: BracketPairConfig {
 8312                pairs: vec![
 8313                    BracketPair {
 8314                        start: "{".to_string(),
 8315                        end: "}".to_string(),
 8316                        close: true,
 8317                        surround: true,
 8318                        newline: true,
 8319                    },
 8320                    BracketPair {
 8321                        start: "(".to_string(),
 8322                        end: ")".to_string(),
 8323                        close: true,
 8324                        surround: true,
 8325                        newline: true,
 8326                    },
 8327                    BracketPair {
 8328                        start: "[".to_string(),
 8329                        end: "]".to_string(),
 8330                        close: false,
 8331                        surround: true,
 8332                        newline: true,
 8333                    },
 8334                ],
 8335                ..Default::default()
 8336            },
 8337            autoclose_before: "})]".to_string(),
 8338            ..Default::default()
 8339        },
 8340        Some(tree_sitter_rust::LANGUAGE.into()),
 8341    ));
 8342
 8343    cx.language_registry().add(language.clone());
 8344    cx.update_buffer(|buffer, cx| {
 8345        buffer.set_language(Some(language), cx);
 8346    });
 8347
 8348    cx.set_state(
 8349        &"
 8350            {(ˇ)}
 8351            [[ˇ]]
 8352            {(ˇ)}
 8353        "
 8354        .unindent(),
 8355    );
 8356
 8357    cx.update_editor(|editor, window, cx| {
 8358        editor.backspace(&Default::default(), window, cx);
 8359        editor.backspace(&Default::default(), window, cx);
 8360    });
 8361
 8362    cx.assert_editor_state(
 8363        &"
 8364            ˇ
 8365            ˇ]]
 8366            ˇ
 8367        "
 8368        .unindent(),
 8369    );
 8370
 8371    cx.update_editor(|editor, window, cx| {
 8372        editor.handle_input("{", window, cx);
 8373        editor.handle_input("{", window, cx);
 8374        editor.move_right(&MoveRight, window, cx);
 8375        editor.move_right(&MoveRight, window, cx);
 8376        editor.move_left(&MoveLeft, window, cx);
 8377        editor.move_left(&MoveLeft, window, cx);
 8378        editor.backspace(&Default::default(), window, cx);
 8379    });
 8380
 8381    cx.assert_editor_state(
 8382        &"
 8383            {ˇ}
 8384            {ˇ}]]
 8385            {ˇ}
 8386        "
 8387        .unindent(),
 8388    );
 8389
 8390    cx.update_editor(|editor, window, cx| {
 8391        editor.backspace(&Default::default(), window, cx);
 8392    });
 8393
 8394    cx.assert_editor_state(
 8395        &"
 8396            ˇ
 8397            ˇ]]
 8398            ˇ
 8399        "
 8400        .unindent(),
 8401    );
 8402}
 8403
 8404#[gpui::test]
 8405async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8406    init_test(cx, |_| {});
 8407
 8408    let language = Arc::new(Language::new(
 8409        LanguageConfig::default(),
 8410        Some(tree_sitter_rust::LANGUAGE.into()),
 8411    ));
 8412
 8413    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8414    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8415    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8416    editor
 8417        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8418        .await;
 8419
 8420    editor.update_in(cx, |editor, window, cx| {
 8421        editor.set_auto_replace_emoji_shortcode(true);
 8422
 8423        editor.handle_input("Hello ", window, cx);
 8424        editor.handle_input(":wave", window, cx);
 8425        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8426
 8427        editor.handle_input(":", window, cx);
 8428        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8429
 8430        editor.handle_input(" :smile", window, cx);
 8431        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8432
 8433        editor.handle_input(":", window, cx);
 8434        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8435
 8436        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8437        editor.handle_input(":wave", window, cx);
 8438        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8439
 8440        editor.handle_input(":", window, cx);
 8441        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8442
 8443        editor.handle_input(":1", window, cx);
 8444        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8445
 8446        editor.handle_input(":", window, cx);
 8447        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8448
 8449        // Ensure shortcode does not get replaced when it is part of a word
 8450        editor.handle_input(" Test:wave", window, cx);
 8451        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8452
 8453        editor.handle_input(":", window, cx);
 8454        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8455
 8456        editor.set_auto_replace_emoji_shortcode(false);
 8457
 8458        // Ensure shortcode does not get replaced when auto replace is off
 8459        editor.handle_input(" :wave", window, cx);
 8460        assert_eq!(
 8461            editor.text(cx),
 8462            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8463        );
 8464
 8465        editor.handle_input(":", window, cx);
 8466        assert_eq!(
 8467            editor.text(cx),
 8468            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8469        );
 8470    });
 8471}
 8472
 8473#[gpui::test]
 8474async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8475    init_test(cx, |_| {});
 8476
 8477    let (text, insertion_ranges) = marked_text_ranges(
 8478        indoc! {"
 8479            ˇ
 8480        "},
 8481        false,
 8482    );
 8483
 8484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8485    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8486
 8487    _ = editor.update_in(cx, |editor, window, cx| {
 8488        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8489
 8490        editor
 8491            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8492            .unwrap();
 8493
 8494        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8495            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8496            assert_eq!(editor.text(cx), expected_text);
 8497            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8498        }
 8499
 8500        assert(
 8501            editor,
 8502            cx,
 8503            indoc! {"
 8504            type «» =•
 8505            "},
 8506        );
 8507
 8508        assert!(editor.context_menu_visible(), "There should be a matches");
 8509    });
 8510}
 8511
 8512#[gpui::test]
 8513async fn test_snippets(cx: &mut TestAppContext) {
 8514    init_test(cx, |_| {});
 8515
 8516    let (text, insertion_ranges) = marked_text_ranges(
 8517        indoc! {"
 8518            a.ˇ b
 8519            a.ˇ b
 8520            a.ˇ b
 8521        "},
 8522        false,
 8523    );
 8524
 8525    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8526    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8527
 8528    editor.update_in(cx, |editor, window, cx| {
 8529        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8530
 8531        editor
 8532            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8533            .unwrap();
 8534
 8535        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8536            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8537            assert_eq!(editor.text(cx), expected_text);
 8538            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8539        }
 8540
 8541        assert(
 8542            editor,
 8543            cx,
 8544            indoc! {"
 8545                a.f(«one», two, «three») b
 8546                a.f(«one», two, «three») b
 8547                a.f(«one», two, «three») b
 8548            "},
 8549        );
 8550
 8551        // Can't move earlier than the first tab stop
 8552        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 8553        assert(
 8554            editor,
 8555            cx,
 8556            indoc! {"
 8557                a.f(«one», two, «three») b
 8558                a.f(«one», two, «three») b
 8559                a.f(«one», two, «three») b
 8560            "},
 8561        );
 8562
 8563        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8564        assert(
 8565            editor,
 8566            cx,
 8567            indoc! {"
 8568                a.f(one, «two», three) b
 8569                a.f(one, «two», three) b
 8570                a.f(one, «two», three) b
 8571            "},
 8572        );
 8573
 8574        editor.move_to_prev_snippet_tabstop(window, cx);
 8575        assert(
 8576            editor,
 8577            cx,
 8578            indoc! {"
 8579                a.f(«one», two, «three») b
 8580                a.f(«one», two, «three») b
 8581                a.f(«one», two, «three») b
 8582            "},
 8583        );
 8584
 8585        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8586        assert(
 8587            editor,
 8588            cx,
 8589            indoc! {"
 8590                a.f(one, «two», three) b
 8591                a.f(one, «two», three) b
 8592                a.f(one, «two», three) b
 8593            "},
 8594        );
 8595        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8596        assert(
 8597            editor,
 8598            cx,
 8599            indoc! {"
 8600                a.f(one, two, three)ˇ b
 8601                a.f(one, two, three)ˇ b
 8602                a.f(one, two, three)ˇ b
 8603            "},
 8604        );
 8605
 8606        // As soon as the last tab stop is reached, snippet state is gone
 8607        editor.move_to_prev_snippet_tabstop(window, cx);
 8608        assert(
 8609            editor,
 8610            cx,
 8611            indoc! {"
 8612                a.f(one, two, three)ˇ b
 8613                a.f(one, two, three)ˇ b
 8614                a.f(one, two, three)ˇ b
 8615            "},
 8616        );
 8617    });
 8618}
 8619
 8620#[gpui::test]
 8621async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8622    init_test(cx, |_| {});
 8623
 8624    let fs = FakeFs::new(cx.executor());
 8625    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8626
 8627    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8628
 8629    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8630    language_registry.add(rust_lang());
 8631    let mut fake_servers = language_registry.register_fake_lsp(
 8632        "Rust",
 8633        FakeLspAdapter {
 8634            capabilities: lsp::ServerCapabilities {
 8635                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8636                ..Default::default()
 8637            },
 8638            ..Default::default()
 8639        },
 8640    );
 8641
 8642    let buffer = project
 8643        .update(cx, |project, cx| {
 8644            project.open_local_buffer(path!("/file.rs"), cx)
 8645        })
 8646        .await
 8647        .unwrap();
 8648
 8649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8650    let (editor, cx) = cx.add_window_view(|window, cx| {
 8651        build_editor_with_project(project.clone(), buffer, window, cx)
 8652    });
 8653    editor.update_in(cx, |editor, window, cx| {
 8654        editor.set_text("one\ntwo\nthree\n", window, cx)
 8655    });
 8656    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8657
 8658    cx.executor().start_waiting();
 8659    let fake_server = fake_servers.next().await.unwrap();
 8660
 8661    {
 8662        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8663            move |params, _| async move {
 8664                assert_eq!(
 8665                    params.text_document.uri,
 8666                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8667                );
 8668                assert_eq!(params.options.tab_size, 4);
 8669                Ok(Some(vec![lsp::TextEdit::new(
 8670                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8671                    ", ".to_string(),
 8672                )]))
 8673            },
 8674        );
 8675        let save = editor
 8676            .update_in(cx, |editor, window, cx| {
 8677                editor.save(true, project.clone(), window, cx)
 8678            })
 8679            .unwrap();
 8680        cx.executor().start_waiting();
 8681        save.await;
 8682
 8683        assert_eq!(
 8684            editor.update(cx, |editor, cx| editor.text(cx)),
 8685            "one, two\nthree\n"
 8686        );
 8687        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8688    }
 8689
 8690    {
 8691        editor.update_in(cx, |editor, window, cx| {
 8692            editor.set_text("one\ntwo\nthree\n", window, cx)
 8693        });
 8694        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8695
 8696        // Ensure we can still save even if formatting hangs.
 8697        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8698            move |params, _| async move {
 8699                assert_eq!(
 8700                    params.text_document.uri,
 8701                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8702                );
 8703                futures::future::pending::<()>().await;
 8704                unreachable!()
 8705            },
 8706        );
 8707        let save = editor
 8708            .update_in(cx, |editor, window, cx| {
 8709                editor.save(true, project.clone(), window, cx)
 8710            })
 8711            .unwrap();
 8712        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8713        cx.executor().start_waiting();
 8714        save.await;
 8715        assert_eq!(
 8716            editor.update(cx, |editor, cx| editor.text(cx)),
 8717            "one\ntwo\nthree\n"
 8718        );
 8719    }
 8720
 8721    // For non-dirty buffer, no formatting request should be sent
 8722    {
 8723        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8724
 8725        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8726            panic!("Should not be invoked on non-dirty buffer");
 8727        });
 8728        let save = editor
 8729            .update_in(cx, |editor, window, cx| {
 8730                editor.save(true, project.clone(), window, cx)
 8731            })
 8732            .unwrap();
 8733        cx.executor().start_waiting();
 8734        save.await;
 8735    }
 8736
 8737    // Set rust language override and assert overridden tabsize is sent to language server
 8738    update_test_language_settings(cx, |settings| {
 8739        settings.languages.insert(
 8740            "Rust".into(),
 8741            LanguageSettingsContent {
 8742                tab_size: NonZeroU32::new(8),
 8743                ..Default::default()
 8744            },
 8745        );
 8746    });
 8747
 8748    {
 8749        editor.update_in(cx, |editor, window, cx| {
 8750            editor.set_text("somehting_new\n", window, cx)
 8751        });
 8752        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8753        let _formatting_request_signal = fake_server
 8754            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8755                assert_eq!(
 8756                    params.text_document.uri,
 8757                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8758                );
 8759                assert_eq!(params.options.tab_size, 8);
 8760                Ok(Some(vec![]))
 8761            });
 8762        let save = editor
 8763            .update_in(cx, |editor, window, cx| {
 8764                editor.save(true, project.clone(), window, cx)
 8765            })
 8766            .unwrap();
 8767        cx.executor().start_waiting();
 8768        save.await;
 8769    }
 8770}
 8771
 8772#[gpui::test]
 8773async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8774    init_test(cx, |_| {});
 8775
 8776    let cols = 4;
 8777    let rows = 10;
 8778    let sample_text_1 = sample_text(rows, cols, 'a');
 8779    assert_eq!(
 8780        sample_text_1,
 8781        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8782    );
 8783    let sample_text_2 = sample_text(rows, cols, 'l');
 8784    assert_eq!(
 8785        sample_text_2,
 8786        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8787    );
 8788    let sample_text_3 = sample_text(rows, cols, 'v');
 8789    assert_eq!(
 8790        sample_text_3,
 8791        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8792    );
 8793
 8794    let fs = FakeFs::new(cx.executor());
 8795    fs.insert_tree(
 8796        path!("/a"),
 8797        json!({
 8798            "main.rs": sample_text_1,
 8799            "other.rs": sample_text_2,
 8800            "lib.rs": sample_text_3,
 8801        }),
 8802    )
 8803    .await;
 8804
 8805    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8806    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8807    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8808
 8809    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8810    language_registry.add(rust_lang());
 8811    let mut fake_servers = language_registry.register_fake_lsp(
 8812        "Rust",
 8813        FakeLspAdapter {
 8814            capabilities: lsp::ServerCapabilities {
 8815                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8816                ..Default::default()
 8817            },
 8818            ..Default::default()
 8819        },
 8820    );
 8821
 8822    let worktree = project.update(cx, |project, cx| {
 8823        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8824        assert_eq!(worktrees.len(), 1);
 8825        worktrees.pop().unwrap()
 8826    });
 8827    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8828
 8829    let buffer_1 = project
 8830        .update(cx, |project, cx| {
 8831            project.open_buffer((worktree_id, "main.rs"), cx)
 8832        })
 8833        .await
 8834        .unwrap();
 8835    let buffer_2 = project
 8836        .update(cx, |project, cx| {
 8837            project.open_buffer((worktree_id, "other.rs"), cx)
 8838        })
 8839        .await
 8840        .unwrap();
 8841    let buffer_3 = project
 8842        .update(cx, |project, cx| {
 8843            project.open_buffer((worktree_id, "lib.rs"), cx)
 8844        })
 8845        .await
 8846        .unwrap();
 8847
 8848    let multi_buffer = cx.new(|cx| {
 8849        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8850        multi_buffer.push_excerpts(
 8851            buffer_1.clone(),
 8852            [
 8853                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8854                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8855                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8856            ],
 8857            cx,
 8858        );
 8859        multi_buffer.push_excerpts(
 8860            buffer_2.clone(),
 8861            [
 8862                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8863                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8864                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8865            ],
 8866            cx,
 8867        );
 8868        multi_buffer.push_excerpts(
 8869            buffer_3.clone(),
 8870            [
 8871                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8872                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8873                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8874            ],
 8875            cx,
 8876        );
 8877        multi_buffer
 8878    });
 8879    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8880        Editor::new(
 8881            EditorMode::full(),
 8882            multi_buffer,
 8883            Some(project.clone()),
 8884            window,
 8885            cx,
 8886        )
 8887    });
 8888
 8889    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8890        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8891            s.select_ranges(Some(1..2))
 8892        });
 8893        editor.insert("|one|two|three|", window, cx);
 8894    });
 8895    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8896    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8897        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8898            s.select_ranges(Some(60..70))
 8899        });
 8900        editor.insert("|four|five|six|", window, cx);
 8901    });
 8902    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8903
 8904    // First two buffers should be edited, but not the third one.
 8905    assert_eq!(
 8906        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8907        "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}",
 8908    );
 8909    buffer_1.update(cx, |buffer, _| {
 8910        assert!(buffer.is_dirty());
 8911        assert_eq!(
 8912            buffer.text(),
 8913            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8914        )
 8915    });
 8916    buffer_2.update(cx, |buffer, _| {
 8917        assert!(buffer.is_dirty());
 8918        assert_eq!(
 8919            buffer.text(),
 8920            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8921        )
 8922    });
 8923    buffer_3.update(cx, |buffer, _| {
 8924        assert!(!buffer.is_dirty());
 8925        assert_eq!(buffer.text(), sample_text_3,)
 8926    });
 8927    cx.executor().run_until_parked();
 8928
 8929    cx.executor().start_waiting();
 8930    let save = multi_buffer_editor
 8931        .update_in(cx, |editor, window, cx| {
 8932            editor.save(true, project.clone(), window, cx)
 8933        })
 8934        .unwrap();
 8935
 8936    let fake_server = fake_servers.next().await.unwrap();
 8937    fake_server
 8938        .server
 8939        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8940            Ok(Some(vec![lsp::TextEdit::new(
 8941                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8942                format!("[{} formatted]", params.text_document.uri),
 8943            )]))
 8944        })
 8945        .detach();
 8946    save.await;
 8947
 8948    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8949    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8950    assert_eq!(
 8951        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8952        uri!(
 8953            "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}"
 8954        ),
 8955    );
 8956    buffer_1.update(cx, |buffer, _| {
 8957        assert!(!buffer.is_dirty());
 8958        assert_eq!(
 8959            buffer.text(),
 8960            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8961        )
 8962    });
 8963    buffer_2.update(cx, |buffer, _| {
 8964        assert!(!buffer.is_dirty());
 8965        assert_eq!(
 8966            buffer.text(),
 8967            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8968        )
 8969    });
 8970    buffer_3.update(cx, |buffer, _| {
 8971        assert!(!buffer.is_dirty());
 8972        assert_eq!(buffer.text(), sample_text_3,)
 8973    });
 8974}
 8975
 8976#[gpui::test]
 8977async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8978    init_test(cx, |_| {});
 8979
 8980    let fs = FakeFs::new(cx.executor());
 8981    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8982
 8983    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8984
 8985    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8986    language_registry.add(rust_lang());
 8987    let mut fake_servers = language_registry.register_fake_lsp(
 8988        "Rust",
 8989        FakeLspAdapter {
 8990            capabilities: lsp::ServerCapabilities {
 8991                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8992                ..Default::default()
 8993            },
 8994            ..Default::default()
 8995        },
 8996    );
 8997
 8998    let buffer = project
 8999        .update(cx, |project, cx| {
 9000            project.open_local_buffer(path!("/file.rs"), cx)
 9001        })
 9002        .await
 9003        .unwrap();
 9004
 9005    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9006    let (editor, cx) = cx.add_window_view(|window, cx| {
 9007        build_editor_with_project(project.clone(), buffer, window, cx)
 9008    });
 9009    editor.update_in(cx, |editor, window, cx| {
 9010        editor.set_text("one\ntwo\nthree\n", window, cx)
 9011    });
 9012    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9013
 9014    cx.executor().start_waiting();
 9015    let fake_server = fake_servers.next().await.unwrap();
 9016
 9017    let save = editor
 9018        .update_in(cx, |editor, window, cx| {
 9019            editor.save(true, project.clone(), window, cx)
 9020        })
 9021        .unwrap();
 9022    fake_server
 9023        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9024            assert_eq!(
 9025                params.text_document.uri,
 9026                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9027            );
 9028            assert_eq!(params.options.tab_size, 4);
 9029            Ok(Some(vec![lsp::TextEdit::new(
 9030                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9031                ", ".to_string(),
 9032            )]))
 9033        })
 9034        .next()
 9035        .await;
 9036    cx.executor().start_waiting();
 9037    save.await;
 9038    assert_eq!(
 9039        editor.update(cx, |editor, cx| editor.text(cx)),
 9040        "one, two\nthree\n"
 9041    );
 9042    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9043
 9044    editor.update_in(cx, |editor, window, cx| {
 9045        editor.set_text("one\ntwo\nthree\n", window, cx)
 9046    });
 9047    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9048
 9049    // Ensure we can still save even if formatting hangs.
 9050    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9051        move |params, _| async move {
 9052            assert_eq!(
 9053                params.text_document.uri,
 9054                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9055            );
 9056            futures::future::pending::<()>().await;
 9057            unreachable!()
 9058        },
 9059    );
 9060    let save = editor
 9061        .update_in(cx, |editor, window, cx| {
 9062            editor.save(true, project.clone(), window, cx)
 9063        })
 9064        .unwrap();
 9065    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9066    cx.executor().start_waiting();
 9067    save.await;
 9068    assert_eq!(
 9069        editor.update(cx, |editor, cx| editor.text(cx)),
 9070        "one\ntwo\nthree\n"
 9071    );
 9072    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9073
 9074    // For non-dirty buffer, no formatting request should be sent
 9075    let save = editor
 9076        .update_in(cx, |editor, window, cx| {
 9077            editor.save(true, project.clone(), window, cx)
 9078        })
 9079        .unwrap();
 9080    let _pending_format_request = fake_server
 9081        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9082            panic!("Should not be invoked on non-dirty buffer");
 9083        })
 9084        .next();
 9085    cx.executor().start_waiting();
 9086    save.await;
 9087
 9088    // Set Rust language override and assert overridden tabsize is sent to language server
 9089    update_test_language_settings(cx, |settings| {
 9090        settings.languages.insert(
 9091            "Rust".into(),
 9092            LanguageSettingsContent {
 9093                tab_size: NonZeroU32::new(8),
 9094                ..Default::default()
 9095            },
 9096        );
 9097    });
 9098
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        editor.set_text("somehting_new\n", window, cx)
 9101    });
 9102    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9103    let save = editor
 9104        .update_in(cx, |editor, window, cx| {
 9105            editor.save(true, project.clone(), window, cx)
 9106        })
 9107        .unwrap();
 9108    fake_server
 9109        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9110            assert_eq!(
 9111                params.text_document.uri,
 9112                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9113            );
 9114            assert_eq!(params.options.tab_size, 8);
 9115            Ok(Some(Vec::new()))
 9116        })
 9117        .next()
 9118        .await;
 9119    save.await;
 9120}
 9121
 9122#[gpui::test]
 9123async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9124    init_test(cx, |settings| {
 9125        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9126            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9127        ))
 9128    });
 9129
 9130    let fs = FakeFs::new(cx.executor());
 9131    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9132
 9133    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9134
 9135    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9136    language_registry.add(Arc::new(Language::new(
 9137        LanguageConfig {
 9138            name: "Rust".into(),
 9139            matcher: LanguageMatcher {
 9140                path_suffixes: vec!["rs".to_string()],
 9141                ..Default::default()
 9142            },
 9143            ..LanguageConfig::default()
 9144        },
 9145        Some(tree_sitter_rust::LANGUAGE.into()),
 9146    )));
 9147    update_test_language_settings(cx, |settings| {
 9148        // Enable Prettier formatting for the same buffer, and ensure
 9149        // LSP is called instead of Prettier.
 9150        settings.defaults.prettier = Some(PrettierSettings {
 9151            allowed: true,
 9152            ..PrettierSettings::default()
 9153        });
 9154    });
 9155    let mut fake_servers = language_registry.register_fake_lsp(
 9156        "Rust",
 9157        FakeLspAdapter {
 9158            capabilities: lsp::ServerCapabilities {
 9159                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9160                ..Default::default()
 9161            },
 9162            ..Default::default()
 9163        },
 9164    );
 9165
 9166    let buffer = project
 9167        .update(cx, |project, cx| {
 9168            project.open_local_buffer(path!("/file.rs"), cx)
 9169        })
 9170        .await
 9171        .unwrap();
 9172
 9173    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9174    let (editor, cx) = cx.add_window_view(|window, cx| {
 9175        build_editor_with_project(project.clone(), buffer, window, cx)
 9176    });
 9177    editor.update_in(cx, |editor, window, cx| {
 9178        editor.set_text("one\ntwo\nthree\n", window, cx)
 9179    });
 9180
 9181    cx.executor().start_waiting();
 9182    let fake_server = fake_servers.next().await.unwrap();
 9183
 9184    let format = editor
 9185        .update_in(cx, |editor, window, cx| {
 9186            editor.perform_format(
 9187                project.clone(),
 9188                FormatTrigger::Manual,
 9189                FormatTarget::Buffers,
 9190                window,
 9191                cx,
 9192            )
 9193        })
 9194        .unwrap();
 9195    fake_server
 9196        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9197            assert_eq!(
 9198                params.text_document.uri,
 9199                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9200            );
 9201            assert_eq!(params.options.tab_size, 4);
 9202            Ok(Some(vec![lsp::TextEdit::new(
 9203                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9204                ", ".to_string(),
 9205            )]))
 9206        })
 9207        .next()
 9208        .await;
 9209    cx.executor().start_waiting();
 9210    format.await;
 9211    assert_eq!(
 9212        editor.update(cx, |editor, cx| editor.text(cx)),
 9213        "one, two\nthree\n"
 9214    );
 9215
 9216    editor.update_in(cx, |editor, window, cx| {
 9217        editor.set_text("one\ntwo\nthree\n", window, cx)
 9218    });
 9219    // Ensure we don't lock if formatting hangs.
 9220    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9221        move |params, _| async move {
 9222            assert_eq!(
 9223                params.text_document.uri,
 9224                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9225            );
 9226            futures::future::pending::<()>().await;
 9227            unreachable!()
 9228        },
 9229    );
 9230    let format = editor
 9231        .update_in(cx, |editor, window, cx| {
 9232            editor.perform_format(
 9233                project,
 9234                FormatTrigger::Manual,
 9235                FormatTarget::Buffers,
 9236                window,
 9237                cx,
 9238            )
 9239        })
 9240        .unwrap();
 9241    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9242    cx.executor().start_waiting();
 9243    format.await;
 9244    assert_eq!(
 9245        editor.update(cx, |editor, cx| editor.text(cx)),
 9246        "one\ntwo\nthree\n"
 9247    );
 9248}
 9249
 9250#[gpui::test]
 9251async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9252    init_test(cx, |settings| {
 9253        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9254        settings.defaults.formatter =
 9255            Some(language_settings::SelectedFormatter::List(FormatterList(
 9256                vec![
 9257                    Formatter::LanguageServer { name: None },
 9258                    Formatter::CodeActions(
 9259                        [
 9260                            ("code-action-1".into(), true),
 9261                            ("code-action-2".into(), true),
 9262                        ]
 9263                        .into_iter()
 9264                        .collect(),
 9265                    ),
 9266                ]
 9267                .into(),
 9268            )))
 9269    });
 9270
 9271    let fs = FakeFs::new(cx.executor());
 9272    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9273        .await;
 9274
 9275    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9276    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9277    language_registry.add(rust_lang());
 9278
 9279    let mut fake_servers = language_registry.register_fake_lsp(
 9280        "Rust",
 9281        FakeLspAdapter {
 9282            capabilities: lsp::ServerCapabilities {
 9283                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9284                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9285                    commands: vec!["the-command-for-code-action-1".into()],
 9286                    ..Default::default()
 9287                }),
 9288                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9289                ..Default::default()
 9290            },
 9291            ..Default::default()
 9292        },
 9293    );
 9294
 9295    let buffer = project
 9296        .update(cx, |project, cx| {
 9297            project.open_local_buffer(path!("/file.rs"), cx)
 9298        })
 9299        .await
 9300        .unwrap();
 9301
 9302    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9303    let (editor, cx) = cx.add_window_view(|window, cx| {
 9304        build_editor_with_project(project.clone(), buffer, window, cx)
 9305    });
 9306
 9307    cx.executor().start_waiting();
 9308
 9309    let fake_server = fake_servers.next().await.unwrap();
 9310    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9311        move |_params, _| async move {
 9312            Ok(Some(vec![lsp::TextEdit::new(
 9313                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9314                "applied-formatting\n".to_string(),
 9315            )]))
 9316        },
 9317    );
 9318    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9319        move |params, _| async move {
 9320            assert_eq!(
 9321                params.context.only,
 9322                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9323            );
 9324            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9325            Ok(Some(vec![
 9326                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9327                    kind: Some("code-action-1".into()),
 9328                    edit: Some(lsp::WorkspaceEdit::new(
 9329                        [(
 9330                            uri.clone(),
 9331                            vec![lsp::TextEdit::new(
 9332                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9333                                "applied-code-action-1-edit\n".to_string(),
 9334                            )],
 9335                        )]
 9336                        .into_iter()
 9337                        .collect(),
 9338                    )),
 9339                    command: Some(lsp::Command {
 9340                        command: "the-command-for-code-action-1".into(),
 9341                        ..Default::default()
 9342                    }),
 9343                    ..Default::default()
 9344                }),
 9345                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9346                    kind: Some("code-action-2".into()),
 9347                    edit: Some(lsp::WorkspaceEdit::new(
 9348                        [(
 9349                            uri.clone(),
 9350                            vec![lsp::TextEdit::new(
 9351                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9352                                "applied-code-action-2-edit\n".to_string(),
 9353                            )],
 9354                        )]
 9355                        .into_iter()
 9356                        .collect(),
 9357                    )),
 9358                    ..Default::default()
 9359                }),
 9360            ]))
 9361        },
 9362    );
 9363
 9364    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9365        move |params, _| async move { Ok(params) }
 9366    });
 9367
 9368    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9369    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9370        let fake = fake_server.clone();
 9371        let lock = command_lock.clone();
 9372        move |params, _| {
 9373            assert_eq!(params.command, "the-command-for-code-action-1");
 9374            let fake = fake.clone();
 9375            let lock = lock.clone();
 9376            async move {
 9377                lock.lock().await;
 9378                fake.server
 9379                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9380                        label: None,
 9381                        edit: lsp::WorkspaceEdit {
 9382                            changes: Some(
 9383                                [(
 9384                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9385                                    vec![lsp::TextEdit {
 9386                                        range: lsp::Range::new(
 9387                                            lsp::Position::new(0, 0),
 9388                                            lsp::Position::new(0, 0),
 9389                                        ),
 9390                                        new_text: "applied-code-action-1-command\n".into(),
 9391                                    }],
 9392                                )]
 9393                                .into_iter()
 9394                                .collect(),
 9395                            ),
 9396                            ..Default::default()
 9397                        },
 9398                    })
 9399                    .await
 9400                    .into_response()
 9401                    .unwrap();
 9402                Ok(Some(json!(null)))
 9403            }
 9404        }
 9405    });
 9406
 9407    cx.executor().start_waiting();
 9408    editor
 9409        .update_in(cx, |editor, window, cx| {
 9410            editor.perform_format(
 9411                project.clone(),
 9412                FormatTrigger::Manual,
 9413                FormatTarget::Buffers,
 9414                window,
 9415                cx,
 9416            )
 9417        })
 9418        .unwrap()
 9419        .await;
 9420    editor.update(cx, |editor, cx| {
 9421        assert_eq!(
 9422            editor.text(cx),
 9423            r#"
 9424                applied-code-action-2-edit
 9425                applied-code-action-1-command
 9426                applied-code-action-1-edit
 9427                applied-formatting
 9428                one
 9429                two
 9430                three
 9431            "#
 9432            .unindent()
 9433        );
 9434    });
 9435
 9436    editor.update_in(cx, |editor, window, cx| {
 9437        editor.undo(&Default::default(), window, cx);
 9438        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9439    });
 9440
 9441    // Perform a manual edit while waiting for an LSP command
 9442    // that's being run as part of a formatting code action.
 9443    let lock_guard = command_lock.lock().await;
 9444    let format = editor
 9445        .update_in(cx, |editor, window, cx| {
 9446            editor.perform_format(
 9447                project.clone(),
 9448                FormatTrigger::Manual,
 9449                FormatTarget::Buffers,
 9450                window,
 9451                cx,
 9452            )
 9453        })
 9454        .unwrap();
 9455    cx.run_until_parked();
 9456    editor.update(cx, |editor, cx| {
 9457        assert_eq!(
 9458            editor.text(cx),
 9459            r#"
 9460                applied-code-action-1-edit
 9461                applied-formatting
 9462                one
 9463                two
 9464                three
 9465            "#
 9466            .unindent()
 9467        );
 9468
 9469        editor.buffer.update(cx, |buffer, cx| {
 9470            let ix = buffer.len(cx);
 9471            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9472        });
 9473    });
 9474
 9475    // Allow the LSP command to proceed. Because the buffer was edited,
 9476    // the second code action will not be run.
 9477    drop(lock_guard);
 9478    format.await;
 9479    editor.update_in(cx, |editor, window, cx| {
 9480        assert_eq!(
 9481            editor.text(cx),
 9482            r#"
 9483                applied-code-action-1-command
 9484                applied-code-action-1-edit
 9485                applied-formatting
 9486                one
 9487                two
 9488                three
 9489                edited
 9490            "#
 9491            .unindent()
 9492        );
 9493
 9494        // The manual edit is undone first, because it is the last thing the user did
 9495        // (even though the command completed afterwards).
 9496        editor.undo(&Default::default(), window, cx);
 9497        assert_eq!(
 9498            editor.text(cx),
 9499            r#"
 9500                applied-code-action-1-command
 9501                applied-code-action-1-edit
 9502                applied-formatting
 9503                one
 9504                two
 9505                three
 9506            "#
 9507            .unindent()
 9508        );
 9509
 9510        // All the formatting (including the command, which completed after the manual edit)
 9511        // is undone together.
 9512        editor.undo(&Default::default(), window, cx);
 9513        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9514    });
 9515}
 9516
 9517#[gpui::test]
 9518async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9519    init_test(cx, |settings| {
 9520        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9521            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9522        ))
 9523    });
 9524
 9525    let fs = FakeFs::new(cx.executor());
 9526    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9527
 9528    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9529
 9530    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9531    language_registry.add(Arc::new(Language::new(
 9532        LanguageConfig {
 9533            name: "TypeScript".into(),
 9534            matcher: LanguageMatcher {
 9535                path_suffixes: vec!["ts".to_string()],
 9536                ..Default::default()
 9537            },
 9538            ..LanguageConfig::default()
 9539        },
 9540        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9541    )));
 9542    update_test_language_settings(cx, |settings| {
 9543        settings.defaults.prettier = Some(PrettierSettings {
 9544            allowed: true,
 9545            ..PrettierSettings::default()
 9546        });
 9547    });
 9548    let mut fake_servers = language_registry.register_fake_lsp(
 9549        "TypeScript",
 9550        FakeLspAdapter {
 9551            capabilities: lsp::ServerCapabilities {
 9552                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9553                ..Default::default()
 9554            },
 9555            ..Default::default()
 9556        },
 9557    );
 9558
 9559    let buffer = project
 9560        .update(cx, |project, cx| {
 9561            project.open_local_buffer(path!("/file.ts"), cx)
 9562        })
 9563        .await
 9564        .unwrap();
 9565
 9566    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9567    let (editor, cx) = cx.add_window_view(|window, cx| {
 9568        build_editor_with_project(project.clone(), buffer, window, cx)
 9569    });
 9570    editor.update_in(cx, |editor, window, cx| {
 9571        editor.set_text(
 9572            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9573            window,
 9574            cx,
 9575        )
 9576    });
 9577
 9578    cx.executor().start_waiting();
 9579    let fake_server = fake_servers.next().await.unwrap();
 9580
 9581    let format = editor
 9582        .update_in(cx, |editor, window, cx| {
 9583            editor.perform_code_action_kind(
 9584                project.clone(),
 9585                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9586                window,
 9587                cx,
 9588            )
 9589        })
 9590        .unwrap();
 9591    fake_server
 9592        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9593            assert_eq!(
 9594                params.text_document.uri,
 9595                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9596            );
 9597            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9598                lsp::CodeAction {
 9599                    title: "Organize Imports".to_string(),
 9600                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9601                    edit: Some(lsp::WorkspaceEdit {
 9602                        changes: Some(
 9603                            [(
 9604                                params.text_document.uri.clone(),
 9605                                vec![lsp::TextEdit::new(
 9606                                    lsp::Range::new(
 9607                                        lsp::Position::new(1, 0),
 9608                                        lsp::Position::new(2, 0),
 9609                                    ),
 9610                                    "".to_string(),
 9611                                )],
 9612                            )]
 9613                            .into_iter()
 9614                            .collect(),
 9615                        ),
 9616                        ..Default::default()
 9617                    }),
 9618                    ..Default::default()
 9619                },
 9620            )]))
 9621        })
 9622        .next()
 9623        .await;
 9624    cx.executor().start_waiting();
 9625    format.await;
 9626    assert_eq!(
 9627        editor.update(cx, |editor, cx| editor.text(cx)),
 9628        "import { a } from 'module';\n\nconst x = a;\n"
 9629    );
 9630
 9631    editor.update_in(cx, |editor, window, cx| {
 9632        editor.set_text(
 9633            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9634            window,
 9635            cx,
 9636        )
 9637    });
 9638    // Ensure we don't lock if code action hangs.
 9639    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9640        move |params, _| async move {
 9641            assert_eq!(
 9642                params.text_document.uri,
 9643                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9644            );
 9645            futures::future::pending::<()>().await;
 9646            unreachable!()
 9647        },
 9648    );
 9649    let format = editor
 9650        .update_in(cx, |editor, window, cx| {
 9651            editor.perform_code_action_kind(
 9652                project,
 9653                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9654                window,
 9655                cx,
 9656            )
 9657        })
 9658        .unwrap();
 9659    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9660    cx.executor().start_waiting();
 9661    format.await;
 9662    assert_eq!(
 9663        editor.update(cx, |editor, cx| editor.text(cx)),
 9664        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9665    );
 9666}
 9667
 9668#[gpui::test]
 9669async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9670    init_test(cx, |_| {});
 9671
 9672    let mut cx = EditorLspTestContext::new_rust(
 9673        lsp::ServerCapabilities {
 9674            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9675            ..Default::default()
 9676        },
 9677        cx,
 9678    )
 9679    .await;
 9680
 9681    cx.set_state(indoc! {"
 9682        one.twoˇ
 9683    "});
 9684
 9685    // The format request takes a long time. When it completes, it inserts
 9686    // a newline and an indent before the `.`
 9687    cx.lsp
 9688        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9689            let executor = cx.background_executor().clone();
 9690            async move {
 9691                executor.timer(Duration::from_millis(100)).await;
 9692                Ok(Some(vec![lsp::TextEdit {
 9693                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9694                    new_text: "\n    ".into(),
 9695                }]))
 9696            }
 9697        });
 9698
 9699    // Submit a format request.
 9700    let format_1 = cx
 9701        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9702        .unwrap();
 9703    cx.executor().run_until_parked();
 9704
 9705    // Submit a second format request.
 9706    let format_2 = cx
 9707        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9708        .unwrap();
 9709    cx.executor().run_until_parked();
 9710
 9711    // Wait for both format requests to complete
 9712    cx.executor().advance_clock(Duration::from_millis(200));
 9713    cx.executor().start_waiting();
 9714    format_1.await.unwrap();
 9715    cx.executor().start_waiting();
 9716    format_2.await.unwrap();
 9717
 9718    // The formatting edits only happens once.
 9719    cx.assert_editor_state(indoc! {"
 9720        one
 9721            .twoˇ
 9722    "});
 9723}
 9724
 9725#[gpui::test]
 9726async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9727    init_test(cx, |settings| {
 9728        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9729    });
 9730
 9731    let mut cx = EditorLspTestContext::new_rust(
 9732        lsp::ServerCapabilities {
 9733            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9734            ..Default::default()
 9735        },
 9736        cx,
 9737    )
 9738    .await;
 9739
 9740    // Set up a buffer white some trailing whitespace and no trailing newline.
 9741    cx.set_state(
 9742        &[
 9743            "one ",   //
 9744            "twoˇ",   //
 9745            "three ", //
 9746            "four",   //
 9747        ]
 9748        .join("\n"),
 9749    );
 9750
 9751    // Submit a format request.
 9752    let format = cx
 9753        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9754        .unwrap();
 9755
 9756    // Record which buffer changes have been sent to the language server
 9757    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9758    cx.lsp
 9759        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9760            let buffer_changes = buffer_changes.clone();
 9761            move |params, _| {
 9762                buffer_changes.lock().extend(
 9763                    params
 9764                        .content_changes
 9765                        .into_iter()
 9766                        .map(|e| (e.range.unwrap(), e.text)),
 9767                );
 9768            }
 9769        });
 9770
 9771    // Handle formatting requests to the language server.
 9772    cx.lsp
 9773        .set_request_handler::<lsp::request::Formatting, _, _>({
 9774            let buffer_changes = buffer_changes.clone();
 9775            move |_, _| {
 9776                // When formatting is requested, trailing whitespace has already been stripped,
 9777                // and the trailing newline has already been added.
 9778                assert_eq!(
 9779                    &buffer_changes.lock()[1..],
 9780                    &[
 9781                        (
 9782                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9783                            "".into()
 9784                        ),
 9785                        (
 9786                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9787                            "".into()
 9788                        ),
 9789                        (
 9790                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9791                            "\n".into()
 9792                        ),
 9793                    ]
 9794                );
 9795
 9796                // Insert blank lines between each line of the buffer.
 9797                async move {
 9798                    Ok(Some(vec![
 9799                        lsp::TextEdit {
 9800                            range: lsp::Range::new(
 9801                                lsp::Position::new(1, 0),
 9802                                lsp::Position::new(1, 0),
 9803                            ),
 9804                            new_text: "\n".into(),
 9805                        },
 9806                        lsp::TextEdit {
 9807                            range: lsp::Range::new(
 9808                                lsp::Position::new(2, 0),
 9809                                lsp::Position::new(2, 0),
 9810                            ),
 9811                            new_text: "\n".into(),
 9812                        },
 9813                    ]))
 9814                }
 9815            }
 9816        });
 9817
 9818    // After formatting the buffer, the trailing whitespace is stripped,
 9819    // a newline is appended, and the edits provided by the language server
 9820    // have been applied.
 9821    format.await.unwrap();
 9822    cx.assert_editor_state(
 9823        &[
 9824            "one",   //
 9825            "",      //
 9826            "twoˇ",  //
 9827            "",      //
 9828            "three", //
 9829            "four",  //
 9830            "",      //
 9831        ]
 9832        .join("\n"),
 9833    );
 9834
 9835    // Undoing the formatting undoes the trailing whitespace removal, the
 9836    // trailing newline, and the LSP edits.
 9837    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9838    cx.assert_editor_state(
 9839        &[
 9840            "one ",   //
 9841            "twoˇ",   //
 9842            "three ", //
 9843            "four",   //
 9844        ]
 9845        .join("\n"),
 9846    );
 9847}
 9848
 9849#[gpui::test]
 9850async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9851    cx: &mut TestAppContext,
 9852) {
 9853    init_test(cx, |_| {});
 9854
 9855    cx.update(|cx| {
 9856        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9857            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9858                settings.auto_signature_help = Some(true);
 9859            });
 9860        });
 9861    });
 9862
 9863    let mut cx = EditorLspTestContext::new_rust(
 9864        lsp::ServerCapabilities {
 9865            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9866                ..Default::default()
 9867            }),
 9868            ..Default::default()
 9869        },
 9870        cx,
 9871    )
 9872    .await;
 9873
 9874    let language = Language::new(
 9875        LanguageConfig {
 9876            name: "Rust".into(),
 9877            brackets: BracketPairConfig {
 9878                pairs: vec![
 9879                    BracketPair {
 9880                        start: "{".to_string(),
 9881                        end: "}".to_string(),
 9882                        close: true,
 9883                        surround: true,
 9884                        newline: true,
 9885                    },
 9886                    BracketPair {
 9887                        start: "(".to_string(),
 9888                        end: ")".to_string(),
 9889                        close: true,
 9890                        surround: true,
 9891                        newline: true,
 9892                    },
 9893                    BracketPair {
 9894                        start: "/*".to_string(),
 9895                        end: " */".to_string(),
 9896                        close: true,
 9897                        surround: true,
 9898                        newline: true,
 9899                    },
 9900                    BracketPair {
 9901                        start: "[".to_string(),
 9902                        end: "]".to_string(),
 9903                        close: false,
 9904                        surround: false,
 9905                        newline: true,
 9906                    },
 9907                    BracketPair {
 9908                        start: "\"".to_string(),
 9909                        end: "\"".to_string(),
 9910                        close: true,
 9911                        surround: true,
 9912                        newline: false,
 9913                    },
 9914                    BracketPair {
 9915                        start: "<".to_string(),
 9916                        end: ">".to_string(),
 9917                        close: false,
 9918                        surround: true,
 9919                        newline: true,
 9920                    },
 9921                ],
 9922                ..Default::default()
 9923            },
 9924            autoclose_before: "})]".to_string(),
 9925            ..Default::default()
 9926        },
 9927        Some(tree_sitter_rust::LANGUAGE.into()),
 9928    );
 9929    let language = Arc::new(language);
 9930
 9931    cx.language_registry().add(language.clone());
 9932    cx.update_buffer(|buffer, cx| {
 9933        buffer.set_language(Some(language), cx);
 9934    });
 9935
 9936    cx.set_state(
 9937        &r#"
 9938            fn main() {
 9939                sampleˇ
 9940            }
 9941        "#
 9942        .unindent(),
 9943    );
 9944
 9945    cx.update_editor(|editor, window, cx| {
 9946        editor.handle_input("(", window, cx);
 9947    });
 9948    cx.assert_editor_state(
 9949        &"
 9950            fn main() {
 9951                sample(ˇ)
 9952            }
 9953        "
 9954        .unindent(),
 9955    );
 9956
 9957    let mocked_response = lsp::SignatureHelp {
 9958        signatures: vec![lsp::SignatureInformation {
 9959            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9960            documentation: None,
 9961            parameters: Some(vec![
 9962                lsp::ParameterInformation {
 9963                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9964                    documentation: None,
 9965                },
 9966                lsp::ParameterInformation {
 9967                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9968                    documentation: None,
 9969                },
 9970            ]),
 9971            active_parameter: None,
 9972        }],
 9973        active_signature: Some(0),
 9974        active_parameter: Some(0),
 9975    };
 9976    handle_signature_help_request(&mut cx, mocked_response).await;
 9977
 9978    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9979        .await;
 9980
 9981    cx.editor(|editor, _, _| {
 9982        let signature_help_state = editor.signature_help_state.popover().cloned();
 9983        assert_eq!(
 9984            signature_help_state.unwrap().label,
 9985            "param1: u8, param2: u8"
 9986        );
 9987    });
 9988}
 9989
 9990#[gpui::test]
 9991async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9992    init_test(cx, |_| {});
 9993
 9994    cx.update(|cx| {
 9995        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9996            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9997                settings.auto_signature_help = Some(false);
 9998                settings.show_signature_help_after_edits = Some(false);
 9999            });
10000        });
10001    });
10002
10003    let mut cx = EditorLspTestContext::new_rust(
10004        lsp::ServerCapabilities {
10005            signature_help_provider: Some(lsp::SignatureHelpOptions {
10006                ..Default::default()
10007            }),
10008            ..Default::default()
10009        },
10010        cx,
10011    )
10012    .await;
10013
10014    let language = Language::new(
10015        LanguageConfig {
10016            name: "Rust".into(),
10017            brackets: BracketPairConfig {
10018                pairs: vec![
10019                    BracketPair {
10020                        start: "{".to_string(),
10021                        end: "}".to_string(),
10022                        close: true,
10023                        surround: true,
10024                        newline: true,
10025                    },
10026                    BracketPair {
10027                        start: "(".to_string(),
10028                        end: ")".to_string(),
10029                        close: true,
10030                        surround: true,
10031                        newline: true,
10032                    },
10033                    BracketPair {
10034                        start: "/*".to_string(),
10035                        end: " */".to_string(),
10036                        close: true,
10037                        surround: true,
10038                        newline: true,
10039                    },
10040                    BracketPair {
10041                        start: "[".to_string(),
10042                        end: "]".to_string(),
10043                        close: false,
10044                        surround: false,
10045                        newline: true,
10046                    },
10047                    BracketPair {
10048                        start: "\"".to_string(),
10049                        end: "\"".to_string(),
10050                        close: true,
10051                        surround: true,
10052                        newline: false,
10053                    },
10054                    BracketPair {
10055                        start: "<".to_string(),
10056                        end: ">".to_string(),
10057                        close: false,
10058                        surround: true,
10059                        newline: true,
10060                    },
10061                ],
10062                ..Default::default()
10063            },
10064            autoclose_before: "})]".to_string(),
10065            ..Default::default()
10066        },
10067        Some(tree_sitter_rust::LANGUAGE.into()),
10068    );
10069    let language = Arc::new(language);
10070
10071    cx.language_registry().add(language.clone());
10072    cx.update_buffer(|buffer, cx| {
10073        buffer.set_language(Some(language), cx);
10074    });
10075
10076    // Ensure that signature_help is not called when no signature help is enabled.
10077    cx.set_state(
10078        &r#"
10079            fn main() {
10080                sampleˇ
10081            }
10082        "#
10083        .unindent(),
10084    );
10085    cx.update_editor(|editor, window, cx| {
10086        editor.handle_input("(", window, cx);
10087    });
10088    cx.assert_editor_state(
10089        &"
10090            fn main() {
10091                sample(ˇ)
10092            }
10093        "
10094        .unindent(),
10095    );
10096    cx.editor(|editor, _, _| {
10097        assert!(editor.signature_help_state.task().is_none());
10098    });
10099
10100    let mocked_response = lsp::SignatureHelp {
10101        signatures: vec![lsp::SignatureInformation {
10102            label: "fn sample(param1: u8, param2: u8)".to_string(),
10103            documentation: None,
10104            parameters: Some(vec![
10105                lsp::ParameterInformation {
10106                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10107                    documentation: None,
10108                },
10109                lsp::ParameterInformation {
10110                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10111                    documentation: None,
10112                },
10113            ]),
10114            active_parameter: None,
10115        }],
10116        active_signature: Some(0),
10117        active_parameter: Some(0),
10118    };
10119
10120    // Ensure that signature_help is called when enabled afte edits
10121    cx.update(|_, cx| {
10122        cx.update_global::<SettingsStore, _>(|settings, cx| {
10123            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10124                settings.auto_signature_help = Some(false);
10125                settings.show_signature_help_after_edits = Some(true);
10126            });
10127        });
10128    });
10129    cx.set_state(
10130        &r#"
10131            fn main() {
10132                sampleˇ
10133            }
10134        "#
10135        .unindent(),
10136    );
10137    cx.update_editor(|editor, window, cx| {
10138        editor.handle_input("(", window, cx);
10139    });
10140    cx.assert_editor_state(
10141        &"
10142            fn main() {
10143                sample(ˇ)
10144            }
10145        "
10146        .unindent(),
10147    );
10148    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10149    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10150        .await;
10151    cx.update_editor(|editor, _, _| {
10152        let signature_help_state = editor.signature_help_state.popover().cloned();
10153        assert!(signature_help_state.is_some());
10154        assert_eq!(
10155            signature_help_state.unwrap().label,
10156            "param1: u8, param2: u8"
10157        );
10158        editor.signature_help_state = SignatureHelpState::default();
10159    });
10160
10161    // Ensure that signature_help is called when auto signature help override is enabled
10162    cx.update(|_, cx| {
10163        cx.update_global::<SettingsStore, _>(|settings, cx| {
10164            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10165                settings.auto_signature_help = Some(true);
10166                settings.show_signature_help_after_edits = Some(false);
10167            });
10168        });
10169    });
10170    cx.set_state(
10171        &r#"
10172            fn main() {
10173                sampleˇ
10174            }
10175        "#
10176        .unindent(),
10177    );
10178    cx.update_editor(|editor, window, cx| {
10179        editor.handle_input("(", window, cx);
10180    });
10181    cx.assert_editor_state(
10182        &"
10183            fn main() {
10184                sample(ˇ)
10185            }
10186        "
10187        .unindent(),
10188    );
10189    handle_signature_help_request(&mut cx, mocked_response).await;
10190    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10191        .await;
10192    cx.editor(|editor, _, _| {
10193        let signature_help_state = editor.signature_help_state.popover().cloned();
10194        assert!(signature_help_state.is_some());
10195        assert_eq!(
10196            signature_help_state.unwrap().label,
10197            "param1: u8, param2: u8"
10198        );
10199    });
10200}
10201
10202#[gpui::test]
10203async fn test_signature_help(cx: &mut TestAppContext) {
10204    init_test(cx, |_| {});
10205    cx.update(|cx| {
10206        cx.update_global::<SettingsStore, _>(|settings, cx| {
10207            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10208                settings.auto_signature_help = Some(true);
10209            });
10210        });
10211    });
10212
10213    let mut cx = EditorLspTestContext::new_rust(
10214        lsp::ServerCapabilities {
10215            signature_help_provider: Some(lsp::SignatureHelpOptions {
10216                ..Default::default()
10217            }),
10218            ..Default::default()
10219        },
10220        cx,
10221    )
10222    .await;
10223
10224    // A test that directly calls `show_signature_help`
10225    cx.update_editor(|editor, window, cx| {
10226        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10227    });
10228
10229    let mocked_response = lsp::SignatureHelp {
10230        signatures: vec![lsp::SignatureInformation {
10231            label: "fn sample(param1: u8, param2: u8)".to_string(),
10232            documentation: None,
10233            parameters: Some(vec![
10234                lsp::ParameterInformation {
10235                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10236                    documentation: None,
10237                },
10238                lsp::ParameterInformation {
10239                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10240                    documentation: None,
10241                },
10242            ]),
10243            active_parameter: None,
10244        }],
10245        active_signature: Some(0),
10246        active_parameter: Some(0),
10247    };
10248    handle_signature_help_request(&mut cx, mocked_response).await;
10249
10250    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10251        .await;
10252
10253    cx.editor(|editor, _, _| {
10254        let signature_help_state = editor.signature_help_state.popover().cloned();
10255        assert!(signature_help_state.is_some());
10256        assert_eq!(
10257            signature_help_state.unwrap().label,
10258            "param1: u8, param2: u8"
10259        );
10260    });
10261
10262    // When exiting outside from inside the brackets, `signature_help` is closed.
10263    cx.set_state(indoc! {"
10264        fn main() {
10265            sample(ˇ);
10266        }
10267
10268        fn sample(param1: u8, param2: u8) {}
10269    "});
10270
10271    cx.update_editor(|editor, window, cx| {
10272        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10273    });
10274
10275    let mocked_response = lsp::SignatureHelp {
10276        signatures: Vec::new(),
10277        active_signature: None,
10278        active_parameter: None,
10279    };
10280    handle_signature_help_request(&mut cx, mocked_response).await;
10281
10282    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10283        .await;
10284
10285    cx.editor(|editor, _, _| {
10286        assert!(!editor.signature_help_state.is_shown());
10287    });
10288
10289    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10290    cx.set_state(indoc! {"
10291        fn main() {
10292            sample(ˇ);
10293        }
10294
10295        fn sample(param1: u8, param2: u8) {}
10296    "});
10297
10298    let mocked_response = lsp::SignatureHelp {
10299        signatures: vec![lsp::SignatureInformation {
10300            label: "fn sample(param1: u8, param2: u8)".to_string(),
10301            documentation: None,
10302            parameters: Some(vec![
10303                lsp::ParameterInformation {
10304                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10305                    documentation: None,
10306                },
10307                lsp::ParameterInformation {
10308                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10309                    documentation: None,
10310                },
10311            ]),
10312            active_parameter: None,
10313        }],
10314        active_signature: Some(0),
10315        active_parameter: Some(0),
10316    };
10317    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10318    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10319        .await;
10320    cx.editor(|editor, _, _| {
10321        assert!(editor.signature_help_state.is_shown());
10322    });
10323
10324    // Restore the popover with more parameter input
10325    cx.set_state(indoc! {"
10326        fn main() {
10327            sample(param1, param2ˇ);
10328        }
10329
10330        fn sample(param1: u8, param2: u8) {}
10331    "});
10332
10333    let mocked_response = lsp::SignatureHelp {
10334        signatures: vec![lsp::SignatureInformation {
10335            label: "fn sample(param1: u8, param2: u8)".to_string(),
10336            documentation: None,
10337            parameters: Some(vec![
10338                lsp::ParameterInformation {
10339                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10340                    documentation: None,
10341                },
10342                lsp::ParameterInformation {
10343                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10344                    documentation: None,
10345                },
10346            ]),
10347            active_parameter: None,
10348        }],
10349        active_signature: Some(0),
10350        active_parameter: Some(1),
10351    };
10352    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10353    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10354        .await;
10355
10356    // When selecting a range, the popover is gone.
10357    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10358    cx.update_editor(|editor, window, cx| {
10359        editor.change_selections(None, window, cx, |s| {
10360            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10361        })
10362    });
10363    cx.assert_editor_state(indoc! {"
10364        fn main() {
10365            sample(param1, «ˇparam2»);
10366        }
10367
10368        fn sample(param1: u8, param2: u8) {}
10369    "});
10370    cx.editor(|editor, _, _| {
10371        assert!(!editor.signature_help_state.is_shown());
10372    });
10373
10374    // When unselecting again, the popover is back if within the brackets.
10375    cx.update_editor(|editor, window, cx| {
10376        editor.change_selections(None, window, cx, |s| {
10377            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10378        })
10379    });
10380    cx.assert_editor_state(indoc! {"
10381        fn main() {
10382            sample(param1, ˇparam2);
10383        }
10384
10385        fn sample(param1: u8, param2: u8) {}
10386    "});
10387    handle_signature_help_request(&mut cx, mocked_response).await;
10388    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10389        .await;
10390    cx.editor(|editor, _, _| {
10391        assert!(editor.signature_help_state.is_shown());
10392    });
10393
10394    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10395    cx.update_editor(|editor, window, cx| {
10396        editor.change_selections(None, window, cx, |s| {
10397            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10398            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10399        })
10400    });
10401    cx.assert_editor_state(indoc! {"
10402        fn main() {
10403            sample(param1, ˇparam2);
10404        }
10405
10406        fn sample(param1: u8, param2: u8) {}
10407    "});
10408
10409    let mocked_response = lsp::SignatureHelp {
10410        signatures: vec![lsp::SignatureInformation {
10411            label: "fn sample(param1: u8, param2: u8)".to_string(),
10412            documentation: None,
10413            parameters: Some(vec![
10414                lsp::ParameterInformation {
10415                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10416                    documentation: None,
10417                },
10418                lsp::ParameterInformation {
10419                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10420                    documentation: None,
10421                },
10422            ]),
10423            active_parameter: None,
10424        }],
10425        active_signature: Some(0),
10426        active_parameter: Some(1),
10427    };
10428    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10429    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10430        .await;
10431    cx.update_editor(|editor, _, cx| {
10432        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10433    });
10434    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10435        .await;
10436    cx.update_editor(|editor, window, cx| {
10437        editor.change_selections(None, window, cx, |s| {
10438            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10439        })
10440    });
10441    cx.assert_editor_state(indoc! {"
10442        fn main() {
10443            sample(param1, «ˇparam2»);
10444        }
10445
10446        fn sample(param1: u8, param2: u8) {}
10447    "});
10448    cx.update_editor(|editor, window, cx| {
10449        editor.change_selections(None, window, cx, |s| {
10450            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10451        })
10452    });
10453    cx.assert_editor_state(indoc! {"
10454        fn main() {
10455            sample(param1, ˇparam2);
10456        }
10457
10458        fn sample(param1: u8, param2: u8) {}
10459    "});
10460    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10461        .await;
10462}
10463
10464#[gpui::test]
10465async fn test_completion_mode(cx: &mut TestAppContext) {
10466    init_test(cx, |_| {});
10467    let mut cx = EditorLspTestContext::new_rust(
10468        lsp::ServerCapabilities {
10469            completion_provider: Some(lsp::CompletionOptions {
10470                resolve_provider: Some(true),
10471                ..Default::default()
10472            }),
10473            ..Default::default()
10474        },
10475        cx,
10476    )
10477    .await;
10478
10479    struct Run {
10480        run_description: &'static str,
10481        initial_state: String,
10482        buffer_marked_text: String,
10483        completion_label: &'static str,
10484        completion_text: &'static str,
10485        expected_with_insert_mode: String,
10486        expected_with_replace_mode: String,
10487        expected_with_replace_subsequence_mode: String,
10488        expected_with_replace_suffix_mode: String,
10489    }
10490
10491    let runs = [
10492        Run {
10493            run_description: "Start of word matches completion text",
10494            initial_state: "before ediˇ after".into(),
10495            buffer_marked_text: "before <edi|> after".into(),
10496            completion_label: "editor",
10497            completion_text: "editor",
10498            expected_with_insert_mode: "before editorˇ after".into(),
10499            expected_with_replace_mode: "before editorˇ after".into(),
10500            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10501            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10502        },
10503        Run {
10504            run_description: "Accept same text at the middle of the word",
10505            initial_state: "before ediˇtor after".into(),
10506            buffer_marked_text: "before <edi|tor> after".into(),
10507            completion_label: "editor",
10508            completion_text: "editor",
10509            expected_with_insert_mode: "before editorˇtor after".into(),
10510            expected_with_replace_mode: "before editorˇ after".into(),
10511            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10512            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10513        },
10514        Run {
10515            run_description: "End of word matches completion text -- cursor at end",
10516            initial_state: "before torˇ after".into(),
10517            buffer_marked_text: "before <tor|> after".into(),
10518            completion_label: "editor",
10519            completion_text: "editor",
10520            expected_with_insert_mode: "before editorˇ after".into(),
10521            expected_with_replace_mode: "before editorˇ after".into(),
10522            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10523            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10524        },
10525        Run {
10526            run_description: "End of word matches completion text -- cursor at start",
10527            initial_state: "before ˇtor after".into(),
10528            buffer_marked_text: "before <|tor> after".into(),
10529            completion_label: "editor",
10530            completion_text: "editor",
10531            expected_with_insert_mode: "before editorˇtor after".into(),
10532            expected_with_replace_mode: "before editorˇ after".into(),
10533            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10534            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10535        },
10536        Run {
10537            run_description: "Prepend text containing whitespace",
10538            initial_state: "pˇfield: bool".into(),
10539            buffer_marked_text: "<p|field>: bool".into(),
10540            completion_label: "pub ",
10541            completion_text: "pub ",
10542            expected_with_insert_mode: "pub ˇfield: bool".into(),
10543            expected_with_replace_mode: "pub ˇ: bool".into(),
10544            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10545            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10546        },
10547        Run {
10548            run_description: "Add element to start of list",
10549            initial_state: "[element_ˇelement_2]".into(),
10550            buffer_marked_text: "[<element_|element_2>]".into(),
10551            completion_label: "element_1",
10552            completion_text: "element_1",
10553            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10554            expected_with_replace_mode: "[element_1ˇ]".into(),
10555            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10556            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10557        },
10558        Run {
10559            run_description: "Add element to start of list -- first and second elements are equal",
10560            initial_state: "[elˇelement]".into(),
10561            buffer_marked_text: "[<el|element>]".into(),
10562            completion_label: "element",
10563            completion_text: "element",
10564            expected_with_insert_mode: "[elementˇelement]".into(),
10565            expected_with_replace_mode: "[elementˇ]".into(),
10566            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10567            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10568        },
10569        Run {
10570            run_description: "Ends with matching suffix",
10571            initial_state: "SubˇError".into(),
10572            buffer_marked_text: "<Sub|Error>".into(),
10573            completion_label: "SubscriptionError",
10574            completion_text: "SubscriptionError",
10575            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10576            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10577            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10578            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10579        },
10580        Run {
10581            run_description: "Suffix is a subsequence -- contiguous",
10582            initial_state: "SubˇErr".into(),
10583            buffer_marked_text: "<Sub|Err>".into(),
10584            completion_label: "SubscriptionError",
10585            completion_text: "SubscriptionError",
10586            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10587            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10588            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10589            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10590        },
10591        Run {
10592            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10593            initial_state: "Suˇscrirr".into(),
10594            buffer_marked_text: "<Su|scrirr>".into(),
10595            completion_label: "SubscriptionError",
10596            completion_text: "SubscriptionError",
10597            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10598            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10599            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10600            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10601        },
10602        Run {
10603            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10604            initial_state: "foo(indˇix)".into(),
10605            buffer_marked_text: "foo(<ind|ix>)".into(),
10606            completion_label: "node_index",
10607            completion_text: "node_index",
10608            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10609            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10610            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10611            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10612        },
10613        Run {
10614            run_description: "Replace range ends before cursor - should extend to cursor",
10615            initial_state: "before editˇo after".into(),
10616            buffer_marked_text: "before <{ed}>it|o after".into(),
10617            completion_label: "editor",
10618            completion_text: "editor",
10619            expected_with_insert_mode: "before editorˇo after".into(),
10620            expected_with_replace_mode: "before editorˇo after".into(),
10621            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
10622            expected_with_replace_suffix_mode: "before editorˇo after".into(),
10623        },
10624        Run {
10625            run_description: "Uses label for suffix matching",
10626            initial_state: "before ediˇtor after".into(),
10627            buffer_marked_text: "before <edi|tor> after".into(),
10628            completion_label: "editor",
10629            completion_text: "editor()",
10630            expected_with_insert_mode: "before editor()ˇtor after".into(),
10631            expected_with_replace_mode: "before editor()ˇ after".into(),
10632            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
10633            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
10634        },
10635        Run {
10636            run_description: "Case insensitive subsequence and suffix matching",
10637            initial_state: "before EDiˇtoR after".into(),
10638            buffer_marked_text: "before <EDi|toR> after".into(),
10639            completion_label: "editor",
10640            completion_text: "editor",
10641            expected_with_insert_mode: "before editorˇtoR after".into(),
10642            expected_with_replace_mode: "before editorˇ after".into(),
10643            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10644            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10645        },
10646    ];
10647
10648    for run in runs {
10649        let run_variations = [
10650            (LspInsertMode::Insert, run.expected_with_insert_mode),
10651            (LspInsertMode::Replace, run.expected_with_replace_mode),
10652            (
10653                LspInsertMode::ReplaceSubsequence,
10654                run.expected_with_replace_subsequence_mode,
10655            ),
10656            (
10657                LspInsertMode::ReplaceSuffix,
10658                run.expected_with_replace_suffix_mode,
10659            ),
10660        ];
10661
10662        for (lsp_insert_mode, expected_text) in run_variations {
10663            eprintln!(
10664                "run = {:?}, mode = {lsp_insert_mode:.?}",
10665                run.run_description,
10666            );
10667
10668            update_test_language_settings(&mut cx, |settings| {
10669                settings.defaults.completions = Some(CompletionSettings {
10670                    lsp_insert_mode,
10671                    words: WordsCompletionMode::Disabled,
10672                    lsp: true,
10673                    lsp_fetch_timeout_ms: 0,
10674                });
10675            });
10676
10677            cx.set_state(&run.initial_state);
10678            cx.update_editor(|editor, window, cx| {
10679                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10680            });
10681
10682            let counter = Arc::new(AtomicUsize::new(0));
10683            handle_completion_request_with_insert_and_replace(
10684                &mut cx,
10685                &run.buffer_marked_text,
10686                vec![(run.completion_label, run.completion_text)],
10687                counter.clone(),
10688            )
10689            .await;
10690            cx.condition(|editor, _| editor.context_menu_visible())
10691                .await;
10692            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10693
10694            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10695                editor
10696                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10697                    .unwrap()
10698            });
10699            cx.assert_editor_state(&expected_text);
10700            handle_resolve_completion_request(&mut cx, None).await;
10701            apply_additional_edits.await.unwrap();
10702        }
10703    }
10704}
10705
10706#[gpui::test]
10707async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10708    init_test(cx, |_| {});
10709    let mut cx = EditorLspTestContext::new_rust(
10710        lsp::ServerCapabilities {
10711            completion_provider: Some(lsp::CompletionOptions {
10712                resolve_provider: Some(true),
10713                ..Default::default()
10714            }),
10715            ..Default::default()
10716        },
10717        cx,
10718    )
10719    .await;
10720
10721    let initial_state = "SubˇError";
10722    let buffer_marked_text = "<Sub|Error>";
10723    let completion_text = "SubscriptionError";
10724    let expected_with_insert_mode = "SubscriptionErrorˇError";
10725    let expected_with_replace_mode = "SubscriptionErrorˇ";
10726
10727    update_test_language_settings(&mut cx, |settings| {
10728        settings.defaults.completions = Some(CompletionSettings {
10729            words: WordsCompletionMode::Disabled,
10730            // set the opposite here to ensure that the action is overriding the default behavior
10731            lsp_insert_mode: LspInsertMode::Insert,
10732            lsp: true,
10733            lsp_fetch_timeout_ms: 0,
10734        });
10735    });
10736
10737    cx.set_state(initial_state);
10738    cx.update_editor(|editor, window, cx| {
10739        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10740    });
10741
10742    let counter = Arc::new(AtomicUsize::new(0));
10743    handle_completion_request_with_insert_and_replace(
10744        &mut cx,
10745        &buffer_marked_text,
10746        vec![(completion_text, completion_text)],
10747        counter.clone(),
10748    )
10749    .await;
10750    cx.condition(|editor, _| editor.context_menu_visible())
10751        .await;
10752    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10753
10754    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10755        editor
10756            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10757            .unwrap()
10758    });
10759    cx.assert_editor_state(&expected_with_replace_mode);
10760    handle_resolve_completion_request(&mut cx, None).await;
10761    apply_additional_edits.await.unwrap();
10762
10763    update_test_language_settings(&mut cx, |settings| {
10764        settings.defaults.completions = Some(CompletionSettings {
10765            words: WordsCompletionMode::Disabled,
10766            // set the opposite here to ensure that the action is overriding the default behavior
10767            lsp_insert_mode: LspInsertMode::Replace,
10768            lsp: true,
10769            lsp_fetch_timeout_ms: 0,
10770        });
10771    });
10772
10773    cx.set_state(initial_state);
10774    cx.update_editor(|editor, window, cx| {
10775        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10776    });
10777    handle_completion_request_with_insert_and_replace(
10778        &mut cx,
10779        &buffer_marked_text,
10780        vec![(completion_text, completion_text)],
10781        counter.clone(),
10782    )
10783    .await;
10784    cx.condition(|editor, _| editor.context_menu_visible())
10785        .await;
10786    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10787
10788    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10789        editor
10790            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10791            .unwrap()
10792    });
10793    cx.assert_editor_state(&expected_with_insert_mode);
10794    handle_resolve_completion_request(&mut cx, None).await;
10795    apply_additional_edits.await.unwrap();
10796}
10797
10798#[gpui::test]
10799async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10800    init_test(cx, |_| {});
10801    let mut cx = EditorLspTestContext::new_rust(
10802        lsp::ServerCapabilities {
10803            completion_provider: Some(lsp::CompletionOptions {
10804                resolve_provider: Some(true),
10805                ..Default::default()
10806            }),
10807            ..Default::default()
10808        },
10809        cx,
10810    )
10811    .await;
10812
10813    // scenario: surrounding text matches completion text
10814    let completion_text = "to_offset";
10815    let initial_state = indoc! {"
10816        1. buf.to_offˇsuffix
10817        2. buf.to_offˇsuf
10818        3. buf.to_offˇfix
10819        4. buf.to_offˇ
10820        5. into_offˇensive
10821        6. ˇsuffix
10822        7. let ˇ //
10823        8. aaˇzz
10824        9. buf.to_off«zzzzzˇ»suffix
10825        10. buf.«ˇzzzzz»suffix
10826        11. to_off«ˇzzzzz»
10827
10828        buf.to_offˇsuffix  // newest cursor
10829    "};
10830    let completion_marked_buffer = indoc! {"
10831        1. buf.to_offsuffix
10832        2. buf.to_offsuf
10833        3. buf.to_offfix
10834        4. buf.to_off
10835        5. into_offensive
10836        6. suffix
10837        7. let  //
10838        8. aazz
10839        9. buf.to_offzzzzzsuffix
10840        10. buf.zzzzzsuffix
10841        11. to_offzzzzz
10842
10843        buf.<to_off|suffix>  // newest cursor
10844    "};
10845    let expected = indoc! {"
10846        1. buf.to_offsetˇ
10847        2. buf.to_offsetˇsuf
10848        3. buf.to_offsetˇfix
10849        4. buf.to_offsetˇ
10850        5. into_offsetˇensive
10851        6. to_offsetˇsuffix
10852        7. let to_offsetˇ //
10853        8. aato_offsetˇzz
10854        9. buf.to_offsetˇ
10855        10. buf.to_offsetˇsuffix
10856        11. to_offsetˇ
10857
10858        buf.to_offsetˇ  // newest cursor
10859    "};
10860    cx.set_state(initial_state);
10861    cx.update_editor(|editor, window, cx| {
10862        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10863    });
10864    handle_completion_request_with_insert_and_replace(
10865        &mut cx,
10866        completion_marked_buffer,
10867        vec![(completion_text, completion_text)],
10868        Arc::new(AtomicUsize::new(0)),
10869    )
10870    .await;
10871    cx.condition(|editor, _| editor.context_menu_visible())
10872        .await;
10873    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10874        editor
10875            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10876            .unwrap()
10877    });
10878    cx.assert_editor_state(expected);
10879    handle_resolve_completion_request(&mut cx, None).await;
10880    apply_additional_edits.await.unwrap();
10881
10882    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10883    let completion_text = "foo_and_bar";
10884    let initial_state = indoc! {"
10885        1. ooanbˇ
10886        2. zooanbˇ
10887        3. ooanbˇz
10888        4. zooanbˇz
10889        5. ooanˇ
10890        6. oanbˇ
10891
10892        ooanbˇ
10893    "};
10894    let completion_marked_buffer = indoc! {"
10895        1. ooanb
10896        2. zooanb
10897        3. ooanbz
10898        4. zooanbz
10899        5. ooan
10900        6. oanb
10901
10902        <ooanb|>
10903    "};
10904    let expected = indoc! {"
10905        1. foo_and_barˇ
10906        2. zfoo_and_barˇ
10907        3. foo_and_barˇz
10908        4. zfoo_and_barˇz
10909        5. ooanfoo_and_barˇ
10910        6. oanbfoo_and_barˇ
10911
10912        foo_and_barˇ
10913    "};
10914    cx.set_state(initial_state);
10915    cx.update_editor(|editor, window, cx| {
10916        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10917    });
10918    handle_completion_request_with_insert_and_replace(
10919        &mut cx,
10920        completion_marked_buffer,
10921        vec![(completion_text, completion_text)],
10922        Arc::new(AtomicUsize::new(0)),
10923    )
10924    .await;
10925    cx.condition(|editor, _| editor.context_menu_visible())
10926        .await;
10927    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10928        editor
10929            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10930            .unwrap()
10931    });
10932    cx.assert_editor_state(expected);
10933    handle_resolve_completion_request(&mut cx, None).await;
10934    apply_additional_edits.await.unwrap();
10935
10936    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10937    // (expects the same as if it was inserted at the end)
10938    let completion_text = "foo_and_bar";
10939    let initial_state = indoc! {"
10940        1. ooˇanb
10941        2. zooˇanb
10942        3. ooˇanbz
10943        4. zooˇanbz
10944
10945        ooˇanb
10946    "};
10947    let completion_marked_buffer = indoc! {"
10948        1. ooanb
10949        2. zooanb
10950        3. ooanbz
10951        4. zooanbz
10952
10953        <oo|anb>
10954    "};
10955    let expected = indoc! {"
10956        1. foo_and_barˇ
10957        2. zfoo_and_barˇ
10958        3. foo_and_barˇz
10959        4. zfoo_and_barˇz
10960
10961        foo_and_barˇ
10962    "};
10963    cx.set_state(initial_state);
10964    cx.update_editor(|editor, window, cx| {
10965        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10966    });
10967    handle_completion_request_with_insert_and_replace(
10968        &mut cx,
10969        completion_marked_buffer,
10970        vec![(completion_text, completion_text)],
10971        Arc::new(AtomicUsize::new(0)),
10972    )
10973    .await;
10974    cx.condition(|editor, _| editor.context_menu_visible())
10975        .await;
10976    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10977        editor
10978            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10979            .unwrap()
10980    });
10981    cx.assert_editor_state(expected);
10982    handle_resolve_completion_request(&mut cx, None).await;
10983    apply_additional_edits.await.unwrap();
10984}
10985
10986// This used to crash
10987#[gpui::test]
10988async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10989    init_test(cx, |_| {});
10990
10991    let buffer_text = indoc! {"
10992        fn main() {
10993            10.satu;
10994
10995            //
10996            // separate cursors so they open in different excerpts (manually reproducible)
10997            //
10998
10999            10.satu20;
11000        }
11001    "};
11002    let multibuffer_text_with_selections = indoc! {"
11003        fn main() {
11004            10.satuˇ;
11005
11006            //
11007
11008            //
11009
11010            10.satuˇ20;
11011        }
11012    "};
11013    let expected_multibuffer = indoc! {"
11014        fn main() {
11015            10.saturating_sub()ˇ;
11016
11017            //
11018
11019            //
11020
11021            10.saturating_sub()ˇ;
11022        }
11023    "};
11024
11025    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11026    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11027
11028    let fs = FakeFs::new(cx.executor());
11029    fs.insert_tree(
11030        path!("/a"),
11031        json!({
11032            "main.rs": buffer_text,
11033        }),
11034    )
11035    .await;
11036
11037    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11038    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11039    language_registry.add(rust_lang());
11040    let mut fake_servers = language_registry.register_fake_lsp(
11041        "Rust",
11042        FakeLspAdapter {
11043            capabilities: lsp::ServerCapabilities {
11044                completion_provider: Some(lsp::CompletionOptions {
11045                    resolve_provider: None,
11046                    ..lsp::CompletionOptions::default()
11047                }),
11048                ..lsp::ServerCapabilities::default()
11049            },
11050            ..FakeLspAdapter::default()
11051        },
11052    );
11053    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11054    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11055    let buffer = project
11056        .update(cx, |project, cx| {
11057            project.open_local_buffer(path!("/a/main.rs"), cx)
11058        })
11059        .await
11060        .unwrap();
11061
11062    let multi_buffer = cx.new(|cx| {
11063        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11064        multi_buffer.push_excerpts(
11065            buffer.clone(),
11066            [ExcerptRange::new(0..first_excerpt_end)],
11067            cx,
11068        );
11069        multi_buffer.push_excerpts(
11070            buffer.clone(),
11071            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11072            cx,
11073        );
11074        multi_buffer
11075    });
11076
11077    let editor = workspace
11078        .update(cx, |_, window, cx| {
11079            cx.new(|cx| {
11080                Editor::new(
11081                    EditorMode::Full {
11082                        scale_ui_elements_with_buffer_font_size: false,
11083                        show_active_line_background: false,
11084                        sized_by_content: false,
11085                    },
11086                    multi_buffer.clone(),
11087                    Some(project.clone()),
11088                    window,
11089                    cx,
11090                )
11091            })
11092        })
11093        .unwrap();
11094
11095    let pane = workspace
11096        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11097        .unwrap();
11098    pane.update_in(cx, |pane, window, cx| {
11099        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11100    });
11101
11102    let fake_server = fake_servers.next().await.unwrap();
11103
11104    editor.update_in(cx, |editor, window, cx| {
11105        editor.change_selections(None, window, cx, |s| {
11106            s.select_ranges([
11107                Point::new(1, 11)..Point::new(1, 11),
11108                Point::new(7, 11)..Point::new(7, 11),
11109            ])
11110        });
11111
11112        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11113    });
11114
11115    editor.update_in(cx, |editor, window, cx| {
11116        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11117    });
11118
11119    fake_server
11120        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11121            let completion_item = lsp::CompletionItem {
11122                label: "saturating_sub()".into(),
11123                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11124                    lsp::InsertReplaceEdit {
11125                        new_text: "saturating_sub()".to_owned(),
11126                        insert: lsp::Range::new(
11127                            lsp::Position::new(7, 7),
11128                            lsp::Position::new(7, 11),
11129                        ),
11130                        replace: lsp::Range::new(
11131                            lsp::Position::new(7, 7),
11132                            lsp::Position::new(7, 13),
11133                        ),
11134                    },
11135                )),
11136                ..lsp::CompletionItem::default()
11137            };
11138
11139            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11140        })
11141        .next()
11142        .await
11143        .unwrap();
11144
11145    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11146        .await;
11147
11148    editor
11149        .update_in(cx, |editor, window, cx| {
11150            editor
11151                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11152                .unwrap()
11153        })
11154        .await
11155        .unwrap();
11156
11157    editor.update(cx, |editor, cx| {
11158        assert_text_with_selections(editor, expected_multibuffer, cx);
11159    })
11160}
11161
11162#[gpui::test]
11163async fn test_completion(cx: &mut TestAppContext) {
11164    init_test(cx, |_| {});
11165
11166    let mut cx = EditorLspTestContext::new_rust(
11167        lsp::ServerCapabilities {
11168            completion_provider: Some(lsp::CompletionOptions {
11169                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11170                resolve_provider: Some(true),
11171                ..Default::default()
11172            }),
11173            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11174            ..Default::default()
11175        },
11176        cx,
11177    )
11178    .await;
11179    let counter = Arc::new(AtomicUsize::new(0));
11180
11181    cx.set_state(indoc! {"
11182        oneˇ
11183        two
11184        three
11185    "});
11186    cx.simulate_keystroke(".");
11187    handle_completion_request(
11188        indoc! {"
11189            one.|<>
11190            two
11191            three
11192        "},
11193        vec!["first_completion", "second_completion"],
11194        true,
11195        counter.clone(),
11196        &mut cx,
11197    )
11198    .await;
11199    cx.condition(|editor, _| editor.context_menu_visible())
11200        .await;
11201    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11202
11203    let _handler = handle_signature_help_request(
11204        &mut cx,
11205        lsp::SignatureHelp {
11206            signatures: vec![lsp::SignatureInformation {
11207                label: "test signature".to_string(),
11208                documentation: None,
11209                parameters: Some(vec![lsp::ParameterInformation {
11210                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11211                    documentation: None,
11212                }]),
11213                active_parameter: None,
11214            }],
11215            active_signature: None,
11216            active_parameter: None,
11217        },
11218    );
11219    cx.update_editor(|editor, window, cx| {
11220        assert!(
11221            !editor.signature_help_state.is_shown(),
11222            "No signature help was called for"
11223        );
11224        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11225    });
11226    cx.run_until_parked();
11227    cx.update_editor(|editor, _, _| {
11228        assert!(
11229            !editor.signature_help_state.is_shown(),
11230            "No signature help should be shown when completions menu is open"
11231        );
11232    });
11233
11234    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11235        editor.context_menu_next(&Default::default(), window, cx);
11236        editor
11237            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11238            .unwrap()
11239    });
11240    cx.assert_editor_state(indoc! {"
11241        one.second_completionˇ
11242        two
11243        three
11244    "});
11245
11246    handle_resolve_completion_request(
11247        &mut cx,
11248        Some(vec![
11249            (
11250                //This overlaps with the primary completion edit which is
11251                //misbehavior from the LSP spec, test that we filter it out
11252                indoc! {"
11253                    one.second_ˇcompletion
11254                    two
11255                    threeˇ
11256                "},
11257                "overlapping additional edit",
11258            ),
11259            (
11260                indoc! {"
11261                    one.second_completion
11262                    two
11263                    threeˇ
11264                "},
11265                "\nadditional edit",
11266            ),
11267        ]),
11268    )
11269    .await;
11270    apply_additional_edits.await.unwrap();
11271    cx.assert_editor_state(indoc! {"
11272        one.second_completionˇ
11273        two
11274        three
11275        additional edit
11276    "});
11277
11278    cx.set_state(indoc! {"
11279        one.second_completion
11280        twoˇ
11281        threeˇ
11282        additional edit
11283    "});
11284    cx.simulate_keystroke(" ");
11285    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11286    cx.simulate_keystroke("s");
11287    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11288
11289    cx.assert_editor_state(indoc! {"
11290        one.second_completion
11291        two sˇ
11292        three sˇ
11293        additional edit
11294    "});
11295    handle_completion_request(
11296        indoc! {"
11297            one.second_completion
11298            two s
11299            three <s|>
11300            additional edit
11301        "},
11302        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11303        true,
11304        counter.clone(),
11305        &mut cx,
11306    )
11307    .await;
11308    cx.condition(|editor, _| editor.context_menu_visible())
11309        .await;
11310    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11311
11312    cx.simulate_keystroke("i");
11313
11314    handle_completion_request(
11315        indoc! {"
11316            one.second_completion
11317            two si
11318            three <si|>
11319            additional edit
11320        "},
11321        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11322        true,
11323        counter.clone(),
11324        &mut cx,
11325    )
11326    .await;
11327    cx.condition(|editor, _| editor.context_menu_visible())
11328        .await;
11329    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11330
11331    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11332        editor
11333            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11334            .unwrap()
11335    });
11336    cx.assert_editor_state(indoc! {"
11337        one.second_completion
11338        two sixth_completionˇ
11339        three sixth_completionˇ
11340        additional edit
11341    "});
11342
11343    apply_additional_edits.await.unwrap();
11344
11345    update_test_language_settings(&mut cx, |settings| {
11346        settings.defaults.show_completions_on_input = Some(false);
11347    });
11348    cx.set_state("editorˇ");
11349    cx.simulate_keystroke(".");
11350    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11351    cx.simulate_keystrokes("c l o");
11352    cx.assert_editor_state("editor.cloˇ");
11353    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11354    cx.update_editor(|editor, window, cx| {
11355        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11356    });
11357    handle_completion_request(
11358        "editor.<clo|>",
11359        vec!["close", "clobber"],
11360        true,
11361        counter.clone(),
11362        &mut cx,
11363    )
11364    .await;
11365    cx.condition(|editor, _| editor.context_menu_visible())
11366        .await;
11367    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11368
11369    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11370        editor
11371            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11372            .unwrap()
11373    });
11374    cx.assert_editor_state("editor.closeˇ");
11375    handle_resolve_completion_request(&mut cx, None).await;
11376    apply_additional_edits.await.unwrap();
11377}
11378
11379#[gpui::test]
11380async fn test_completion_reuse(cx: &mut TestAppContext) {
11381    init_test(cx, |_| {});
11382
11383    let mut cx = EditorLspTestContext::new_rust(
11384        lsp::ServerCapabilities {
11385            completion_provider: Some(lsp::CompletionOptions {
11386                trigger_characters: Some(vec![".".to_string()]),
11387                ..Default::default()
11388            }),
11389            ..Default::default()
11390        },
11391        cx,
11392    )
11393    .await;
11394
11395    let counter = Arc::new(AtomicUsize::new(0));
11396    cx.set_state("objˇ");
11397    cx.simulate_keystroke(".");
11398
11399    // Initial completion request returns complete results
11400    let is_incomplete = false;
11401    handle_completion_request(
11402        "obj.|<>",
11403        vec!["a", "ab", "abc"],
11404        is_incomplete,
11405        counter.clone(),
11406        &mut cx,
11407    )
11408    .await;
11409    cx.run_until_parked();
11410    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11411    cx.assert_editor_state("obj.ˇ");
11412    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11413
11414    // Type "a" - filters existing completions
11415    cx.simulate_keystroke("a");
11416    cx.run_until_parked();
11417    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11418    cx.assert_editor_state("obj.aˇ");
11419    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11420
11421    // Type "b" - filters existing completions
11422    cx.simulate_keystroke("b");
11423    cx.run_until_parked();
11424    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11425    cx.assert_editor_state("obj.abˇ");
11426    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11427
11428    // Type "c" - filters existing completions
11429    cx.simulate_keystroke("c");
11430    cx.run_until_parked();
11431    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11432    cx.assert_editor_state("obj.abcˇ");
11433    check_displayed_completions(vec!["abc"], &mut cx);
11434
11435    // Backspace to delete "c" - filters existing completions
11436    cx.update_editor(|editor, window, cx| {
11437        editor.backspace(&Backspace, window, cx);
11438    });
11439    cx.run_until_parked();
11440    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11441    cx.assert_editor_state("obj.abˇ");
11442    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11443
11444    // Moving cursor to the left dismisses menu.
11445    cx.update_editor(|editor, window, cx| {
11446        editor.move_left(&MoveLeft, window, cx);
11447    });
11448    cx.run_until_parked();
11449    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11450    cx.assert_editor_state("obj.aˇb");
11451    cx.update_editor(|editor, _, _| {
11452        assert_eq!(editor.context_menu_visible(), false);
11453    });
11454
11455    // Type "b" - new request
11456    cx.simulate_keystroke("b");
11457    let is_incomplete = false;
11458    handle_completion_request(
11459        "obj.<ab|>a",
11460        vec!["ab", "abc"],
11461        is_incomplete,
11462        counter.clone(),
11463        &mut cx,
11464    )
11465    .await;
11466    cx.run_until_parked();
11467    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11468    cx.assert_editor_state("obj.abˇb");
11469    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11470
11471    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
11472    cx.update_editor(|editor, window, cx| {
11473        editor.backspace(&Backspace, window, cx);
11474    });
11475    let is_incomplete = false;
11476    handle_completion_request(
11477        "obj.<a|>b",
11478        vec!["a", "ab", "abc"],
11479        is_incomplete,
11480        counter.clone(),
11481        &mut cx,
11482    )
11483    .await;
11484    cx.run_until_parked();
11485    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11486    cx.assert_editor_state("obj.aˇb");
11487    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11488
11489    // Backspace to delete "a" - dismisses menu.
11490    cx.update_editor(|editor, window, cx| {
11491        editor.backspace(&Backspace, window, cx);
11492    });
11493    cx.run_until_parked();
11494    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11495    cx.assert_editor_state("obj.ˇb");
11496    cx.update_editor(|editor, _, _| {
11497        assert_eq!(editor.context_menu_visible(), false);
11498    });
11499}
11500
11501#[gpui::test]
11502async fn test_word_completion(cx: &mut TestAppContext) {
11503    let lsp_fetch_timeout_ms = 10;
11504    init_test(cx, |language_settings| {
11505        language_settings.defaults.completions = Some(CompletionSettings {
11506            words: WordsCompletionMode::Fallback,
11507            lsp: true,
11508            lsp_fetch_timeout_ms: 10,
11509            lsp_insert_mode: LspInsertMode::Insert,
11510        });
11511    });
11512
11513    let mut cx = EditorLspTestContext::new_rust(
11514        lsp::ServerCapabilities {
11515            completion_provider: Some(lsp::CompletionOptions {
11516                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11517                ..lsp::CompletionOptions::default()
11518            }),
11519            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11520            ..lsp::ServerCapabilities::default()
11521        },
11522        cx,
11523    )
11524    .await;
11525
11526    let throttle_completions = Arc::new(AtomicBool::new(false));
11527
11528    let lsp_throttle_completions = throttle_completions.clone();
11529    let _completion_requests_handler =
11530        cx.lsp
11531            .server
11532            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11533                let lsp_throttle_completions = lsp_throttle_completions.clone();
11534                let cx = cx.clone();
11535                async move {
11536                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11537                        cx.background_executor()
11538                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11539                            .await;
11540                    }
11541                    Ok(Some(lsp::CompletionResponse::Array(vec![
11542                        lsp::CompletionItem {
11543                            label: "first".into(),
11544                            ..lsp::CompletionItem::default()
11545                        },
11546                        lsp::CompletionItem {
11547                            label: "last".into(),
11548                            ..lsp::CompletionItem::default()
11549                        },
11550                    ])))
11551                }
11552            });
11553
11554    cx.set_state(indoc! {"
11555        oneˇ
11556        two
11557        three
11558    "});
11559    cx.simulate_keystroke(".");
11560    cx.executor().run_until_parked();
11561    cx.condition(|editor, _| editor.context_menu_visible())
11562        .await;
11563    cx.update_editor(|editor, window, cx| {
11564        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11565        {
11566            assert_eq!(
11567                completion_menu_entries(&menu),
11568                &["first", "last"],
11569                "When LSP server is fast to reply, no fallback word completions are used"
11570            );
11571        } else {
11572            panic!("expected completion menu to be open");
11573        }
11574        editor.cancel(&Cancel, window, cx);
11575    });
11576    cx.executor().run_until_parked();
11577    cx.condition(|editor, _| !editor.context_menu_visible())
11578        .await;
11579
11580    throttle_completions.store(true, atomic::Ordering::Release);
11581    cx.simulate_keystroke(".");
11582    cx.executor()
11583        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11584    cx.executor().run_until_parked();
11585    cx.condition(|editor, _| editor.context_menu_visible())
11586        .await;
11587    cx.update_editor(|editor, _, _| {
11588        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11589        {
11590            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11591                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11592        } else {
11593            panic!("expected completion menu to be open");
11594        }
11595    });
11596}
11597
11598#[gpui::test]
11599async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11600    init_test(cx, |language_settings| {
11601        language_settings.defaults.completions = Some(CompletionSettings {
11602            words: WordsCompletionMode::Enabled,
11603            lsp: true,
11604            lsp_fetch_timeout_ms: 0,
11605            lsp_insert_mode: LspInsertMode::Insert,
11606        });
11607    });
11608
11609    let mut cx = EditorLspTestContext::new_rust(
11610        lsp::ServerCapabilities {
11611            completion_provider: Some(lsp::CompletionOptions {
11612                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11613                ..lsp::CompletionOptions::default()
11614            }),
11615            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11616            ..lsp::ServerCapabilities::default()
11617        },
11618        cx,
11619    )
11620    .await;
11621
11622    let _completion_requests_handler =
11623        cx.lsp
11624            .server
11625            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11626                Ok(Some(lsp::CompletionResponse::Array(vec![
11627                    lsp::CompletionItem {
11628                        label: "first".into(),
11629                        ..lsp::CompletionItem::default()
11630                    },
11631                    lsp::CompletionItem {
11632                        label: "last".into(),
11633                        ..lsp::CompletionItem::default()
11634                    },
11635                ])))
11636            });
11637
11638    cx.set_state(indoc! {"ˇ
11639        first
11640        last
11641        second
11642    "});
11643    cx.simulate_keystroke(".");
11644    cx.executor().run_until_parked();
11645    cx.condition(|editor, _| editor.context_menu_visible())
11646        .await;
11647    cx.update_editor(|editor, _, _| {
11648        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11649        {
11650            assert_eq!(
11651                completion_menu_entries(&menu),
11652                &["first", "last", "second"],
11653                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11654            );
11655        } else {
11656            panic!("expected completion menu to be open");
11657        }
11658    });
11659}
11660
11661#[gpui::test]
11662async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11663    init_test(cx, |language_settings| {
11664        language_settings.defaults.completions = Some(CompletionSettings {
11665            words: WordsCompletionMode::Disabled,
11666            lsp: true,
11667            lsp_fetch_timeout_ms: 0,
11668            lsp_insert_mode: LspInsertMode::Insert,
11669        });
11670    });
11671
11672    let mut cx = EditorLspTestContext::new_rust(
11673        lsp::ServerCapabilities {
11674            completion_provider: Some(lsp::CompletionOptions {
11675                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11676                ..lsp::CompletionOptions::default()
11677            }),
11678            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11679            ..lsp::ServerCapabilities::default()
11680        },
11681        cx,
11682    )
11683    .await;
11684
11685    let _completion_requests_handler =
11686        cx.lsp
11687            .server
11688            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11689                panic!("LSP completions should not be queried when dealing with word completions")
11690            });
11691
11692    cx.set_state(indoc! {"ˇ
11693        first
11694        last
11695        second
11696    "});
11697    cx.update_editor(|editor, window, cx| {
11698        editor.show_word_completions(&ShowWordCompletions, window, cx);
11699    });
11700    cx.executor().run_until_parked();
11701    cx.condition(|editor, _| editor.context_menu_visible())
11702        .await;
11703    cx.update_editor(|editor, _, _| {
11704        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11705        {
11706            assert_eq!(
11707                completion_menu_entries(&menu),
11708                &["first", "last", "second"],
11709                "`ShowWordCompletions` action should show word completions"
11710            );
11711        } else {
11712            panic!("expected completion menu to be open");
11713        }
11714    });
11715
11716    cx.simulate_keystroke("l");
11717    cx.executor().run_until_parked();
11718    cx.condition(|editor, _| editor.context_menu_visible())
11719        .await;
11720    cx.update_editor(|editor, _, _| {
11721        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11722        {
11723            assert_eq!(
11724                completion_menu_entries(&menu),
11725                &["last"],
11726                "After showing word completions, further editing should filter them and not query the LSP"
11727            );
11728        } else {
11729            panic!("expected completion menu to be open");
11730        }
11731    });
11732}
11733
11734#[gpui::test]
11735async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11736    init_test(cx, |language_settings| {
11737        language_settings.defaults.completions = Some(CompletionSettings {
11738            words: WordsCompletionMode::Fallback,
11739            lsp: false,
11740            lsp_fetch_timeout_ms: 0,
11741            lsp_insert_mode: LspInsertMode::Insert,
11742        });
11743    });
11744
11745    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11746
11747    cx.set_state(indoc! {"ˇ
11748        0_usize
11749        let
11750        33
11751        4.5f32
11752    "});
11753    cx.update_editor(|editor, window, cx| {
11754        editor.show_completions(&ShowCompletions::default(), window, cx);
11755    });
11756    cx.executor().run_until_parked();
11757    cx.condition(|editor, _| editor.context_menu_visible())
11758        .await;
11759    cx.update_editor(|editor, window, cx| {
11760        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11761        {
11762            assert_eq!(
11763                completion_menu_entries(&menu),
11764                &["let"],
11765                "With no digits in the completion query, no digits should be in the word completions"
11766            );
11767        } else {
11768            panic!("expected completion menu to be open");
11769        }
11770        editor.cancel(&Cancel, window, cx);
11771    });
11772
11773    cx.set_state(indoc! {"11774        0_usize
11775        let
11776        3
11777        33.35f32
11778    "});
11779    cx.update_editor(|editor, window, cx| {
11780        editor.show_completions(&ShowCompletions::default(), window, cx);
11781    });
11782    cx.executor().run_until_parked();
11783    cx.condition(|editor, _| editor.context_menu_visible())
11784        .await;
11785    cx.update_editor(|editor, _, _| {
11786        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11787        {
11788            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11789                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11790        } else {
11791            panic!("expected completion menu to be open");
11792        }
11793    });
11794}
11795
11796fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11797    let position = || lsp::Position {
11798        line: params.text_document_position.position.line,
11799        character: params.text_document_position.position.character,
11800    };
11801    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11802        range: lsp::Range {
11803            start: position(),
11804            end: position(),
11805        },
11806        new_text: text.to_string(),
11807    }))
11808}
11809
11810#[gpui::test]
11811async fn test_multiline_completion(cx: &mut TestAppContext) {
11812    init_test(cx, |_| {});
11813
11814    let fs = FakeFs::new(cx.executor());
11815    fs.insert_tree(
11816        path!("/a"),
11817        json!({
11818            "main.ts": "a",
11819        }),
11820    )
11821    .await;
11822
11823    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11824    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11825    let typescript_language = Arc::new(Language::new(
11826        LanguageConfig {
11827            name: "TypeScript".into(),
11828            matcher: LanguageMatcher {
11829                path_suffixes: vec!["ts".to_string()],
11830                ..LanguageMatcher::default()
11831            },
11832            line_comments: vec!["// ".into()],
11833            ..LanguageConfig::default()
11834        },
11835        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11836    ));
11837    language_registry.add(typescript_language.clone());
11838    let mut fake_servers = language_registry.register_fake_lsp(
11839        "TypeScript",
11840        FakeLspAdapter {
11841            capabilities: lsp::ServerCapabilities {
11842                completion_provider: Some(lsp::CompletionOptions {
11843                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11844                    ..lsp::CompletionOptions::default()
11845                }),
11846                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11847                ..lsp::ServerCapabilities::default()
11848            },
11849            // Emulate vtsls label generation
11850            label_for_completion: Some(Box::new(|item, _| {
11851                let text = if let Some(description) = item
11852                    .label_details
11853                    .as_ref()
11854                    .and_then(|label_details| label_details.description.as_ref())
11855                {
11856                    format!("{} {}", item.label, description)
11857                } else if let Some(detail) = &item.detail {
11858                    format!("{} {}", item.label, detail)
11859                } else {
11860                    item.label.clone()
11861                };
11862                let len = text.len();
11863                Some(language::CodeLabel {
11864                    text,
11865                    runs: Vec::new(),
11866                    filter_range: 0..len,
11867                })
11868            })),
11869            ..FakeLspAdapter::default()
11870        },
11871    );
11872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11873    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11874    let worktree_id = workspace
11875        .update(cx, |workspace, _window, cx| {
11876            workspace.project().update(cx, |project, cx| {
11877                project.worktrees(cx).next().unwrap().read(cx).id()
11878            })
11879        })
11880        .unwrap();
11881    let _buffer = project
11882        .update(cx, |project, cx| {
11883            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11884        })
11885        .await
11886        .unwrap();
11887    let editor = workspace
11888        .update(cx, |workspace, window, cx| {
11889            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11890        })
11891        .unwrap()
11892        .await
11893        .unwrap()
11894        .downcast::<Editor>()
11895        .unwrap();
11896    let fake_server = fake_servers.next().await.unwrap();
11897
11898    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11899    let multiline_label_2 = "a\nb\nc\n";
11900    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11901    let multiline_description = "d\ne\nf\n";
11902    let multiline_detail_2 = "g\nh\ni\n";
11903
11904    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11905        move |params, _| async move {
11906            Ok(Some(lsp::CompletionResponse::Array(vec![
11907                lsp::CompletionItem {
11908                    label: multiline_label.to_string(),
11909                    text_edit: gen_text_edit(&params, "new_text_1"),
11910                    ..lsp::CompletionItem::default()
11911                },
11912                lsp::CompletionItem {
11913                    label: "single line label 1".to_string(),
11914                    detail: Some(multiline_detail.to_string()),
11915                    text_edit: gen_text_edit(&params, "new_text_2"),
11916                    ..lsp::CompletionItem::default()
11917                },
11918                lsp::CompletionItem {
11919                    label: "single line label 2".to_string(),
11920                    label_details: Some(lsp::CompletionItemLabelDetails {
11921                        description: Some(multiline_description.to_string()),
11922                        detail: None,
11923                    }),
11924                    text_edit: gen_text_edit(&params, "new_text_2"),
11925                    ..lsp::CompletionItem::default()
11926                },
11927                lsp::CompletionItem {
11928                    label: multiline_label_2.to_string(),
11929                    detail: Some(multiline_detail_2.to_string()),
11930                    text_edit: gen_text_edit(&params, "new_text_3"),
11931                    ..lsp::CompletionItem::default()
11932                },
11933                lsp::CompletionItem {
11934                    label: "Label with many     spaces and \t but without newlines".to_string(),
11935                    detail: Some(
11936                        "Details with many     spaces and \t but without newlines".to_string(),
11937                    ),
11938                    text_edit: gen_text_edit(&params, "new_text_4"),
11939                    ..lsp::CompletionItem::default()
11940                },
11941            ])))
11942        },
11943    );
11944
11945    editor.update_in(cx, |editor, window, cx| {
11946        cx.focus_self(window);
11947        editor.move_to_end(&MoveToEnd, window, cx);
11948        editor.handle_input(".", window, cx);
11949    });
11950    cx.run_until_parked();
11951    completion_handle.next().await.unwrap();
11952
11953    editor.update(cx, |editor, _| {
11954        assert!(editor.context_menu_visible());
11955        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11956        {
11957            let completion_labels = menu
11958                .completions
11959                .borrow()
11960                .iter()
11961                .map(|c| c.label.text.clone())
11962                .collect::<Vec<_>>();
11963            assert_eq!(
11964                completion_labels,
11965                &[
11966                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11967                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11968                    "single line label 2 d e f ",
11969                    "a b c g h i ",
11970                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11971                ],
11972                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11973            );
11974
11975            for completion in menu
11976                .completions
11977                .borrow()
11978                .iter() {
11979                    assert_eq!(
11980                        completion.label.filter_range,
11981                        0..completion.label.text.len(),
11982                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11983                    );
11984                }
11985        } else {
11986            panic!("expected completion menu to be open");
11987        }
11988    });
11989}
11990
11991#[gpui::test]
11992async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11993    init_test(cx, |_| {});
11994    let mut cx = EditorLspTestContext::new_rust(
11995        lsp::ServerCapabilities {
11996            completion_provider: Some(lsp::CompletionOptions {
11997                trigger_characters: Some(vec![".".to_string()]),
11998                ..Default::default()
11999            }),
12000            ..Default::default()
12001        },
12002        cx,
12003    )
12004    .await;
12005    cx.lsp
12006        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12007            Ok(Some(lsp::CompletionResponse::Array(vec![
12008                lsp::CompletionItem {
12009                    label: "first".into(),
12010                    ..Default::default()
12011                },
12012                lsp::CompletionItem {
12013                    label: "last".into(),
12014                    ..Default::default()
12015                },
12016            ])))
12017        });
12018    cx.set_state("variableˇ");
12019    cx.simulate_keystroke(".");
12020    cx.executor().run_until_parked();
12021
12022    cx.update_editor(|editor, _, _| {
12023        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12024        {
12025            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12026        } else {
12027            panic!("expected completion menu to be open");
12028        }
12029    });
12030
12031    cx.update_editor(|editor, window, cx| {
12032        editor.move_page_down(&MovePageDown::default(), window, cx);
12033        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12034        {
12035            assert!(
12036                menu.selected_item == 1,
12037                "expected PageDown to select the last item from the context menu"
12038            );
12039        } else {
12040            panic!("expected completion menu to stay open after PageDown");
12041        }
12042    });
12043
12044    cx.update_editor(|editor, window, cx| {
12045        editor.move_page_up(&MovePageUp::default(), window, cx);
12046        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12047        {
12048            assert!(
12049                menu.selected_item == 0,
12050                "expected PageUp to select the first item from the context menu"
12051            );
12052        } else {
12053            panic!("expected completion menu to stay open after PageUp");
12054        }
12055    });
12056}
12057
12058#[gpui::test]
12059async fn test_as_is_completions(cx: &mut TestAppContext) {
12060    init_test(cx, |_| {});
12061    let mut cx = EditorLspTestContext::new_rust(
12062        lsp::ServerCapabilities {
12063            completion_provider: Some(lsp::CompletionOptions {
12064                ..Default::default()
12065            }),
12066            ..Default::default()
12067        },
12068        cx,
12069    )
12070    .await;
12071    cx.lsp
12072        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12073            Ok(Some(lsp::CompletionResponse::Array(vec![
12074                lsp::CompletionItem {
12075                    label: "unsafe".into(),
12076                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12077                        range: lsp::Range {
12078                            start: lsp::Position {
12079                                line: 1,
12080                                character: 2,
12081                            },
12082                            end: lsp::Position {
12083                                line: 1,
12084                                character: 3,
12085                            },
12086                        },
12087                        new_text: "unsafe".to_string(),
12088                    })),
12089                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12090                    ..Default::default()
12091                },
12092            ])))
12093        });
12094    cx.set_state("fn a() {}\n");
12095    cx.executor().run_until_parked();
12096    cx.update_editor(|editor, window, cx| {
12097        editor.show_completions(
12098            &ShowCompletions {
12099                trigger: Some("\n".into()),
12100            },
12101            window,
12102            cx,
12103        );
12104    });
12105    cx.executor().run_until_parked();
12106
12107    cx.update_editor(|editor, window, cx| {
12108        editor.confirm_completion(&Default::default(), window, cx)
12109    });
12110    cx.executor().run_until_parked();
12111    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12112}
12113
12114#[gpui::test]
12115async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12116    init_test(cx, |_| {});
12117
12118    let mut cx = EditorLspTestContext::new_rust(
12119        lsp::ServerCapabilities {
12120            completion_provider: Some(lsp::CompletionOptions {
12121                trigger_characters: Some(vec![".".to_string()]),
12122                resolve_provider: Some(true),
12123                ..Default::default()
12124            }),
12125            ..Default::default()
12126        },
12127        cx,
12128    )
12129    .await;
12130
12131    cx.set_state("fn main() { let a = 2ˇ; }");
12132    cx.simulate_keystroke(".");
12133    let completion_item = lsp::CompletionItem {
12134        label: "Some".into(),
12135        kind: Some(lsp::CompletionItemKind::SNIPPET),
12136        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12137        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12138            kind: lsp::MarkupKind::Markdown,
12139            value: "```rust\nSome(2)\n```".to_string(),
12140        })),
12141        deprecated: Some(false),
12142        sort_text: Some("Some".to_string()),
12143        filter_text: Some("Some".to_string()),
12144        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12145        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12146            range: lsp::Range {
12147                start: lsp::Position {
12148                    line: 0,
12149                    character: 22,
12150                },
12151                end: lsp::Position {
12152                    line: 0,
12153                    character: 22,
12154                },
12155            },
12156            new_text: "Some(2)".to_string(),
12157        })),
12158        additional_text_edits: Some(vec![lsp::TextEdit {
12159            range: lsp::Range {
12160                start: lsp::Position {
12161                    line: 0,
12162                    character: 20,
12163                },
12164                end: lsp::Position {
12165                    line: 0,
12166                    character: 22,
12167                },
12168            },
12169            new_text: "".to_string(),
12170        }]),
12171        ..Default::default()
12172    };
12173
12174    let closure_completion_item = completion_item.clone();
12175    let counter = Arc::new(AtomicUsize::new(0));
12176    let counter_clone = counter.clone();
12177    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12178        let task_completion_item = closure_completion_item.clone();
12179        counter_clone.fetch_add(1, atomic::Ordering::Release);
12180        async move {
12181            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12182                is_incomplete: true,
12183                item_defaults: None,
12184                items: vec![task_completion_item],
12185            })))
12186        }
12187    });
12188
12189    cx.condition(|editor, _| editor.context_menu_visible())
12190        .await;
12191    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12192    assert!(request.next().await.is_some());
12193    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12194
12195    cx.simulate_keystrokes("S o m");
12196    cx.condition(|editor, _| editor.context_menu_visible())
12197        .await;
12198    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12199    assert!(request.next().await.is_some());
12200    assert!(request.next().await.is_some());
12201    assert!(request.next().await.is_some());
12202    request.close();
12203    assert!(request.next().await.is_none());
12204    assert_eq!(
12205        counter.load(atomic::Ordering::Acquire),
12206        4,
12207        "With the completions menu open, only one LSP request should happen per input"
12208    );
12209}
12210
12211#[gpui::test]
12212async fn test_toggle_comment(cx: &mut TestAppContext) {
12213    init_test(cx, |_| {});
12214    let mut cx = EditorTestContext::new(cx).await;
12215    let language = Arc::new(Language::new(
12216        LanguageConfig {
12217            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12218            ..Default::default()
12219        },
12220        Some(tree_sitter_rust::LANGUAGE.into()),
12221    ));
12222    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12223
12224    // If multiple selections intersect a line, the line is only toggled once.
12225    cx.set_state(indoc! {"
12226        fn a() {
12227            «//b();
12228            ˇ»// «c();
12229            //ˇ»  d();
12230        }
12231    "});
12232
12233    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12234
12235    cx.assert_editor_state(indoc! {"
12236        fn a() {
12237            «b();
12238            c();
12239            ˇ» d();
12240        }
12241    "});
12242
12243    // The comment prefix is inserted at the same column for every line in a
12244    // selection.
12245    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12246
12247    cx.assert_editor_state(indoc! {"
12248        fn a() {
12249            // «b();
12250            // c();
12251            ˇ»//  d();
12252        }
12253    "});
12254
12255    // If a selection ends at the beginning of a line, that line is not toggled.
12256    cx.set_selections_state(indoc! {"
12257        fn a() {
12258            // b();
12259            «// c();
12260        ˇ»    //  d();
12261        }
12262    "});
12263
12264    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12265
12266    cx.assert_editor_state(indoc! {"
12267        fn a() {
12268            // b();
12269            «c();
12270        ˇ»    //  d();
12271        }
12272    "});
12273
12274    // If a selection span a single line and is empty, the line is toggled.
12275    cx.set_state(indoc! {"
12276        fn a() {
12277            a();
12278            b();
12279        ˇ
12280        }
12281    "});
12282
12283    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12284
12285    cx.assert_editor_state(indoc! {"
12286        fn a() {
12287            a();
12288            b();
12289        //•ˇ
12290        }
12291    "});
12292
12293    // If a selection span multiple lines, empty lines are not toggled.
12294    cx.set_state(indoc! {"
12295        fn a() {
12296            «a();
12297
12298            c();ˇ»
12299        }
12300    "});
12301
12302    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12303
12304    cx.assert_editor_state(indoc! {"
12305        fn a() {
12306            // «a();
12307
12308            // c();ˇ»
12309        }
12310    "});
12311
12312    // If a selection includes multiple comment prefixes, all lines are uncommented.
12313    cx.set_state(indoc! {"
12314        fn a() {
12315            «// a();
12316            /// b();
12317            //! c();ˇ»
12318        }
12319    "});
12320
12321    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12322
12323    cx.assert_editor_state(indoc! {"
12324        fn a() {
12325            «a();
12326            b();
12327            c();ˇ»
12328        }
12329    "});
12330}
12331
12332#[gpui::test]
12333async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12334    init_test(cx, |_| {});
12335    let mut cx = EditorTestContext::new(cx).await;
12336    let language = Arc::new(Language::new(
12337        LanguageConfig {
12338            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12339            ..Default::default()
12340        },
12341        Some(tree_sitter_rust::LANGUAGE.into()),
12342    ));
12343    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12344
12345    let toggle_comments = &ToggleComments {
12346        advance_downwards: false,
12347        ignore_indent: true,
12348    };
12349
12350    // If multiple selections intersect a line, the line is only toggled once.
12351    cx.set_state(indoc! {"
12352        fn a() {
12353        //    «b();
12354        //    c();
12355        //    ˇ» d();
12356        }
12357    "});
12358
12359    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12360
12361    cx.assert_editor_state(indoc! {"
12362        fn a() {
12363            «b();
12364            c();
12365            ˇ» d();
12366        }
12367    "});
12368
12369    // The comment prefix is inserted at the beginning of each line
12370    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12371
12372    cx.assert_editor_state(indoc! {"
12373        fn a() {
12374        //    «b();
12375        //    c();
12376        //    ˇ» d();
12377        }
12378    "});
12379
12380    // If a selection ends at the beginning of a line, that line is not toggled.
12381    cx.set_selections_state(indoc! {"
12382        fn a() {
12383        //    b();
12384        //    «c();
12385        ˇ»//     d();
12386        }
12387    "});
12388
12389    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12390
12391    cx.assert_editor_state(indoc! {"
12392        fn a() {
12393        //    b();
12394            «c();
12395        ˇ»//     d();
12396        }
12397    "});
12398
12399    // If a selection span a single line and is empty, the line is toggled.
12400    cx.set_state(indoc! {"
12401        fn a() {
12402            a();
12403            b();
12404        ˇ
12405        }
12406    "});
12407
12408    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12409
12410    cx.assert_editor_state(indoc! {"
12411        fn a() {
12412            a();
12413            b();
12414        //ˇ
12415        }
12416    "});
12417
12418    // If a selection span multiple lines, empty lines are not toggled.
12419    cx.set_state(indoc! {"
12420        fn a() {
12421            «a();
12422
12423            c();ˇ»
12424        }
12425    "});
12426
12427    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12428
12429    cx.assert_editor_state(indoc! {"
12430        fn a() {
12431        //    «a();
12432
12433        //    c();ˇ»
12434        }
12435    "});
12436
12437    // If a selection includes multiple comment prefixes, all lines are uncommented.
12438    cx.set_state(indoc! {"
12439        fn a() {
12440        //    «a();
12441        ///    b();
12442        //!    c();ˇ»
12443        }
12444    "});
12445
12446    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12447
12448    cx.assert_editor_state(indoc! {"
12449        fn a() {
12450            «a();
12451            b();
12452            c();ˇ»
12453        }
12454    "});
12455}
12456
12457#[gpui::test]
12458async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12459    init_test(cx, |_| {});
12460
12461    let language = Arc::new(Language::new(
12462        LanguageConfig {
12463            line_comments: vec!["// ".into()],
12464            ..Default::default()
12465        },
12466        Some(tree_sitter_rust::LANGUAGE.into()),
12467    ));
12468
12469    let mut cx = EditorTestContext::new(cx).await;
12470
12471    cx.language_registry().add(language.clone());
12472    cx.update_buffer(|buffer, cx| {
12473        buffer.set_language(Some(language), cx);
12474    });
12475
12476    let toggle_comments = &ToggleComments {
12477        advance_downwards: true,
12478        ignore_indent: false,
12479    };
12480
12481    // Single cursor on one line -> advance
12482    // Cursor moves horizontally 3 characters as well on non-blank line
12483    cx.set_state(indoc!(
12484        "fn a() {
12485             ˇdog();
12486             cat();
12487        }"
12488    ));
12489    cx.update_editor(|editor, window, cx| {
12490        editor.toggle_comments(toggle_comments, window, cx);
12491    });
12492    cx.assert_editor_state(indoc!(
12493        "fn a() {
12494             // dog();
12495             catˇ();
12496        }"
12497    ));
12498
12499    // Single selection on one line -> don't advance
12500    cx.set_state(indoc!(
12501        "fn a() {
12502             «dog()ˇ»;
12503             cat();
12504        }"
12505    ));
12506    cx.update_editor(|editor, window, cx| {
12507        editor.toggle_comments(toggle_comments, window, cx);
12508    });
12509    cx.assert_editor_state(indoc!(
12510        "fn a() {
12511             // «dog()ˇ»;
12512             cat();
12513        }"
12514    ));
12515
12516    // Multiple cursors on one line -> advance
12517    cx.set_state(indoc!(
12518        "fn a() {
12519             ˇdˇog();
12520             cat();
12521        }"
12522    ));
12523    cx.update_editor(|editor, window, cx| {
12524        editor.toggle_comments(toggle_comments, window, cx);
12525    });
12526    cx.assert_editor_state(indoc!(
12527        "fn a() {
12528             // dog();
12529             catˇ(ˇ);
12530        }"
12531    ));
12532
12533    // Multiple cursors on one line, with selection -> don't advance
12534    cx.set_state(indoc!(
12535        "fn a() {
12536             ˇdˇog«()ˇ»;
12537             cat();
12538        }"
12539    ));
12540    cx.update_editor(|editor, window, cx| {
12541        editor.toggle_comments(toggle_comments, window, cx);
12542    });
12543    cx.assert_editor_state(indoc!(
12544        "fn a() {
12545             // ˇdˇog«()ˇ»;
12546             cat();
12547        }"
12548    ));
12549
12550    // Single cursor on one line -> advance
12551    // Cursor moves to column 0 on blank line
12552    cx.set_state(indoc!(
12553        "fn a() {
12554             ˇdog();
12555
12556             cat();
12557        }"
12558    ));
12559    cx.update_editor(|editor, window, cx| {
12560        editor.toggle_comments(toggle_comments, window, cx);
12561    });
12562    cx.assert_editor_state(indoc!(
12563        "fn a() {
12564             // dog();
12565        ˇ
12566             cat();
12567        }"
12568    ));
12569
12570    // Single cursor on one line -> advance
12571    // Cursor starts and ends at column 0
12572    cx.set_state(indoc!(
12573        "fn a() {
12574         ˇ    dog();
12575             cat();
12576        }"
12577    ));
12578    cx.update_editor(|editor, window, cx| {
12579        editor.toggle_comments(toggle_comments, window, cx);
12580    });
12581    cx.assert_editor_state(indoc!(
12582        "fn a() {
12583             // dog();
12584         ˇ    cat();
12585        }"
12586    ));
12587}
12588
12589#[gpui::test]
12590async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12591    init_test(cx, |_| {});
12592
12593    let mut cx = EditorTestContext::new(cx).await;
12594
12595    let html_language = Arc::new(
12596        Language::new(
12597            LanguageConfig {
12598                name: "HTML".into(),
12599                block_comment: Some(("<!-- ".into(), " -->".into())),
12600                ..Default::default()
12601            },
12602            Some(tree_sitter_html::LANGUAGE.into()),
12603        )
12604        .with_injection_query(
12605            r#"
12606            (script_element
12607                (raw_text) @injection.content
12608                (#set! injection.language "javascript"))
12609            "#,
12610        )
12611        .unwrap(),
12612    );
12613
12614    let javascript_language = Arc::new(Language::new(
12615        LanguageConfig {
12616            name: "JavaScript".into(),
12617            line_comments: vec!["// ".into()],
12618            ..Default::default()
12619        },
12620        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12621    ));
12622
12623    cx.language_registry().add(html_language.clone());
12624    cx.language_registry().add(javascript_language.clone());
12625    cx.update_buffer(|buffer, cx| {
12626        buffer.set_language(Some(html_language), cx);
12627    });
12628
12629    // Toggle comments for empty selections
12630    cx.set_state(
12631        &r#"
12632            <p>A</p>ˇ
12633            <p>B</p>ˇ
12634            <p>C</p>ˇ
12635        "#
12636        .unindent(),
12637    );
12638    cx.update_editor(|editor, window, cx| {
12639        editor.toggle_comments(&ToggleComments::default(), window, cx)
12640    });
12641    cx.assert_editor_state(
12642        &r#"
12643            <!-- <p>A</p>ˇ -->
12644            <!-- <p>B</p>ˇ -->
12645            <!-- <p>C</p>ˇ -->
12646        "#
12647        .unindent(),
12648    );
12649    cx.update_editor(|editor, window, cx| {
12650        editor.toggle_comments(&ToggleComments::default(), window, cx)
12651    });
12652    cx.assert_editor_state(
12653        &r#"
12654            <p>A</p>ˇ
12655            <p>B</p>ˇ
12656            <p>C</p>ˇ
12657        "#
12658        .unindent(),
12659    );
12660
12661    // Toggle comments for mixture of empty and non-empty selections, where
12662    // multiple selections occupy a given line.
12663    cx.set_state(
12664        &r#"
12665            <p>A«</p>
12666            <p>ˇ»B</p>ˇ
12667            <p>C«</p>
12668            <p>ˇ»D</p>ˇ
12669        "#
12670        .unindent(),
12671    );
12672
12673    cx.update_editor(|editor, window, cx| {
12674        editor.toggle_comments(&ToggleComments::default(), window, cx)
12675    });
12676    cx.assert_editor_state(
12677        &r#"
12678            <!-- <p>A«</p>
12679            <p>ˇ»B</p>ˇ -->
12680            <!-- <p>C«</p>
12681            <p>ˇ»D</p>ˇ -->
12682        "#
12683        .unindent(),
12684    );
12685    cx.update_editor(|editor, window, cx| {
12686        editor.toggle_comments(&ToggleComments::default(), window, cx)
12687    });
12688    cx.assert_editor_state(
12689        &r#"
12690            <p>A«</p>
12691            <p>ˇ»B</p>ˇ
12692            <p>C«</p>
12693            <p>ˇ»D</p>ˇ
12694        "#
12695        .unindent(),
12696    );
12697
12698    // Toggle comments when different languages are active for different
12699    // selections.
12700    cx.set_state(
12701        &r#"
12702            ˇ<script>
12703                ˇvar x = new Y();
12704            ˇ</script>
12705        "#
12706        .unindent(),
12707    );
12708    cx.executor().run_until_parked();
12709    cx.update_editor(|editor, window, cx| {
12710        editor.toggle_comments(&ToggleComments::default(), window, cx)
12711    });
12712    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12713    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12714    cx.assert_editor_state(
12715        &r#"
12716            <!-- ˇ<script> -->
12717                // ˇvar x = new Y();
12718            <!-- ˇ</script> -->
12719        "#
12720        .unindent(),
12721    );
12722}
12723
12724#[gpui::test]
12725fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12726    init_test(cx, |_| {});
12727
12728    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12729    let multibuffer = cx.new(|cx| {
12730        let mut multibuffer = MultiBuffer::new(ReadWrite);
12731        multibuffer.push_excerpts(
12732            buffer.clone(),
12733            [
12734                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12735                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12736            ],
12737            cx,
12738        );
12739        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12740        multibuffer
12741    });
12742
12743    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12744    editor.update_in(cx, |editor, window, cx| {
12745        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12746        editor.change_selections(None, window, cx, |s| {
12747            s.select_ranges([
12748                Point::new(0, 0)..Point::new(0, 0),
12749                Point::new(1, 0)..Point::new(1, 0),
12750            ])
12751        });
12752
12753        editor.handle_input("X", window, cx);
12754        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12755        assert_eq!(
12756            editor.selections.ranges(cx),
12757            [
12758                Point::new(0, 1)..Point::new(0, 1),
12759                Point::new(1, 1)..Point::new(1, 1),
12760            ]
12761        );
12762
12763        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12764        editor.change_selections(None, window, cx, |s| {
12765            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12766        });
12767        editor.backspace(&Default::default(), window, cx);
12768        assert_eq!(editor.text(cx), "Xa\nbbb");
12769        assert_eq!(
12770            editor.selections.ranges(cx),
12771            [Point::new(1, 0)..Point::new(1, 0)]
12772        );
12773
12774        editor.change_selections(None, window, cx, |s| {
12775            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12776        });
12777        editor.backspace(&Default::default(), window, cx);
12778        assert_eq!(editor.text(cx), "X\nbb");
12779        assert_eq!(
12780            editor.selections.ranges(cx),
12781            [Point::new(0, 1)..Point::new(0, 1)]
12782        );
12783    });
12784}
12785
12786#[gpui::test]
12787fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12788    init_test(cx, |_| {});
12789
12790    let markers = vec![('[', ']').into(), ('(', ')').into()];
12791    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12792        indoc! {"
12793            [aaaa
12794            (bbbb]
12795            cccc)",
12796        },
12797        markers.clone(),
12798    );
12799    let excerpt_ranges = markers.into_iter().map(|marker| {
12800        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12801        ExcerptRange::new(context.clone())
12802    });
12803    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12804    let multibuffer = cx.new(|cx| {
12805        let mut multibuffer = MultiBuffer::new(ReadWrite);
12806        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12807        multibuffer
12808    });
12809
12810    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12811    editor.update_in(cx, |editor, window, cx| {
12812        let (expected_text, selection_ranges) = marked_text_ranges(
12813            indoc! {"
12814                aaaa
12815                bˇbbb
12816                bˇbbˇb
12817                cccc"
12818            },
12819            true,
12820        );
12821        assert_eq!(editor.text(cx), expected_text);
12822        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12823
12824        editor.handle_input("X", window, cx);
12825
12826        let (expected_text, expected_selections) = marked_text_ranges(
12827            indoc! {"
12828                aaaa
12829                bXˇbbXb
12830                bXˇbbXˇb
12831                cccc"
12832            },
12833            false,
12834        );
12835        assert_eq!(editor.text(cx), expected_text);
12836        assert_eq!(editor.selections.ranges(cx), expected_selections);
12837
12838        editor.newline(&Newline, window, cx);
12839        let (expected_text, expected_selections) = marked_text_ranges(
12840            indoc! {"
12841                aaaa
12842                bX
12843                ˇbbX
12844                b
12845                bX
12846                ˇbbX
12847                ˇb
12848                cccc"
12849            },
12850            false,
12851        );
12852        assert_eq!(editor.text(cx), expected_text);
12853        assert_eq!(editor.selections.ranges(cx), expected_selections);
12854    });
12855}
12856
12857#[gpui::test]
12858fn test_refresh_selections(cx: &mut TestAppContext) {
12859    init_test(cx, |_| {});
12860
12861    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12862    let mut excerpt1_id = None;
12863    let multibuffer = cx.new(|cx| {
12864        let mut multibuffer = MultiBuffer::new(ReadWrite);
12865        excerpt1_id = multibuffer
12866            .push_excerpts(
12867                buffer.clone(),
12868                [
12869                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12870                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12871                ],
12872                cx,
12873            )
12874            .into_iter()
12875            .next();
12876        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12877        multibuffer
12878    });
12879
12880    let editor = cx.add_window(|window, cx| {
12881        let mut editor = build_editor(multibuffer.clone(), window, cx);
12882        let snapshot = editor.snapshot(window, cx);
12883        editor.change_selections(None, window, cx, |s| {
12884            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12885        });
12886        editor.begin_selection(
12887            Point::new(2, 1).to_display_point(&snapshot),
12888            true,
12889            1,
12890            window,
12891            cx,
12892        );
12893        assert_eq!(
12894            editor.selections.ranges(cx),
12895            [
12896                Point::new(1, 3)..Point::new(1, 3),
12897                Point::new(2, 1)..Point::new(2, 1),
12898            ]
12899        );
12900        editor
12901    });
12902
12903    // Refreshing selections is a no-op when excerpts haven't changed.
12904    _ = editor.update(cx, |editor, window, cx| {
12905        editor.change_selections(None, window, cx, |s| s.refresh());
12906        assert_eq!(
12907            editor.selections.ranges(cx),
12908            [
12909                Point::new(1, 3)..Point::new(1, 3),
12910                Point::new(2, 1)..Point::new(2, 1),
12911            ]
12912        );
12913    });
12914
12915    multibuffer.update(cx, |multibuffer, cx| {
12916        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12917    });
12918    _ = editor.update(cx, |editor, window, cx| {
12919        // Removing an excerpt causes the first selection to become degenerate.
12920        assert_eq!(
12921            editor.selections.ranges(cx),
12922            [
12923                Point::new(0, 0)..Point::new(0, 0),
12924                Point::new(0, 1)..Point::new(0, 1)
12925            ]
12926        );
12927
12928        // Refreshing selections will relocate the first selection to the original buffer
12929        // location.
12930        editor.change_selections(None, window, cx, |s| s.refresh());
12931        assert_eq!(
12932            editor.selections.ranges(cx),
12933            [
12934                Point::new(0, 1)..Point::new(0, 1),
12935                Point::new(0, 3)..Point::new(0, 3)
12936            ]
12937        );
12938        assert!(editor.selections.pending_anchor().is_some());
12939    });
12940}
12941
12942#[gpui::test]
12943fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12944    init_test(cx, |_| {});
12945
12946    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12947    let mut excerpt1_id = None;
12948    let multibuffer = cx.new(|cx| {
12949        let mut multibuffer = MultiBuffer::new(ReadWrite);
12950        excerpt1_id = multibuffer
12951            .push_excerpts(
12952                buffer.clone(),
12953                [
12954                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12955                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12956                ],
12957                cx,
12958            )
12959            .into_iter()
12960            .next();
12961        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12962        multibuffer
12963    });
12964
12965    let editor = cx.add_window(|window, cx| {
12966        let mut editor = build_editor(multibuffer.clone(), window, cx);
12967        let snapshot = editor.snapshot(window, cx);
12968        editor.begin_selection(
12969            Point::new(1, 3).to_display_point(&snapshot),
12970            false,
12971            1,
12972            window,
12973            cx,
12974        );
12975        assert_eq!(
12976            editor.selections.ranges(cx),
12977            [Point::new(1, 3)..Point::new(1, 3)]
12978        );
12979        editor
12980    });
12981
12982    multibuffer.update(cx, |multibuffer, cx| {
12983        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12984    });
12985    _ = editor.update(cx, |editor, window, cx| {
12986        assert_eq!(
12987            editor.selections.ranges(cx),
12988            [Point::new(0, 0)..Point::new(0, 0)]
12989        );
12990
12991        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12992        editor.change_selections(None, window, cx, |s| s.refresh());
12993        assert_eq!(
12994            editor.selections.ranges(cx),
12995            [Point::new(0, 3)..Point::new(0, 3)]
12996        );
12997        assert!(editor.selections.pending_anchor().is_some());
12998    });
12999}
13000
13001#[gpui::test]
13002async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13003    init_test(cx, |_| {});
13004
13005    let language = Arc::new(
13006        Language::new(
13007            LanguageConfig {
13008                brackets: BracketPairConfig {
13009                    pairs: vec![
13010                        BracketPair {
13011                            start: "{".to_string(),
13012                            end: "}".to_string(),
13013                            close: true,
13014                            surround: true,
13015                            newline: true,
13016                        },
13017                        BracketPair {
13018                            start: "/* ".to_string(),
13019                            end: " */".to_string(),
13020                            close: true,
13021                            surround: true,
13022                            newline: true,
13023                        },
13024                    ],
13025                    ..Default::default()
13026                },
13027                ..Default::default()
13028            },
13029            Some(tree_sitter_rust::LANGUAGE.into()),
13030        )
13031        .with_indents_query("")
13032        .unwrap(),
13033    );
13034
13035    let text = concat!(
13036        "{   }\n",     //
13037        "  x\n",       //
13038        "  /*   */\n", //
13039        "x\n",         //
13040        "{{} }\n",     //
13041    );
13042
13043    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13044    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13045    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13046    editor
13047        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13048        .await;
13049
13050    editor.update_in(cx, |editor, window, cx| {
13051        editor.change_selections(None, window, cx, |s| {
13052            s.select_display_ranges([
13053                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13054                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13055                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13056            ])
13057        });
13058        editor.newline(&Newline, window, cx);
13059
13060        assert_eq!(
13061            editor.buffer().read(cx).read(cx).text(),
13062            concat!(
13063                "{ \n",    // Suppress rustfmt
13064                "\n",      //
13065                "}\n",     //
13066                "  x\n",   //
13067                "  /* \n", //
13068                "  \n",    //
13069                "  */\n",  //
13070                "x\n",     //
13071                "{{} \n",  //
13072                "}\n",     //
13073            )
13074        );
13075    });
13076}
13077
13078#[gpui::test]
13079fn test_highlighted_ranges(cx: &mut TestAppContext) {
13080    init_test(cx, |_| {});
13081
13082    let editor = cx.add_window(|window, cx| {
13083        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13084        build_editor(buffer.clone(), window, cx)
13085    });
13086
13087    _ = editor.update(cx, |editor, window, cx| {
13088        struct Type1;
13089        struct Type2;
13090
13091        let buffer = editor.buffer.read(cx).snapshot(cx);
13092
13093        let anchor_range =
13094            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13095
13096        editor.highlight_background::<Type1>(
13097            &[
13098                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13099                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13100                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13101                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13102            ],
13103            |_| Hsla::red(),
13104            cx,
13105        );
13106        editor.highlight_background::<Type2>(
13107            &[
13108                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13109                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13110                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13111                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13112            ],
13113            |_| Hsla::green(),
13114            cx,
13115        );
13116
13117        let snapshot = editor.snapshot(window, cx);
13118        let mut highlighted_ranges = editor.background_highlights_in_range(
13119            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13120            &snapshot,
13121            cx.theme().colors(),
13122        );
13123        // Enforce a consistent ordering based on color without relying on the ordering of the
13124        // highlight's `TypeId` which is non-executor.
13125        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13126        assert_eq!(
13127            highlighted_ranges,
13128            &[
13129                (
13130                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13131                    Hsla::red(),
13132                ),
13133                (
13134                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13135                    Hsla::red(),
13136                ),
13137                (
13138                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13139                    Hsla::green(),
13140                ),
13141                (
13142                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13143                    Hsla::green(),
13144                ),
13145            ]
13146        );
13147        assert_eq!(
13148            editor.background_highlights_in_range(
13149                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13150                &snapshot,
13151                cx.theme().colors(),
13152            ),
13153            &[(
13154                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13155                Hsla::red(),
13156            )]
13157        );
13158    });
13159}
13160
13161#[gpui::test]
13162async fn test_following(cx: &mut TestAppContext) {
13163    init_test(cx, |_| {});
13164
13165    let fs = FakeFs::new(cx.executor());
13166    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13167
13168    let buffer = project.update(cx, |project, cx| {
13169        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13170        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13171    });
13172    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13173    let follower = cx.update(|cx| {
13174        cx.open_window(
13175            WindowOptions {
13176                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13177                    gpui::Point::new(px(0.), px(0.)),
13178                    gpui::Point::new(px(10.), px(80.)),
13179                ))),
13180                ..Default::default()
13181            },
13182            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13183        )
13184        .unwrap()
13185    });
13186
13187    let is_still_following = Rc::new(RefCell::new(true));
13188    let follower_edit_event_count = Rc::new(RefCell::new(0));
13189    let pending_update = Rc::new(RefCell::new(None));
13190    let leader_entity = leader.root(cx).unwrap();
13191    let follower_entity = follower.root(cx).unwrap();
13192    _ = follower.update(cx, {
13193        let update = pending_update.clone();
13194        let is_still_following = is_still_following.clone();
13195        let follower_edit_event_count = follower_edit_event_count.clone();
13196        |_, window, cx| {
13197            cx.subscribe_in(
13198                &leader_entity,
13199                window,
13200                move |_, leader, event, window, cx| {
13201                    leader.read(cx).add_event_to_update_proto(
13202                        event,
13203                        &mut update.borrow_mut(),
13204                        window,
13205                        cx,
13206                    );
13207                },
13208            )
13209            .detach();
13210
13211            cx.subscribe_in(
13212                &follower_entity,
13213                window,
13214                move |_, _, event: &EditorEvent, _window, _cx| {
13215                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13216                        *is_still_following.borrow_mut() = false;
13217                    }
13218
13219                    if let EditorEvent::BufferEdited = event {
13220                        *follower_edit_event_count.borrow_mut() += 1;
13221                    }
13222                },
13223            )
13224            .detach();
13225        }
13226    });
13227
13228    // Update the selections only
13229    _ = leader.update(cx, |leader, window, cx| {
13230        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13231    });
13232    follower
13233        .update(cx, |follower, window, cx| {
13234            follower.apply_update_proto(
13235                &project,
13236                pending_update.borrow_mut().take().unwrap(),
13237                window,
13238                cx,
13239            )
13240        })
13241        .unwrap()
13242        .await
13243        .unwrap();
13244    _ = follower.update(cx, |follower, _, cx| {
13245        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13246    });
13247    assert!(*is_still_following.borrow());
13248    assert_eq!(*follower_edit_event_count.borrow(), 0);
13249
13250    // Update the scroll position only
13251    _ = leader.update(cx, |leader, window, cx| {
13252        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13253    });
13254    follower
13255        .update(cx, |follower, window, cx| {
13256            follower.apply_update_proto(
13257                &project,
13258                pending_update.borrow_mut().take().unwrap(),
13259                window,
13260                cx,
13261            )
13262        })
13263        .unwrap()
13264        .await
13265        .unwrap();
13266    assert_eq!(
13267        follower
13268            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13269            .unwrap(),
13270        gpui::Point::new(1.5, 3.5)
13271    );
13272    assert!(*is_still_following.borrow());
13273    assert_eq!(*follower_edit_event_count.borrow(), 0);
13274
13275    // Update the selections and scroll position. The follower's scroll position is updated
13276    // via autoscroll, not via the leader's exact scroll position.
13277    _ = leader.update(cx, |leader, window, cx| {
13278        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13279        leader.request_autoscroll(Autoscroll::newest(), cx);
13280        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13281    });
13282    follower
13283        .update(cx, |follower, window, cx| {
13284            follower.apply_update_proto(
13285                &project,
13286                pending_update.borrow_mut().take().unwrap(),
13287                window,
13288                cx,
13289            )
13290        })
13291        .unwrap()
13292        .await
13293        .unwrap();
13294    _ = follower.update(cx, |follower, _, cx| {
13295        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13296        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13297    });
13298    assert!(*is_still_following.borrow());
13299
13300    // Creating a pending selection that precedes another selection
13301    _ = leader.update(cx, |leader, window, cx| {
13302        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13303        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13304    });
13305    follower
13306        .update(cx, |follower, window, cx| {
13307            follower.apply_update_proto(
13308                &project,
13309                pending_update.borrow_mut().take().unwrap(),
13310                window,
13311                cx,
13312            )
13313        })
13314        .unwrap()
13315        .await
13316        .unwrap();
13317    _ = follower.update(cx, |follower, _, cx| {
13318        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13319    });
13320    assert!(*is_still_following.borrow());
13321
13322    // Extend the pending selection so that it surrounds another selection
13323    _ = leader.update(cx, |leader, window, cx| {
13324        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13325    });
13326    follower
13327        .update(cx, |follower, window, cx| {
13328            follower.apply_update_proto(
13329                &project,
13330                pending_update.borrow_mut().take().unwrap(),
13331                window,
13332                cx,
13333            )
13334        })
13335        .unwrap()
13336        .await
13337        .unwrap();
13338    _ = follower.update(cx, |follower, _, cx| {
13339        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13340    });
13341
13342    // Scrolling locally breaks the follow
13343    _ = follower.update(cx, |follower, window, cx| {
13344        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13345        follower.set_scroll_anchor(
13346            ScrollAnchor {
13347                anchor: top_anchor,
13348                offset: gpui::Point::new(0.0, 0.5),
13349            },
13350            window,
13351            cx,
13352        );
13353    });
13354    assert!(!(*is_still_following.borrow()));
13355}
13356
13357#[gpui::test]
13358async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13359    init_test(cx, |_| {});
13360
13361    let fs = FakeFs::new(cx.executor());
13362    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13363    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13364    let pane = workspace
13365        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13366        .unwrap();
13367
13368    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13369
13370    let leader = pane.update_in(cx, |_, window, cx| {
13371        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13372        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13373    });
13374
13375    // Start following the editor when it has no excerpts.
13376    let mut state_message =
13377        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13378    let workspace_entity = workspace.root(cx).unwrap();
13379    let follower_1 = cx
13380        .update_window(*workspace.deref(), |_, window, cx| {
13381            Editor::from_state_proto(
13382                workspace_entity,
13383                ViewId {
13384                    creator: CollaboratorId::PeerId(PeerId::default()),
13385                    id: 0,
13386                },
13387                &mut state_message,
13388                window,
13389                cx,
13390            )
13391        })
13392        .unwrap()
13393        .unwrap()
13394        .await
13395        .unwrap();
13396
13397    let update_message = Rc::new(RefCell::new(None));
13398    follower_1.update_in(cx, {
13399        let update = update_message.clone();
13400        |_, window, cx| {
13401            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13402                leader.read(cx).add_event_to_update_proto(
13403                    event,
13404                    &mut update.borrow_mut(),
13405                    window,
13406                    cx,
13407                );
13408            })
13409            .detach();
13410        }
13411    });
13412
13413    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13414        (
13415            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13416            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13417        )
13418    });
13419
13420    // Insert some excerpts.
13421    leader.update(cx, |leader, cx| {
13422        leader.buffer.update(cx, |multibuffer, cx| {
13423            multibuffer.set_excerpts_for_path(
13424                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13425                buffer_1.clone(),
13426                vec![
13427                    Point::row_range(0..3),
13428                    Point::row_range(1..6),
13429                    Point::row_range(12..15),
13430                ],
13431                0,
13432                cx,
13433            );
13434            multibuffer.set_excerpts_for_path(
13435                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13436                buffer_2.clone(),
13437                vec![Point::row_range(0..6), Point::row_range(8..12)],
13438                0,
13439                cx,
13440            );
13441        });
13442    });
13443
13444    // Apply the update of adding the excerpts.
13445    follower_1
13446        .update_in(cx, |follower, window, cx| {
13447            follower.apply_update_proto(
13448                &project,
13449                update_message.borrow().clone().unwrap(),
13450                window,
13451                cx,
13452            )
13453        })
13454        .await
13455        .unwrap();
13456    assert_eq!(
13457        follower_1.update(cx, |editor, cx| editor.text(cx)),
13458        leader.update(cx, |editor, cx| editor.text(cx))
13459    );
13460    update_message.borrow_mut().take();
13461
13462    // Start following separately after it already has excerpts.
13463    let mut state_message =
13464        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13465    let workspace_entity = workspace.root(cx).unwrap();
13466    let follower_2 = cx
13467        .update_window(*workspace.deref(), |_, window, cx| {
13468            Editor::from_state_proto(
13469                workspace_entity,
13470                ViewId {
13471                    creator: CollaboratorId::PeerId(PeerId::default()),
13472                    id: 0,
13473                },
13474                &mut state_message,
13475                window,
13476                cx,
13477            )
13478        })
13479        .unwrap()
13480        .unwrap()
13481        .await
13482        .unwrap();
13483    assert_eq!(
13484        follower_2.update(cx, |editor, cx| editor.text(cx)),
13485        leader.update(cx, |editor, cx| editor.text(cx))
13486    );
13487
13488    // Remove some excerpts.
13489    leader.update(cx, |leader, cx| {
13490        leader.buffer.update(cx, |multibuffer, cx| {
13491            let excerpt_ids = multibuffer.excerpt_ids();
13492            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13493            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13494        });
13495    });
13496
13497    // Apply the update of removing the excerpts.
13498    follower_1
13499        .update_in(cx, |follower, window, cx| {
13500            follower.apply_update_proto(
13501                &project,
13502                update_message.borrow().clone().unwrap(),
13503                window,
13504                cx,
13505            )
13506        })
13507        .await
13508        .unwrap();
13509    follower_2
13510        .update_in(cx, |follower, window, cx| {
13511            follower.apply_update_proto(
13512                &project,
13513                update_message.borrow().clone().unwrap(),
13514                window,
13515                cx,
13516            )
13517        })
13518        .await
13519        .unwrap();
13520    update_message.borrow_mut().take();
13521    assert_eq!(
13522        follower_1.update(cx, |editor, cx| editor.text(cx)),
13523        leader.update(cx, |editor, cx| editor.text(cx))
13524    );
13525}
13526
13527#[gpui::test]
13528async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13529    init_test(cx, |_| {});
13530
13531    let mut cx = EditorTestContext::new(cx).await;
13532    let lsp_store =
13533        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13534
13535    cx.set_state(indoc! {"
13536        ˇfn func(abc def: i32) -> u32 {
13537        }
13538    "});
13539
13540    cx.update(|_, cx| {
13541        lsp_store.update(cx, |lsp_store, cx| {
13542            lsp_store
13543                .update_diagnostics(
13544                    LanguageServerId(0),
13545                    lsp::PublishDiagnosticsParams {
13546                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13547                        version: None,
13548                        diagnostics: vec![
13549                            lsp::Diagnostic {
13550                                range: lsp::Range::new(
13551                                    lsp::Position::new(0, 11),
13552                                    lsp::Position::new(0, 12),
13553                                ),
13554                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13555                                ..Default::default()
13556                            },
13557                            lsp::Diagnostic {
13558                                range: lsp::Range::new(
13559                                    lsp::Position::new(0, 12),
13560                                    lsp::Position::new(0, 15),
13561                                ),
13562                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13563                                ..Default::default()
13564                            },
13565                            lsp::Diagnostic {
13566                                range: lsp::Range::new(
13567                                    lsp::Position::new(0, 25),
13568                                    lsp::Position::new(0, 28),
13569                                ),
13570                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13571                                ..Default::default()
13572                            },
13573                        ],
13574                    },
13575                    &[],
13576                    cx,
13577                )
13578                .unwrap()
13579        });
13580    });
13581
13582    executor.run_until_parked();
13583
13584    cx.update_editor(|editor, window, cx| {
13585        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13586    });
13587
13588    cx.assert_editor_state(indoc! {"
13589        fn func(abc def: i32) -> ˇu32 {
13590        }
13591    "});
13592
13593    cx.update_editor(|editor, window, cx| {
13594        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13595    });
13596
13597    cx.assert_editor_state(indoc! {"
13598        fn func(abc ˇdef: i32) -> u32 {
13599        }
13600    "});
13601
13602    cx.update_editor(|editor, window, cx| {
13603        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13604    });
13605
13606    cx.assert_editor_state(indoc! {"
13607        fn func(abcˇ def: i32) -> u32 {
13608        }
13609    "});
13610
13611    cx.update_editor(|editor, window, cx| {
13612        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13613    });
13614
13615    cx.assert_editor_state(indoc! {"
13616        fn func(abc def: i32) -> ˇu32 {
13617        }
13618    "});
13619}
13620
13621#[gpui::test]
13622async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13623    init_test(cx, |_| {});
13624
13625    let mut cx = EditorTestContext::new(cx).await;
13626
13627    let diff_base = r#"
13628        use some::mod;
13629
13630        const A: u32 = 42;
13631
13632        fn main() {
13633            println!("hello");
13634
13635            println!("world");
13636        }
13637        "#
13638    .unindent();
13639
13640    // Edits are modified, removed, modified, added
13641    cx.set_state(
13642        &r#"
13643        use some::modified;
13644
13645        ˇ
13646        fn main() {
13647            println!("hello there");
13648
13649            println!("around the");
13650            println!("world");
13651        }
13652        "#
13653        .unindent(),
13654    );
13655
13656    cx.set_head_text(&diff_base);
13657    executor.run_until_parked();
13658
13659    cx.update_editor(|editor, window, cx| {
13660        //Wrap around the bottom of the buffer
13661        for _ in 0..3 {
13662            editor.go_to_next_hunk(&GoToHunk, window, cx);
13663        }
13664    });
13665
13666    cx.assert_editor_state(
13667        &r#"
13668        ˇuse some::modified;
13669
13670
13671        fn main() {
13672            println!("hello there");
13673
13674            println!("around the");
13675            println!("world");
13676        }
13677        "#
13678        .unindent(),
13679    );
13680
13681    cx.update_editor(|editor, window, cx| {
13682        //Wrap around the top of the buffer
13683        for _ in 0..2 {
13684            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13685        }
13686    });
13687
13688    cx.assert_editor_state(
13689        &r#"
13690        use some::modified;
13691
13692
13693        fn main() {
13694        ˇ    println!("hello there");
13695
13696            println!("around the");
13697            println!("world");
13698        }
13699        "#
13700        .unindent(),
13701    );
13702
13703    cx.update_editor(|editor, window, cx| {
13704        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13705    });
13706
13707    cx.assert_editor_state(
13708        &r#"
13709        use some::modified;
13710
13711        ˇ
13712        fn main() {
13713            println!("hello there");
13714
13715            println!("around the");
13716            println!("world");
13717        }
13718        "#
13719        .unindent(),
13720    );
13721
13722    cx.update_editor(|editor, window, cx| {
13723        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13724    });
13725
13726    cx.assert_editor_state(
13727        &r#"
13728        ˇuse some::modified;
13729
13730
13731        fn main() {
13732            println!("hello there");
13733
13734            println!("around the");
13735            println!("world");
13736        }
13737        "#
13738        .unindent(),
13739    );
13740
13741    cx.update_editor(|editor, window, cx| {
13742        for _ in 0..2 {
13743            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13744        }
13745    });
13746
13747    cx.assert_editor_state(
13748        &r#"
13749        use some::modified;
13750
13751
13752        fn main() {
13753        ˇ    println!("hello there");
13754
13755            println!("around the");
13756            println!("world");
13757        }
13758        "#
13759        .unindent(),
13760    );
13761
13762    cx.update_editor(|editor, window, cx| {
13763        editor.fold(&Fold, window, cx);
13764    });
13765
13766    cx.update_editor(|editor, window, cx| {
13767        editor.go_to_next_hunk(&GoToHunk, window, cx);
13768    });
13769
13770    cx.assert_editor_state(
13771        &r#"
13772        ˇuse some::modified;
13773
13774
13775        fn main() {
13776            println!("hello there");
13777
13778            println!("around the");
13779            println!("world");
13780        }
13781        "#
13782        .unindent(),
13783    );
13784}
13785
13786#[test]
13787fn test_split_words() {
13788    fn split(text: &str) -> Vec<&str> {
13789        split_words(text).collect()
13790    }
13791
13792    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13793    assert_eq!(split("hello_world"), &["hello_", "world"]);
13794    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13795    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13796    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13797    assert_eq!(split("helloworld"), &["helloworld"]);
13798
13799    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13800}
13801
13802#[gpui::test]
13803async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13804    init_test(cx, |_| {});
13805
13806    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13807    let mut assert = |before, after| {
13808        let _state_context = cx.set_state(before);
13809        cx.run_until_parked();
13810        cx.update_editor(|editor, window, cx| {
13811            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13812        });
13813        cx.run_until_parked();
13814        cx.assert_editor_state(after);
13815    };
13816
13817    // Outside bracket jumps to outside of matching bracket
13818    assert("console.logˇ(var);", "console.log(var)ˇ;");
13819    assert("console.log(var)ˇ;", "console.logˇ(var);");
13820
13821    // Inside bracket jumps to inside of matching bracket
13822    assert("console.log(ˇvar);", "console.log(varˇ);");
13823    assert("console.log(varˇ);", "console.log(ˇvar);");
13824
13825    // When outside a bracket and inside, favor jumping to the inside bracket
13826    assert(
13827        "console.log('foo', [1, 2, 3]ˇ);",
13828        "console.log(ˇ'foo', [1, 2, 3]);",
13829    );
13830    assert(
13831        "console.log(ˇ'foo', [1, 2, 3]);",
13832        "console.log('foo', [1, 2, 3]ˇ);",
13833    );
13834
13835    // Bias forward if two options are equally likely
13836    assert(
13837        "let result = curried_fun()ˇ();",
13838        "let result = curried_fun()()ˇ;",
13839    );
13840
13841    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13842    assert(
13843        indoc! {"
13844            function test() {
13845                console.log('test')ˇ
13846            }"},
13847        indoc! {"
13848            function test() {
13849                console.logˇ('test')
13850            }"},
13851    );
13852}
13853
13854#[gpui::test]
13855async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13856    init_test(cx, |_| {});
13857
13858    let fs = FakeFs::new(cx.executor());
13859    fs.insert_tree(
13860        path!("/a"),
13861        json!({
13862            "main.rs": "fn main() { let a = 5; }",
13863            "other.rs": "// Test file",
13864        }),
13865    )
13866    .await;
13867    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13868
13869    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13870    language_registry.add(Arc::new(Language::new(
13871        LanguageConfig {
13872            name: "Rust".into(),
13873            matcher: LanguageMatcher {
13874                path_suffixes: vec!["rs".to_string()],
13875                ..Default::default()
13876            },
13877            brackets: BracketPairConfig {
13878                pairs: vec![BracketPair {
13879                    start: "{".to_string(),
13880                    end: "}".to_string(),
13881                    close: true,
13882                    surround: true,
13883                    newline: true,
13884                }],
13885                disabled_scopes_by_bracket_ix: Vec::new(),
13886            },
13887            ..Default::default()
13888        },
13889        Some(tree_sitter_rust::LANGUAGE.into()),
13890    )));
13891    let mut fake_servers = language_registry.register_fake_lsp(
13892        "Rust",
13893        FakeLspAdapter {
13894            capabilities: lsp::ServerCapabilities {
13895                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13896                    first_trigger_character: "{".to_string(),
13897                    more_trigger_character: None,
13898                }),
13899                ..Default::default()
13900            },
13901            ..Default::default()
13902        },
13903    );
13904
13905    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13906
13907    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13908
13909    let worktree_id = workspace
13910        .update(cx, |workspace, _, cx| {
13911            workspace.project().update(cx, |project, cx| {
13912                project.worktrees(cx).next().unwrap().read(cx).id()
13913            })
13914        })
13915        .unwrap();
13916
13917    let buffer = project
13918        .update(cx, |project, cx| {
13919            project.open_local_buffer(path!("/a/main.rs"), cx)
13920        })
13921        .await
13922        .unwrap();
13923    let editor_handle = workspace
13924        .update(cx, |workspace, window, cx| {
13925            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13926        })
13927        .unwrap()
13928        .await
13929        .unwrap()
13930        .downcast::<Editor>()
13931        .unwrap();
13932
13933    cx.executor().start_waiting();
13934    let fake_server = fake_servers.next().await.unwrap();
13935
13936    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13937        |params, _| async move {
13938            assert_eq!(
13939                params.text_document_position.text_document.uri,
13940                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13941            );
13942            assert_eq!(
13943                params.text_document_position.position,
13944                lsp::Position::new(0, 21),
13945            );
13946
13947            Ok(Some(vec![lsp::TextEdit {
13948                new_text: "]".to_string(),
13949                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13950            }]))
13951        },
13952    );
13953
13954    editor_handle.update_in(cx, |editor, window, cx| {
13955        window.focus(&editor.focus_handle(cx));
13956        editor.change_selections(None, window, cx, |s| {
13957            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13958        });
13959        editor.handle_input("{", window, cx);
13960    });
13961
13962    cx.executor().run_until_parked();
13963
13964    buffer.update(cx, |buffer, _| {
13965        assert_eq!(
13966            buffer.text(),
13967            "fn main() { let a = {5}; }",
13968            "No extra braces from on type formatting should appear in the buffer"
13969        )
13970    });
13971}
13972
13973#[gpui::test]
13974async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13975    init_test(cx, |_| {});
13976
13977    let fs = FakeFs::new(cx.executor());
13978    fs.insert_tree(
13979        path!("/a"),
13980        json!({
13981            "main.rs": "fn main() { let a = 5; }",
13982            "other.rs": "// Test file",
13983        }),
13984    )
13985    .await;
13986
13987    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13988
13989    let server_restarts = Arc::new(AtomicUsize::new(0));
13990    let closure_restarts = Arc::clone(&server_restarts);
13991    let language_server_name = "test language server";
13992    let language_name: LanguageName = "Rust".into();
13993
13994    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13995    language_registry.add(Arc::new(Language::new(
13996        LanguageConfig {
13997            name: language_name.clone(),
13998            matcher: LanguageMatcher {
13999                path_suffixes: vec!["rs".to_string()],
14000                ..Default::default()
14001            },
14002            ..Default::default()
14003        },
14004        Some(tree_sitter_rust::LANGUAGE.into()),
14005    )));
14006    let mut fake_servers = language_registry.register_fake_lsp(
14007        "Rust",
14008        FakeLspAdapter {
14009            name: language_server_name,
14010            initialization_options: Some(json!({
14011                "testOptionValue": true
14012            })),
14013            initializer: Some(Box::new(move |fake_server| {
14014                let task_restarts = Arc::clone(&closure_restarts);
14015                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14016                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14017                    futures::future::ready(Ok(()))
14018                });
14019            })),
14020            ..Default::default()
14021        },
14022    );
14023
14024    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14025    let _buffer = project
14026        .update(cx, |project, cx| {
14027            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14028        })
14029        .await
14030        .unwrap();
14031    let _fake_server = fake_servers.next().await.unwrap();
14032    update_test_language_settings(cx, |language_settings| {
14033        language_settings.languages.insert(
14034            language_name.clone(),
14035            LanguageSettingsContent {
14036                tab_size: NonZeroU32::new(8),
14037                ..Default::default()
14038            },
14039        );
14040    });
14041    cx.executor().run_until_parked();
14042    assert_eq!(
14043        server_restarts.load(atomic::Ordering::Acquire),
14044        0,
14045        "Should not restart LSP server on an unrelated change"
14046    );
14047
14048    update_test_project_settings(cx, |project_settings| {
14049        project_settings.lsp.insert(
14050            "Some other server name".into(),
14051            LspSettings {
14052                binary: None,
14053                settings: None,
14054                initialization_options: Some(json!({
14055                    "some other init value": false
14056                })),
14057                enable_lsp_tasks: false,
14058            },
14059        );
14060    });
14061    cx.executor().run_until_parked();
14062    assert_eq!(
14063        server_restarts.load(atomic::Ordering::Acquire),
14064        0,
14065        "Should not restart LSP server on an unrelated LSP settings change"
14066    );
14067
14068    update_test_project_settings(cx, |project_settings| {
14069        project_settings.lsp.insert(
14070            language_server_name.into(),
14071            LspSettings {
14072                binary: None,
14073                settings: None,
14074                initialization_options: Some(json!({
14075                    "anotherInitValue": false
14076                })),
14077                enable_lsp_tasks: false,
14078            },
14079        );
14080    });
14081    cx.executor().run_until_parked();
14082    assert_eq!(
14083        server_restarts.load(atomic::Ordering::Acquire),
14084        1,
14085        "Should restart LSP server on a related LSP settings change"
14086    );
14087
14088    update_test_project_settings(cx, |project_settings| {
14089        project_settings.lsp.insert(
14090            language_server_name.into(),
14091            LspSettings {
14092                binary: None,
14093                settings: None,
14094                initialization_options: Some(json!({
14095                    "anotherInitValue": false
14096                })),
14097                enable_lsp_tasks: false,
14098            },
14099        );
14100    });
14101    cx.executor().run_until_parked();
14102    assert_eq!(
14103        server_restarts.load(atomic::Ordering::Acquire),
14104        1,
14105        "Should not restart LSP server on a related LSP settings change that is the same"
14106    );
14107
14108    update_test_project_settings(cx, |project_settings| {
14109        project_settings.lsp.insert(
14110            language_server_name.into(),
14111            LspSettings {
14112                binary: None,
14113                settings: None,
14114                initialization_options: None,
14115                enable_lsp_tasks: false,
14116            },
14117        );
14118    });
14119    cx.executor().run_until_parked();
14120    assert_eq!(
14121        server_restarts.load(atomic::Ordering::Acquire),
14122        2,
14123        "Should restart LSP server on another related LSP settings change"
14124    );
14125}
14126
14127#[gpui::test]
14128async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14129    init_test(cx, |_| {});
14130
14131    let mut cx = EditorLspTestContext::new_rust(
14132        lsp::ServerCapabilities {
14133            completion_provider: Some(lsp::CompletionOptions {
14134                trigger_characters: Some(vec![".".to_string()]),
14135                resolve_provider: Some(true),
14136                ..Default::default()
14137            }),
14138            ..Default::default()
14139        },
14140        cx,
14141    )
14142    .await;
14143
14144    cx.set_state("fn main() { let a = 2ˇ; }");
14145    cx.simulate_keystroke(".");
14146    let completion_item = lsp::CompletionItem {
14147        label: "some".into(),
14148        kind: Some(lsp::CompletionItemKind::SNIPPET),
14149        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14150        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14151            kind: lsp::MarkupKind::Markdown,
14152            value: "```rust\nSome(2)\n```".to_string(),
14153        })),
14154        deprecated: Some(false),
14155        sort_text: Some("fffffff2".to_string()),
14156        filter_text: Some("some".to_string()),
14157        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14158        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14159            range: lsp::Range {
14160                start: lsp::Position {
14161                    line: 0,
14162                    character: 22,
14163                },
14164                end: lsp::Position {
14165                    line: 0,
14166                    character: 22,
14167                },
14168            },
14169            new_text: "Some(2)".to_string(),
14170        })),
14171        additional_text_edits: Some(vec![lsp::TextEdit {
14172            range: lsp::Range {
14173                start: lsp::Position {
14174                    line: 0,
14175                    character: 20,
14176                },
14177                end: lsp::Position {
14178                    line: 0,
14179                    character: 22,
14180                },
14181            },
14182            new_text: "".to_string(),
14183        }]),
14184        ..Default::default()
14185    };
14186
14187    let closure_completion_item = completion_item.clone();
14188    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14189        let task_completion_item = closure_completion_item.clone();
14190        async move {
14191            Ok(Some(lsp::CompletionResponse::Array(vec![
14192                task_completion_item,
14193            ])))
14194        }
14195    });
14196
14197    request.next().await;
14198
14199    cx.condition(|editor, _| editor.context_menu_visible())
14200        .await;
14201    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14202        editor
14203            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14204            .unwrap()
14205    });
14206    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14207
14208    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14209        let task_completion_item = completion_item.clone();
14210        async move { Ok(task_completion_item) }
14211    })
14212    .next()
14213    .await
14214    .unwrap();
14215    apply_additional_edits.await.unwrap();
14216    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14217}
14218
14219#[gpui::test]
14220async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14221    init_test(cx, |_| {});
14222
14223    let mut cx = EditorLspTestContext::new_rust(
14224        lsp::ServerCapabilities {
14225            completion_provider: Some(lsp::CompletionOptions {
14226                trigger_characters: Some(vec![".".to_string()]),
14227                resolve_provider: Some(true),
14228                ..Default::default()
14229            }),
14230            ..Default::default()
14231        },
14232        cx,
14233    )
14234    .await;
14235
14236    cx.set_state("fn main() { let a = 2ˇ; }");
14237    cx.simulate_keystroke(".");
14238
14239    let item1 = lsp::CompletionItem {
14240        label: "method id()".to_string(),
14241        filter_text: Some("id".to_string()),
14242        detail: None,
14243        documentation: None,
14244        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14245            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14246            new_text: ".id".to_string(),
14247        })),
14248        ..lsp::CompletionItem::default()
14249    };
14250
14251    let item2 = lsp::CompletionItem {
14252        label: "other".to_string(),
14253        filter_text: Some("other".to_string()),
14254        detail: None,
14255        documentation: None,
14256        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14257            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14258            new_text: ".other".to_string(),
14259        })),
14260        ..lsp::CompletionItem::default()
14261    };
14262
14263    let item1 = item1.clone();
14264    cx.set_request_handler::<lsp::request::Completion, _, _>({
14265        let item1 = item1.clone();
14266        move |_, _, _| {
14267            let item1 = item1.clone();
14268            let item2 = item2.clone();
14269            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14270        }
14271    })
14272    .next()
14273    .await;
14274
14275    cx.condition(|editor, _| editor.context_menu_visible())
14276        .await;
14277    cx.update_editor(|editor, _, _| {
14278        let context_menu = editor.context_menu.borrow_mut();
14279        let context_menu = context_menu
14280            .as_ref()
14281            .expect("Should have the context menu deployed");
14282        match context_menu {
14283            CodeContextMenu::Completions(completions_menu) => {
14284                let completions = completions_menu.completions.borrow_mut();
14285                assert_eq!(
14286                    completions
14287                        .iter()
14288                        .map(|completion| &completion.label.text)
14289                        .collect::<Vec<_>>(),
14290                    vec!["method id()", "other"]
14291                )
14292            }
14293            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14294        }
14295    });
14296
14297    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14298        let item1 = item1.clone();
14299        move |_, item_to_resolve, _| {
14300            let item1 = item1.clone();
14301            async move {
14302                if item1 == item_to_resolve {
14303                    Ok(lsp::CompletionItem {
14304                        label: "method id()".to_string(),
14305                        filter_text: Some("id".to_string()),
14306                        detail: Some("Now resolved!".to_string()),
14307                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14308                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14309                            range: lsp::Range::new(
14310                                lsp::Position::new(0, 22),
14311                                lsp::Position::new(0, 22),
14312                            ),
14313                            new_text: ".id".to_string(),
14314                        })),
14315                        ..lsp::CompletionItem::default()
14316                    })
14317                } else {
14318                    Ok(item_to_resolve)
14319                }
14320            }
14321        }
14322    })
14323    .next()
14324    .await
14325    .unwrap();
14326    cx.run_until_parked();
14327
14328    cx.update_editor(|editor, window, cx| {
14329        editor.context_menu_next(&Default::default(), window, cx);
14330    });
14331
14332    cx.update_editor(|editor, _, _| {
14333        let context_menu = editor.context_menu.borrow_mut();
14334        let context_menu = context_menu
14335            .as_ref()
14336            .expect("Should have the context menu deployed");
14337        match context_menu {
14338            CodeContextMenu::Completions(completions_menu) => {
14339                let completions = completions_menu.completions.borrow_mut();
14340                assert_eq!(
14341                    completions
14342                        .iter()
14343                        .map(|completion| &completion.label.text)
14344                        .collect::<Vec<_>>(),
14345                    vec!["method id() Now resolved!", "other"],
14346                    "Should update first completion label, but not second as the filter text did not match."
14347                );
14348            }
14349            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14350        }
14351    });
14352}
14353
14354#[gpui::test]
14355async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14356    init_test(cx, |_| {});
14357    let mut cx = EditorLspTestContext::new_rust(
14358        lsp::ServerCapabilities {
14359            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14360            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14361            completion_provider: Some(lsp::CompletionOptions {
14362                resolve_provider: Some(true),
14363                ..Default::default()
14364            }),
14365            ..Default::default()
14366        },
14367        cx,
14368    )
14369    .await;
14370    cx.set_state(indoc! {"
14371        struct TestStruct {
14372            field: i32
14373        }
14374
14375        fn mainˇ() {
14376            let unused_var = 42;
14377            let test_struct = TestStruct { field: 42 };
14378        }
14379    "});
14380    let symbol_range = cx.lsp_range(indoc! {"
14381        struct TestStruct {
14382            field: i32
14383        }
14384
14385        «fn main»() {
14386            let unused_var = 42;
14387            let test_struct = TestStruct { field: 42 };
14388        }
14389    "});
14390    let mut hover_requests =
14391        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14392            Ok(Some(lsp::Hover {
14393                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14394                    kind: lsp::MarkupKind::Markdown,
14395                    value: "Function documentation".to_string(),
14396                }),
14397                range: Some(symbol_range),
14398            }))
14399        });
14400
14401    // Case 1: Test that code action menu hide hover popover
14402    cx.dispatch_action(Hover);
14403    hover_requests.next().await;
14404    cx.condition(|editor, _| editor.hover_state.visible()).await;
14405    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14406        move |_, _, _| async move {
14407            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14408                lsp::CodeAction {
14409                    title: "Remove unused variable".to_string(),
14410                    kind: Some(CodeActionKind::QUICKFIX),
14411                    edit: Some(lsp::WorkspaceEdit {
14412                        changes: Some(
14413                            [(
14414                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14415                                vec![lsp::TextEdit {
14416                                    range: lsp::Range::new(
14417                                        lsp::Position::new(5, 4),
14418                                        lsp::Position::new(5, 27),
14419                                    ),
14420                                    new_text: "".to_string(),
14421                                }],
14422                            )]
14423                            .into_iter()
14424                            .collect(),
14425                        ),
14426                        ..Default::default()
14427                    }),
14428                    ..Default::default()
14429                },
14430            )]))
14431        },
14432    );
14433    cx.update_editor(|editor, window, cx| {
14434        editor.toggle_code_actions(
14435            &ToggleCodeActions {
14436                deployed_from: None,
14437                quick_launch: false,
14438            },
14439            window,
14440            cx,
14441        );
14442    });
14443    code_action_requests.next().await;
14444    cx.run_until_parked();
14445    cx.condition(|editor, _| editor.context_menu_visible())
14446        .await;
14447    cx.update_editor(|editor, _, _| {
14448        assert!(
14449            !editor.hover_state.visible(),
14450            "Hover popover should be hidden when code action menu is shown"
14451        );
14452        // Hide code actions
14453        editor.context_menu.take();
14454    });
14455
14456    // Case 2: Test that code completions hide hover popover
14457    cx.dispatch_action(Hover);
14458    hover_requests.next().await;
14459    cx.condition(|editor, _| editor.hover_state.visible()).await;
14460    let counter = Arc::new(AtomicUsize::new(0));
14461    let mut completion_requests =
14462        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14463            let counter = counter.clone();
14464            async move {
14465                counter.fetch_add(1, atomic::Ordering::Release);
14466                Ok(Some(lsp::CompletionResponse::Array(vec![
14467                    lsp::CompletionItem {
14468                        label: "main".into(),
14469                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14470                        detail: Some("() -> ()".to_string()),
14471                        ..Default::default()
14472                    },
14473                    lsp::CompletionItem {
14474                        label: "TestStruct".into(),
14475                        kind: Some(lsp::CompletionItemKind::STRUCT),
14476                        detail: Some("struct TestStruct".to_string()),
14477                        ..Default::default()
14478                    },
14479                ])))
14480            }
14481        });
14482    cx.update_editor(|editor, window, cx| {
14483        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14484    });
14485    completion_requests.next().await;
14486    cx.condition(|editor, _| editor.context_menu_visible())
14487        .await;
14488    cx.update_editor(|editor, _, _| {
14489        assert!(
14490            !editor.hover_state.visible(),
14491            "Hover popover should be hidden when completion menu is shown"
14492        );
14493    });
14494}
14495
14496#[gpui::test]
14497async fn test_completions_resolve_happens_once(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
14516    let unresolved_item_1 = lsp::CompletionItem {
14517        label: "id".to_string(),
14518        filter_text: Some("id".to_string()),
14519        detail: None,
14520        documentation: None,
14521        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14522            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14523            new_text: ".id".to_string(),
14524        })),
14525        ..lsp::CompletionItem::default()
14526    };
14527    let resolved_item_1 = lsp::CompletionItem {
14528        additional_text_edits: Some(vec![lsp::TextEdit {
14529            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14530            new_text: "!!".to_string(),
14531        }]),
14532        ..unresolved_item_1.clone()
14533    };
14534    let unresolved_item_2 = lsp::CompletionItem {
14535        label: "other".to_string(),
14536        filter_text: Some("other".to_string()),
14537        detail: None,
14538        documentation: None,
14539        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14540            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14541            new_text: ".other".to_string(),
14542        })),
14543        ..lsp::CompletionItem::default()
14544    };
14545    let resolved_item_2 = lsp::CompletionItem {
14546        additional_text_edits: Some(vec![lsp::TextEdit {
14547            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14548            new_text: "??".to_string(),
14549        }]),
14550        ..unresolved_item_2.clone()
14551    };
14552
14553    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14554    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14555    cx.lsp
14556        .server
14557        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14558            let unresolved_item_1 = unresolved_item_1.clone();
14559            let resolved_item_1 = resolved_item_1.clone();
14560            let unresolved_item_2 = unresolved_item_2.clone();
14561            let resolved_item_2 = resolved_item_2.clone();
14562            let resolve_requests_1 = resolve_requests_1.clone();
14563            let resolve_requests_2 = resolve_requests_2.clone();
14564            move |unresolved_request, _| {
14565                let unresolved_item_1 = unresolved_item_1.clone();
14566                let resolved_item_1 = resolved_item_1.clone();
14567                let unresolved_item_2 = unresolved_item_2.clone();
14568                let resolved_item_2 = resolved_item_2.clone();
14569                let resolve_requests_1 = resolve_requests_1.clone();
14570                let resolve_requests_2 = resolve_requests_2.clone();
14571                async move {
14572                    if unresolved_request == unresolved_item_1 {
14573                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14574                        Ok(resolved_item_1.clone())
14575                    } else if unresolved_request == unresolved_item_2 {
14576                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14577                        Ok(resolved_item_2.clone())
14578                    } else {
14579                        panic!("Unexpected completion item {unresolved_request:?}")
14580                    }
14581                }
14582            }
14583        })
14584        .detach();
14585
14586    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14587        let unresolved_item_1 = unresolved_item_1.clone();
14588        let unresolved_item_2 = unresolved_item_2.clone();
14589        async move {
14590            Ok(Some(lsp::CompletionResponse::Array(vec![
14591                unresolved_item_1,
14592                unresolved_item_2,
14593            ])))
14594        }
14595    })
14596    .next()
14597    .await;
14598
14599    cx.condition(|editor, _| editor.context_menu_visible())
14600        .await;
14601    cx.update_editor(|editor, _, _| {
14602        let context_menu = editor.context_menu.borrow_mut();
14603        let context_menu = context_menu
14604            .as_ref()
14605            .expect("Should have the context menu deployed");
14606        match context_menu {
14607            CodeContextMenu::Completions(completions_menu) => {
14608                let completions = completions_menu.completions.borrow_mut();
14609                assert_eq!(
14610                    completions
14611                        .iter()
14612                        .map(|completion| &completion.label.text)
14613                        .collect::<Vec<_>>(),
14614                    vec!["id", "other"]
14615                )
14616            }
14617            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14618        }
14619    });
14620    cx.run_until_parked();
14621
14622    cx.update_editor(|editor, window, cx| {
14623        editor.context_menu_next(&ContextMenuNext, window, cx);
14624    });
14625    cx.run_until_parked();
14626    cx.update_editor(|editor, window, cx| {
14627        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14628    });
14629    cx.run_until_parked();
14630    cx.update_editor(|editor, window, cx| {
14631        editor.context_menu_next(&ContextMenuNext, window, cx);
14632    });
14633    cx.run_until_parked();
14634    cx.update_editor(|editor, window, cx| {
14635        editor
14636            .compose_completion(&ComposeCompletion::default(), window, cx)
14637            .expect("No task returned")
14638    })
14639    .await
14640    .expect("Completion failed");
14641    cx.run_until_parked();
14642
14643    cx.update_editor(|editor, _, cx| {
14644        assert_eq!(
14645            resolve_requests_1.load(atomic::Ordering::Acquire),
14646            1,
14647            "Should always resolve once despite multiple selections"
14648        );
14649        assert_eq!(
14650            resolve_requests_2.load(atomic::Ordering::Acquire),
14651            1,
14652            "Should always resolve once after multiple selections and applying the completion"
14653        );
14654        assert_eq!(
14655            editor.text(cx),
14656            "fn main() { let a = ??.other; }",
14657            "Should use resolved data when applying the completion"
14658        );
14659    });
14660}
14661
14662#[gpui::test]
14663async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14664    init_test(cx, |_| {});
14665
14666    let item_0 = lsp::CompletionItem {
14667        label: "abs".into(),
14668        insert_text: Some("abs".into()),
14669        data: Some(json!({ "very": "special"})),
14670        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14671        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14672            lsp::InsertReplaceEdit {
14673                new_text: "abs".to_string(),
14674                insert: lsp::Range::default(),
14675                replace: lsp::Range::default(),
14676            },
14677        )),
14678        ..lsp::CompletionItem::default()
14679    };
14680    let items = iter::once(item_0.clone())
14681        .chain((11..51).map(|i| lsp::CompletionItem {
14682            label: format!("item_{}", i),
14683            insert_text: Some(format!("item_{}", i)),
14684            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14685            ..lsp::CompletionItem::default()
14686        }))
14687        .collect::<Vec<_>>();
14688
14689    let default_commit_characters = vec!["?".to_string()];
14690    let default_data = json!({ "default": "data"});
14691    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14692    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14693    let default_edit_range = lsp::Range {
14694        start: lsp::Position {
14695            line: 0,
14696            character: 5,
14697        },
14698        end: lsp::Position {
14699            line: 0,
14700            character: 5,
14701        },
14702    };
14703
14704    let mut cx = EditorLspTestContext::new_rust(
14705        lsp::ServerCapabilities {
14706            completion_provider: Some(lsp::CompletionOptions {
14707                trigger_characters: Some(vec![".".to_string()]),
14708                resolve_provider: Some(true),
14709                ..Default::default()
14710            }),
14711            ..Default::default()
14712        },
14713        cx,
14714    )
14715    .await;
14716
14717    cx.set_state("fn main() { let a = 2ˇ; }");
14718    cx.simulate_keystroke(".");
14719
14720    let completion_data = default_data.clone();
14721    let completion_characters = default_commit_characters.clone();
14722    let completion_items = items.clone();
14723    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14724        let default_data = completion_data.clone();
14725        let default_commit_characters = completion_characters.clone();
14726        let items = completion_items.clone();
14727        async move {
14728            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14729                items,
14730                item_defaults: Some(lsp::CompletionListItemDefaults {
14731                    data: Some(default_data.clone()),
14732                    commit_characters: Some(default_commit_characters.clone()),
14733                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14734                        default_edit_range,
14735                    )),
14736                    insert_text_format: Some(default_insert_text_format),
14737                    insert_text_mode: Some(default_insert_text_mode),
14738                }),
14739                ..lsp::CompletionList::default()
14740            })))
14741        }
14742    })
14743    .next()
14744    .await;
14745
14746    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14747    cx.lsp
14748        .server
14749        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14750            let closure_resolved_items = resolved_items.clone();
14751            move |item_to_resolve, _| {
14752                let closure_resolved_items = closure_resolved_items.clone();
14753                async move {
14754                    closure_resolved_items.lock().push(item_to_resolve.clone());
14755                    Ok(item_to_resolve)
14756                }
14757            }
14758        })
14759        .detach();
14760
14761    cx.condition(|editor, _| editor.context_menu_visible())
14762        .await;
14763    cx.run_until_parked();
14764    cx.update_editor(|editor, _, _| {
14765        let menu = editor.context_menu.borrow_mut();
14766        match menu.as_ref().expect("should have the completions menu") {
14767            CodeContextMenu::Completions(completions_menu) => {
14768                assert_eq!(
14769                    completions_menu
14770                        .entries
14771                        .borrow()
14772                        .iter()
14773                        .map(|mat| mat.string.clone())
14774                        .collect::<Vec<String>>(),
14775                    items
14776                        .iter()
14777                        .map(|completion| completion.label.clone())
14778                        .collect::<Vec<String>>()
14779                );
14780            }
14781            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14782        }
14783    });
14784    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14785    // with 4 from the end.
14786    assert_eq!(
14787        *resolved_items.lock(),
14788        [&items[0..16], &items[items.len() - 4..items.len()]]
14789            .concat()
14790            .iter()
14791            .cloned()
14792            .map(|mut item| {
14793                if item.data.is_none() {
14794                    item.data = Some(default_data.clone());
14795                }
14796                item
14797            })
14798            .collect::<Vec<lsp::CompletionItem>>(),
14799        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14800    );
14801    resolved_items.lock().clear();
14802
14803    cx.update_editor(|editor, window, cx| {
14804        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14805    });
14806    cx.run_until_parked();
14807    // Completions that have already been resolved are skipped.
14808    assert_eq!(
14809        *resolved_items.lock(),
14810        items[items.len() - 16..items.len() - 4]
14811            .iter()
14812            .cloned()
14813            .map(|mut item| {
14814                if item.data.is_none() {
14815                    item.data = Some(default_data.clone());
14816                }
14817                item
14818            })
14819            .collect::<Vec<lsp::CompletionItem>>()
14820    );
14821    resolved_items.lock().clear();
14822}
14823
14824#[gpui::test]
14825async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14826    init_test(cx, |_| {});
14827
14828    let mut cx = EditorLspTestContext::new(
14829        Language::new(
14830            LanguageConfig {
14831                matcher: LanguageMatcher {
14832                    path_suffixes: vec!["jsx".into()],
14833                    ..Default::default()
14834                },
14835                overrides: [(
14836                    "element".into(),
14837                    LanguageConfigOverride {
14838                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14839                        ..Default::default()
14840                    },
14841                )]
14842                .into_iter()
14843                .collect(),
14844                ..Default::default()
14845            },
14846            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14847        )
14848        .with_override_query("(jsx_self_closing_element) @element")
14849        .unwrap(),
14850        lsp::ServerCapabilities {
14851            completion_provider: Some(lsp::CompletionOptions {
14852                trigger_characters: Some(vec![":".to_string()]),
14853                ..Default::default()
14854            }),
14855            ..Default::default()
14856        },
14857        cx,
14858    )
14859    .await;
14860
14861    cx.lsp
14862        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14863            Ok(Some(lsp::CompletionResponse::Array(vec![
14864                lsp::CompletionItem {
14865                    label: "bg-blue".into(),
14866                    ..Default::default()
14867                },
14868                lsp::CompletionItem {
14869                    label: "bg-red".into(),
14870                    ..Default::default()
14871                },
14872                lsp::CompletionItem {
14873                    label: "bg-yellow".into(),
14874                    ..Default::default()
14875                },
14876            ])))
14877        });
14878
14879    cx.set_state(r#"<p class="bgˇ" />"#);
14880
14881    // Trigger completion when typing a dash, because the dash is an extra
14882    // word character in the 'element' scope, which contains the cursor.
14883    cx.simulate_keystroke("-");
14884    cx.executor().run_until_parked();
14885    cx.update_editor(|editor, _, _| {
14886        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14887        {
14888            assert_eq!(
14889                completion_menu_entries(&menu),
14890                &["bg-red", "bg-blue", "bg-yellow"]
14891            );
14892        } else {
14893            panic!("expected completion menu to be open");
14894        }
14895    });
14896
14897    cx.simulate_keystroke("l");
14898    cx.executor().run_until_parked();
14899    cx.update_editor(|editor, _, _| {
14900        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14901        {
14902            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14903        } else {
14904            panic!("expected completion menu to be open");
14905        }
14906    });
14907
14908    // When filtering completions, consider the character after the '-' to
14909    // be the start of a subword.
14910    cx.set_state(r#"<p class="yelˇ" />"#);
14911    cx.simulate_keystroke("l");
14912    cx.executor().run_until_parked();
14913    cx.update_editor(|editor, _, _| {
14914        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14915        {
14916            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14917        } else {
14918            panic!("expected completion menu to be open");
14919        }
14920    });
14921}
14922
14923fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14924    let entries = menu.entries.borrow();
14925    entries.iter().map(|mat| mat.string.clone()).collect()
14926}
14927
14928#[gpui::test]
14929async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14930    init_test(cx, |settings| {
14931        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14932            FormatterList(vec![Formatter::Prettier].into()),
14933        ))
14934    });
14935
14936    let fs = FakeFs::new(cx.executor());
14937    fs.insert_file(path!("/file.ts"), Default::default()).await;
14938
14939    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14940    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14941
14942    language_registry.add(Arc::new(Language::new(
14943        LanguageConfig {
14944            name: "TypeScript".into(),
14945            matcher: LanguageMatcher {
14946                path_suffixes: vec!["ts".to_string()],
14947                ..Default::default()
14948            },
14949            ..Default::default()
14950        },
14951        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14952    )));
14953    update_test_language_settings(cx, |settings| {
14954        settings.defaults.prettier = Some(PrettierSettings {
14955            allowed: true,
14956            ..PrettierSettings::default()
14957        });
14958    });
14959
14960    let test_plugin = "test_plugin";
14961    let _ = language_registry.register_fake_lsp(
14962        "TypeScript",
14963        FakeLspAdapter {
14964            prettier_plugins: vec![test_plugin],
14965            ..Default::default()
14966        },
14967    );
14968
14969    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14970    let buffer = project
14971        .update(cx, |project, cx| {
14972            project.open_local_buffer(path!("/file.ts"), cx)
14973        })
14974        .await
14975        .unwrap();
14976
14977    let buffer_text = "one\ntwo\nthree\n";
14978    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14979    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14980    editor.update_in(cx, |editor, window, cx| {
14981        editor.set_text(buffer_text, window, cx)
14982    });
14983
14984    editor
14985        .update_in(cx, |editor, window, cx| {
14986            editor.perform_format(
14987                project.clone(),
14988                FormatTrigger::Manual,
14989                FormatTarget::Buffers,
14990                window,
14991                cx,
14992            )
14993        })
14994        .unwrap()
14995        .await;
14996    assert_eq!(
14997        editor.update(cx, |editor, cx| editor.text(cx)),
14998        buffer_text.to_string() + prettier_format_suffix,
14999        "Test prettier formatting was not applied to the original buffer text",
15000    );
15001
15002    update_test_language_settings(cx, |settings| {
15003        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15004    });
15005    let format = editor.update_in(cx, |editor, window, cx| {
15006        editor.perform_format(
15007            project.clone(),
15008            FormatTrigger::Manual,
15009            FormatTarget::Buffers,
15010            window,
15011            cx,
15012        )
15013    });
15014    format.await.unwrap();
15015    assert_eq!(
15016        editor.update(cx, |editor, cx| editor.text(cx)),
15017        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15018        "Autoformatting (via test prettier) was not applied to the original buffer text",
15019    );
15020}
15021
15022#[gpui::test]
15023async fn test_addition_reverts(cx: &mut TestAppContext) {
15024    init_test(cx, |_| {});
15025    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15026    let base_text = indoc! {r#"
15027        struct Row;
15028        struct Row1;
15029        struct Row2;
15030
15031        struct Row4;
15032        struct Row5;
15033        struct Row6;
15034
15035        struct Row8;
15036        struct Row9;
15037        struct Row10;"#};
15038
15039    // When addition hunks are not adjacent to carets, no hunk revert is performed
15040    assert_hunk_revert(
15041        indoc! {r#"struct Row;
15042                   struct Row1;
15043                   struct Row1.1;
15044                   struct Row1.2;
15045                   struct Row2;ˇ
15046
15047                   struct Row4;
15048                   struct Row5;
15049                   struct Row6;
15050
15051                   struct Row8;
15052                   ˇstruct Row9;
15053                   struct Row9.1;
15054                   struct Row9.2;
15055                   struct Row9.3;
15056                   struct Row10;"#},
15057        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15058        indoc! {r#"struct Row;
15059                   struct Row1;
15060                   struct Row1.1;
15061                   struct Row1.2;
15062                   struct Row2;ˇ
15063
15064                   struct Row4;
15065                   struct Row5;
15066                   struct Row6;
15067
15068                   struct Row8;
15069                   ˇstruct Row9;
15070                   struct Row9.1;
15071                   struct Row9.2;
15072                   struct Row9.3;
15073                   struct Row10;"#},
15074        base_text,
15075        &mut cx,
15076    );
15077    // Same for selections
15078    assert_hunk_revert(
15079        indoc! {r#"struct Row;
15080                   struct Row1;
15081                   struct Row2;
15082                   struct Row2.1;
15083                   struct Row2.2;
15084                   «ˇ
15085                   struct Row4;
15086                   struct» Row5;
15087                   «struct Row6;
15088                   ˇ»
15089                   struct Row9.1;
15090                   struct Row9.2;
15091                   struct Row9.3;
15092                   struct Row8;
15093                   struct Row9;
15094                   struct Row10;"#},
15095        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15096        indoc! {r#"struct Row;
15097                   struct Row1;
15098                   struct Row2;
15099                   struct Row2.1;
15100                   struct Row2.2;
15101                   «ˇ
15102                   struct Row4;
15103                   struct» Row5;
15104                   «struct Row6;
15105                   ˇ»
15106                   struct Row9.1;
15107                   struct Row9.2;
15108                   struct Row9.3;
15109                   struct Row8;
15110                   struct Row9;
15111                   struct Row10;"#},
15112        base_text,
15113        &mut cx,
15114    );
15115
15116    // When carets and selections intersect the addition hunks, those are reverted.
15117    // Adjacent carets got merged.
15118    assert_hunk_revert(
15119        indoc! {r#"struct Row;
15120                   ˇ// something on the top
15121                   struct Row1;
15122                   struct Row2;
15123                   struct Roˇw3.1;
15124                   struct Row2.2;
15125                   struct Row2.3;ˇ
15126
15127                   struct Row4;
15128                   struct ˇRow5.1;
15129                   struct Row5.2;
15130                   struct «Rowˇ»5.3;
15131                   struct Row5;
15132                   struct Row6;
15133                   ˇ
15134                   struct Row9.1;
15135                   struct «Rowˇ»9.2;
15136                   struct «ˇRow»9.3;
15137                   struct Row8;
15138                   struct Row9;
15139                   «ˇ// something on bottom»
15140                   struct Row10;"#},
15141        vec![
15142            DiffHunkStatusKind::Added,
15143            DiffHunkStatusKind::Added,
15144            DiffHunkStatusKind::Added,
15145            DiffHunkStatusKind::Added,
15146            DiffHunkStatusKind::Added,
15147        ],
15148        indoc! {r#"struct Row;
15149                   ˇstruct Row1;
15150                   struct Row2;
15151                   ˇ
15152                   struct Row4;
15153                   ˇstruct Row5;
15154                   struct Row6;
15155                   ˇ
15156                   ˇstruct Row8;
15157                   struct Row9;
15158                   ˇstruct Row10;"#},
15159        base_text,
15160        &mut cx,
15161    );
15162}
15163
15164#[gpui::test]
15165async fn test_modification_reverts(cx: &mut TestAppContext) {
15166    init_test(cx, |_| {});
15167    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15168    let base_text = indoc! {r#"
15169        struct Row;
15170        struct Row1;
15171        struct Row2;
15172
15173        struct Row4;
15174        struct Row5;
15175        struct Row6;
15176
15177        struct Row8;
15178        struct Row9;
15179        struct Row10;"#};
15180
15181    // Modification hunks behave the same as the addition ones.
15182    assert_hunk_revert(
15183        indoc! {r#"struct Row;
15184                   struct Row1;
15185                   struct Row33;
15186                   ˇ
15187                   struct Row4;
15188                   struct Row5;
15189                   struct Row6;
15190                   ˇ
15191                   struct Row99;
15192                   struct Row9;
15193                   struct Row10;"#},
15194        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15195        indoc! {r#"struct Row;
15196                   struct Row1;
15197                   struct Row33;
15198                   ˇ
15199                   struct Row4;
15200                   struct Row5;
15201                   struct Row6;
15202                   ˇ
15203                   struct Row99;
15204                   struct Row9;
15205                   struct Row10;"#},
15206        base_text,
15207        &mut cx,
15208    );
15209    assert_hunk_revert(
15210        indoc! {r#"struct Row;
15211                   struct Row1;
15212                   struct Row33;
15213                   «ˇ
15214                   struct Row4;
15215                   struct» Row5;
15216                   «struct Row6;
15217                   ˇ»
15218                   struct Row99;
15219                   struct Row9;
15220                   struct Row10;"#},
15221        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15222        indoc! {r#"struct Row;
15223                   struct Row1;
15224                   struct Row33;
15225                   «ˇ
15226                   struct Row4;
15227                   struct» Row5;
15228                   «struct Row6;
15229                   ˇ»
15230                   struct Row99;
15231                   struct Row9;
15232                   struct Row10;"#},
15233        base_text,
15234        &mut cx,
15235    );
15236
15237    assert_hunk_revert(
15238        indoc! {r#"ˇstruct Row1.1;
15239                   struct Row1;
15240                   «ˇstr»uct Row22;
15241
15242                   struct ˇRow44;
15243                   struct Row5;
15244                   struct «Rˇ»ow66;ˇ
15245
15246                   «struˇ»ct Row88;
15247                   struct Row9;
15248                   struct Row1011;ˇ"#},
15249        vec![
15250            DiffHunkStatusKind::Modified,
15251            DiffHunkStatusKind::Modified,
15252            DiffHunkStatusKind::Modified,
15253            DiffHunkStatusKind::Modified,
15254            DiffHunkStatusKind::Modified,
15255            DiffHunkStatusKind::Modified,
15256        ],
15257        indoc! {r#"struct Row;
15258                   ˇstruct Row1;
15259                   struct Row2;
15260                   ˇ
15261                   struct Row4;
15262                   ˇstruct Row5;
15263                   struct Row6;
15264                   ˇ
15265                   struct Row8;
15266                   ˇstruct Row9;
15267                   struct Row10;ˇ"#},
15268        base_text,
15269        &mut cx,
15270    );
15271}
15272
15273#[gpui::test]
15274async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15275    init_test(cx, |_| {});
15276    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15277    let base_text = indoc! {r#"
15278        one
15279
15280        two
15281        three
15282        "#};
15283
15284    cx.set_head_text(base_text);
15285    cx.set_state("\nˇ\n");
15286    cx.executor().run_until_parked();
15287    cx.update_editor(|editor, _window, cx| {
15288        editor.expand_selected_diff_hunks(cx);
15289    });
15290    cx.executor().run_until_parked();
15291    cx.update_editor(|editor, window, cx| {
15292        editor.backspace(&Default::default(), window, cx);
15293    });
15294    cx.run_until_parked();
15295    cx.assert_state_with_diff(
15296        indoc! {r#"
15297
15298        - two
15299        - threeˇ
15300        +
15301        "#}
15302        .to_string(),
15303    );
15304}
15305
15306#[gpui::test]
15307async fn test_deletion_reverts(cx: &mut TestAppContext) {
15308    init_test(cx, |_| {});
15309    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15310    let base_text = indoc! {r#"struct Row;
15311struct Row1;
15312struct Row2;
15313
15314struct Row4;
15315struct Row5;
15316struct Row6;
15317
15318struct Row8;
15319struct Row9;
15320struct Row10;"#};
15321
15322    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15323    assert_hunk_revert(
15324        indoc! {r#"struct Row;
15325                   struct Row2;
15326
15327                   ˇstruct Row4;
15328                   struct Row5;
15329                   struct Row6;
15330                   ˇ
15331                   struct Row8;
15332                   struct Row10;"#},
15333        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15334        indoc! {r#"struct Row;
15335                   struct Row2;
15336
15337                   ˇstruct Row4;
15338                   struct Row5;
15339                   struct Row6;
15340                   ˇ
15341                   struct Row8;
15342                   struct Row10;"#},
15343        base_text,
15344        &mut cx,
15345    );
15346    assert_hunk_revert(
15347        indoc! {r#"struct Row;
15348                   struct Row2;
15349
15350                   «ˇstruct Row4;
15351                   struct» Row5;
15352                   «struct Row6;
15353                   ˇ»
15354                   struct Row8;
15355                   struct Row10;"#},
15356        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15357        indoc! {r#"struct Row;
15358                   struct Row2;
15359
15360                   «ˇstruct Row4;
15361                   struct» Row5;
15362                   «struct Row6;
15363                   ˇ»
15364                   struct Row8;
15365                   struct Row10;"#},
15366        base_text,
15367        &mut cx,
15368    );
15369
15370    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15371    assert_hunk_revert(
15372        indoc! {r#"struct Row;
15373                   ˇstruct Row2;
15374
15375                   struct Row4;
15376                   struct Row5;
15377                   struct Row6;
15378
15379                   struct Row8;ˇ
15380                   struct Row10;"#},
15381        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15382        indoc! {r#"struct Row;
15383                   struct Row1;
15384                   ˇstruct Row2;
15385
15386                   struct Row4;
15387                   struct Row5;
15388                   struct Row6;
15389
15390                   struct Row8;ˇ
15391                   struct Row9;
15392                   struct Row10;"#},
15393        base_text,
15394        &mut cx,
15395    );
15396    assert_hunk_revert(
15397        indoc! {r#"struct Row;
15398                   struct Row2«ˇ;
15399                   struct Row4;
15400                   struct» Row5;
15401                   «struct Row6;
15402
15403                   struct Row8;ˇ»
15404                   struct Row10;"#},
15405        vec![
15406            DiffHunkStatusKind::Deleted,
15407            DiffHunkStatusKind::Deleted,
15408            DiffHunkStatusKind::Deleted,
15409        ],
15410        indoc! {r#"struct Row;
15411                   struct Row1;
15412                   struct Row2«ˇ;
15413
15414                   struct Row4;
15415                   struct» Row5;
15416                   «struct Row6;
15417
15418                   struct Row8;ˇ»
15419                   struct Row9;
15420                   struct Row10;"#},
15421        base_text,
15422        &mut cx,
15423    );
15424}
15425
15426#[gpui::test]
15427async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15428    init_test(cx, |_| {});
15429
15430    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15431    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15432    let base_text_3 =
15433        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15434
15435    let text_1 = edit_first_char_of_every_line(base_text_1);
15436    let text_2 = edit_first_char_of_every_line(base_text_2);
15437    let text_3 = edit_first_char_of_every_line(base_text_3);
15438
15439    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15440    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15441    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15442
15443    let multibuffer = cx.new(|cx| {
15444        let mut multibuffer = MultiBuffer::new(ReadWrite);
15445        multibuffer.push_excerpts(
15446            buffer_1.clone(),
15447            [
15448                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15449                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15450                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15451            ],
15452            cx,
15453        );
15454        multibuffer.push_excerpts(
15455            buffer_2.clone(),
15456            [
15457                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15458                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15459                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15460            ],
15461            cx,
15462        );
15463        multibuffer.push_excerpts(
15464            buffer_3.clone(),
15465            [
15466                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15467                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15468                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15469            ],
15470            cx,
15471        );
15472        multibuffer
15473    });
15474
15475    let fs = FakeFs::new(cx.executor());
15476    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15477    let (editor, cx) = cx
15478        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15479    editor.update_in(cx, |editor, _window, cx| {
15480        for (buffer, diff_base) in [
15481            (buffer_1.clone(), base_text_1),
15482            (buffer_2.clone(), base_text_2),
15483            (buffer_3.clone(), base_text_3),
15484        ] {
15485            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15486            editor
15487                .buffer
15488                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15489        }
15490    });
15491    cx.executor().run_until_parked();
15492
15493    editor.update_in(cx, |editor, window, cx| {
15494        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}");
15495        editor.select_all(&SelectAll, window, cx);
15496        editor.git_restore(&Default::default(), window, cx);
15497    });
15498    cx.executor().run_until_parked();
15499
15500    // When all ranges are selected, all buffer hunks are reverted.
15501    editor.update(cx, |editor, cx| {
15502        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");
15503    });
15504    buffer_1.update(cx, |buffer, _| {
15505        assert_eq!(buffer.text(), base_text_1);
15506    });
15507    buffer_2.update(cx, |buffer, _| {
15508        assert_eq!(buffer.text(), base_text_2);
15509    });
15510    buffer_3.update(cx, |buffer, _| {
15511        assert_eq!(buffer.text(), base_text_3);
15512    });
15513
15514    editor.update_in(cx, |editor, window, cx| {
15515        editor.undo(&Default::default(), window, cx);
15516    });
15517
15518    editor.update_in(cx, |editor, window, cx| {
15519        editor.change_selections(None, window, cx, |s| {
15520            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15521        });
15522        editor.git_restore(&Default::default(), window, cx);
15523    });
15524
15525    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15526    // but not affect buffer_2 and its related excerpts.
15527    editor.update(cx, |editor, cx| {
15528        assert_eq!(
15529            editor.text(cx),
15530            "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}"
15531        );
15532    });
15533    buffer_1.update(cx, |buffer, _| {
15534        assert_eq!(buffer.text(), base_text_1);
15535    });
15536    buffer_2.update(cx, |buffer, _| {
15537        assert_eq!(
15538            buffer.text(),
15539            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15540        );
15541    });
15542    buffer_3.update(cx, |buffer, _| {
15543        assert_eq!(
15544            buffer.text(),
15545            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15546        );
15547    });
15548
15549    fn edit_first_char_of_every_line(text: &str) -> String {
15550        text.split('\n')
15551            .map(|line| format!("X{}", &line[1..]))
15552            .collect::<Vec<_>>()
15553            .join("\n")
15554    }
15555}
15556
15557#[gpui::test]
15558async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15559    init_test(cx, |_| {});
15560
15561    let cols = 4;
15562    let rows = 10;
15563    let sample_text_1 = sample_text(rows, cols, 'a');
15564    assert_eq!(
15565        sample_text_1,
15566        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15567    );
15568    let sample_text_2 = sample_text(rows, cols, 'l');
15569    assert_eq!(
15570        sample_text_2,
15571        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15572    );
15573    let sample_text_3 = sample_text(rows, cols, 'v');
15574    assert_eq!(
15575        sample_text_3,
15576        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15577    );
15578
15579    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15580    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15581    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15582
15583    let multi_buffer = cx.new(|cx| {
15584        let mut multibuffer = MultiBuffer::new(ReadWrite);
15585        multibuffer.push_excerpts(
15586            buffer_1.clone(),
15587            [
15588                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15589                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15590                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15591            ],
15592            cx,
15593        );
15594        multibuffer.push_excerpts(
15595            buffer_2.clone(),
15596            [
15597                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15598                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15599                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15600            ],
15601            cx,
15602        );
15603        multibuffer.push_excerpts(
15604            buffer_3.clone(),
15605            [
15606                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15607                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15608                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15609            ],
15610            cx,
15611        );
15612        multibuffer
15613    });
15614
15615    let fs = FakeFs::new(cx.executor());
15616    fs.insert_tree(
15617        "/a",
15618        json!({
15619            "main.rs": sample_text_1,
15620            "other.rs": sample_text_2,
15621            "lib.rs": sample_text_3,
15622        }),
15623    )
15624    .await;
15625    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15626    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15627    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15628    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15629        Editor::new(
15630            EditorMode::full(),
15631            multi_buffer,
15632            Some(project.clone()),
15633            window,
15634            cx,
15635        )
15636    });
15637    let multibuffer_item_id = workspace
15638        .update(cx, |workspace, window, cx| {
15639            assert!(
15640                workspace.active_item(cx).is_none(),
15641                "active item should be None before the first item is added"
15642            );
15643            workspace.add_item_to_active_pane(
15644                Box::new(multi_buffer_editor.clone()),
15645                None,
15646                true,
15647                window,
15648                cx,
15649            );
15650            let active_item = workspace
15651                .active_item(cx)
15652                .expect("should have an active item after adding the multi buffer");
15653            assert!(
15654                !active_item.is_singleton(cx),
15655                "A multi buffer was expected to active after adding"
15656            );
15657            active_item.item_id()
15658        })
15659        .unwrap();
15660    cx.executor().run_until_parked();
15661
15662    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15663        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15664            s.select_ranges(Some(1..2))
15665        });
15666        editor.open_excerpts(&OpenExcerpts, window, cx);
15667    });
15668    cx.executor().run_until_parked();
15669    let first_item_id = workspace
15670        .update(cx, |workspace, window, cx| {
15671            let active_item = workspace
15672                .active_item(cx)
15673                .expect("should have an active item after navigating into the 1st buffer");
15674            let first_item_id = active_item.item_id();
15675            assert_ne!(
15676                first_item_id, multibuffer_item_id,
15677                "Should navigate into the 1st buffer and activate it"
15678            );
15679            assert!(
15680                active_item.is_singleton(cx),
15681                "New active item should be a singleton buffer"
15682            );
15683            assert_eq!(
15684                active_item
15685                    .act_as::<Editor>(cx)
15686                    .expect("should have navigated into an editor for the 1st buffer")
15687                    .read(cx)
15688                    .text(cx),
15689                sample_text_1
15690            );
15691
15692            workspace
15693                .go_back(workspace.active_pane().downgrade(), window, cx)
15694                .detach_and_log_err(cx);
15695
15696            first_item_id
15697        })
15698        .unwrap();
15699    cx.executor().run_until_parked();
15700    workspace
15701        .update(cx, |workspace, _, cx| {
15702            let active_item = workspace
15703                .active_item(cx)
15704                .expect("should have an active item after navigating back");
15705            assert_eq!(
15706                active_item.item_id(),
15707                multibuffer_item_id,
15708                "Should navigate back to the multi buffer"
15709            );
15710            assert!(!active_item.is_singleton(cx));
15711        })
15712        .unwrap();
15713
15714    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15715        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15716            s.select_ranges(Some(39..40))
15717        });
15718        editor.open_excerpts(&OpenExcerpts, window, cx);
15719    });
15720    cx.executor().run_until_parked();
15721    let second_item_id = workspace
15722        .update(cx, |workspace, window, cx| {
15723            let active_item = workspace
15724                .active_item(cx)
15725                .expect("should have an active item after navigating into the 2nd buffer");
15726            let second_item_id = active_item.item_id();
15727            assert_ne!(
15728                second_item_id, multibuffer_item_id,
15729                "Should navigate away from the multibuffer"
15730            );
15731            assert_ne!(
15732                second_item_id, first_item_id,
15733                "Should navigate into the 2nd buffer and activate it"
15734            );
15735            assert!(
15736                active_item.is_singleton(cx),
15737                "New active item should be a singleton buffer"
15738            );
15739            assert_eq!(
15740                active_item
15741                    .act_as::<Editor>(cx)
15742                    .expect("should have navigated into an editor")
15743                    .read(cx)
15744                    .text(cx),
15745                sample_text_2
15746            );
15747
15748            workspace
15749                .go_back(workspace.active_pane().downgrade(), window, cx)
15750                .detach_and_log_err(cx);
15751
15752            second_item_id
15753        })
15754        .unwrap();
15755    cx.executor().run_until_parked();
15756    workspace
15757        .update(cx, |workspace, _, cx| {
15758            let active_item = workspace
15759                .active_item(cx)
15760                .expect("should have an active item after navigating back from the 2nd buffer");
15761            assert_eq!(
15762                active_item.item_id(),
15763                multibuffer_item_id,
15764                "Should navigate back from the 2nd buffer to the multi buffer"
15765            );
15766            assert!(!active_item.is_singleton(cx));
15767        })
15768        .unwrap();
15769
15770    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15771        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15772            s.select_ranges(Some(70..70))
15773        });
15774        editor.open_excerpts(&OpenExcerpts, window, cx);
15775    });
15776    cx.executor().run_until_parked();
15777    workspace
15778        .update(cx, |workspace, window, cx| {
15779            let active_item = workspace
15780                .active_item(cx)
15781                .expect("should have an active item after navigating into the 3rd buffer");
15782            let third_item_id = active_item.item_id();
15783            assert_ne!(
15784                third_item_id, multibuffer_item_id,
15785                "Should navigate into the 3rd buffer and activate it"
15786            );
15787            assert_ne!(third_item_id, first_item_id);
15788            assert_ne!(third_item_id, second_item_id);
15789            assert!(
15790                active_item.is_singleton(cx),
15791                "New active item should be a singleton buffer"
15792            );
15793            assert_eq!(
15794                active_item
15795                    .act_as::<Editor>(cx)
15796                    .expect("should have navigated into an editor")
15797                    .read(cx)
15798                    .text(cx),
15799                sample_text_3
15800            );
15801
15802            workspace
15803                .go_back(workspace.active_pane().downgrade(), window, cx)
15804                .detach_and_log_err(cx);
15805        })
15806        .unwrap();
15807    cx.executor().run_until_parked();
15808    workspace
15809        .update(cx, |workspace, _, cx| {
15810            let active_item = workspace
15811                .active_item(cx)
15812                .expect("should have an active item after navigating back from the 3rd buffer");
15813            assert_eq!(
15814                active_item.item_id(),
15815                multibuffer_item_id,
15816                "Should navigate back from the 3rd buffer to the multi buffer"
15817            );
15818            assert!(!active_item.is_singleton(cx));
15819        })
15820        .unwrap();
15821}
15822
15823#[gpui::test]
15824async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15825    init_test(cx, |_| {});
15826
15827    let mut cx = EditorTestContext::new(cx).await;
15828
15829    let diff_base = r#"
15830        use some::mod;
15831
15832        const A: u32 = 42;
15833
15834        fn main() {
15835            println!("hello");
15836
15837            println!("world");
15838        }
15839        "#
15840    .unindent();
15841
15842    cx.set_state(
15843        &r#"
15844        use some::modified;
15845
15846        ˇ
15847        fn main() {
15848            println!("hello there");
15849
15850            println!("around the");
15851            println!("world");
15852        }
15853        "#
15854        .unindent(),
15855    );
15856
15857    cx.set_head_text(&diff_base);
15858    executor.run_until_parked();
15859
15860    cx.update_editor(|editor, window, cx| {
15861        editor.go_to_next_hunk(&GoToHunk, window, cx);
15862        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15863    });
15864    executor.run_until_parked();
15865    cx.assert_state_with_diff(
15866        r#"
15867          use some::modified;
15868
15869
15870          fn main() {
15871        -     println!("hello");
15872        + ˇ    println!("hello there");
15873
15874              println!("around the");
15875              println!("world");
15876          }
15877        "#
15878        .unindent(),
15879    );
15880
15881    cx.update_editor(|editor, window, cx| {
15882        for _ in 0..2 {
15883            editor.go_to_next_hunk(&GoToHunk, window, cx);
15884            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15885        }
15886    });
15887    executor.run_until_parked();
15888    cx.assert_state_with_diff(
15889        r#"
15890        - use some::mod;
15891        + ˇuse some::modified;
15892
15893
15894          fn main() {
15895        -     println!("hello");
15896        +     println!("hello there");
15897
15898        +     println!("around the");
15899              println!("world");
15900          }
15901        "#
15902        .unindent(),
15903    );
15904
15905    cx.update_editor(|editor, window, cx| {
15906        editor.go_to_next_hunk(&GoToHunk, window, cx);
15907        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15908    });
15909    executor.run_until_parked();
15910    cx.assert_state_with_diff(
15911        r#"
15912        - use some::mod;
15913        + use some::modified;
15914
15915        - const A: u32 = 42;
15916          ˇ
15917          fn main() {
15918        -     println!("hello");
15919        +     println!("hello there");
15920
15921        +     println!("around the");
15922              println!("world");
15923          }
15924        "#
15925        .unindent(),
15926    );
15927
15928    cx.update_editor(|editor, window, cx| {
15929        editor.cancel(&Cancel, window, cx);
15930    });
15931
15932    cx.assert_state_with_diff(
15933        r#"
15934          use some::modified;
15935
15936          ˇ
15937          fn main() {
15938              println!("hello there");
15939
15940              println!("around the");
15941              println!("world");
15942          }
15943        "#
15944        .unindent(),
15945    );
15946}
15947
15948#[gpui::test]
15949async fn test_diff_base_change_with_expanded_diff_hunks(
15950    executor: BackgroundExecutor,
15951    cx: &mut TestAppContext,
15952) {
15953    init_test(cx, |_| {});
15954
15955    let mut cx = EditorTestContext::new(cx).await;
15956
15957    let diff_base = r#"
15958        use some::mod1;
15959        use some::mod2;
15960
15961        const A: u32 = 42;
15962        const B: u32 = 42;
15963        const C: u32 = 42;
15964
15965        fn main() {
15966            println!("hello");
15967
15968            println!("world");
15969        }
15970        "#
15971    .unindent();
15972
15973    cx.set_state(
15974        &r#"
15975        use some::mod2;
15976
15977        const A: u32 = 42;
15978        const C: u32 = 42;
15979
15980        fn main(ˇ) {
15981            //println!("hello");
15982
15983            println!("world");
15984            //
15985            //
15986        }
15987        "#
15988        .unindent(),
15989    );
15990
15991    cx.set_head_text(&diff_base);
15992    executor.run_until_parked();
15993
15994    cx.update_editor(|editor, window, cx| {
15995        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15996    });
15997    executor.run_until_parked();
15998    cx.assert_state_with_diff(
15999        r#"
16000        - use some::mod1;
16001          use some::mod2;
16002
16003          const A: u32 = 42;
16004        - const B: u32 = 42;
16005          const C: u32 = 42;
16006
16007          fn main(ˇ) {
16008        -     println!("hello");
16009        +     //println!("hello");
16010
16011              println!("world");
16012        +     //
16013        +     //
16014          }
16015        "#
16016        .unindent(),
16017    );
16018
16019    cx.set_head_text("new diff base!");
16020    executor.run_until_parked();
16021    cx.assert_state_with_diff(
16022        r#"
16023        - new diff base!
16024        + use some::mod2;
16025        +
16026        + const A: u32 = 42;
16027        + const C: u32 = 42;
16028        +
16029        + fn main(ˇ) {
16030        +     //println!("hello");
16031        +
16032        +     println!("world");
16033        +     //
16034        +     //
16035        + }
16036        "#
16037        .unindent(),
16038    );
16039}
16040
16041#[gpui::test]
16042async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16043    init_test(cx, |_| {});
16044
16045    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16046    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16047    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16048    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16049    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16050    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16051
16052    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16053    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16054    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16055
16056    let multi_buffer = cx.new(|cx| {
16057        let mut multibuffer = MultiBuffer::new(ReadWrite);
16058        multibuffer.push_excerpts(
16059            buffer_1.clone(),
16060            [
16061                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16062                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16063                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16064            ],
16065            cx,
16066        );
16067        multibuffer.push_excerpts(
16068            buffer_2.clone(),
16069            [
16070                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16071                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16072                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16073            ],
16074            cx,
16075        );
16076        multibuffer.push_excerpts(
16077            buffer_3.clone(),
16078            [
16079                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16080                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16081                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16082            ],
16083            cx,
16084        );
16085        multibuffer
16086    });
16087
16088    let editor =
16089        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16090    editor
16091        .update(cx, |editor, _window, cx| {
16092            for (buffer, diff_base) in [
16093                (buffer_1.clone(), file_1_old),
16094                (buffer_2.clone(), file_2_old),
16095                (buffer_3.clone(), file_3_old),
16096            ] {
16097                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16098                editor
16099                    .buffer
16100                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16101            }
16102        })
16103        .unwrap();
16104
16105    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16106    cx.run_until_parked();
16107
16108    cx.assert_editor_state(
16109        &"
16110            ˇaaa
16111            ccc
16112            ddd
16113
16114            ggg
16115            hhh
16116
16117
16118            lll
16119            mmm
16120            NNN
16121
16122            qqq
16123            rrr
16124
16125            uuu
16126            111
16127            222
16128            333
16129
16130            666
16131            777
16132
16133            000
16134            !!!"
16135        .unindent(),
16136    );
16137
16138    cx.update_editor(|editor, window, cx| {
16139        editor.select_all(&SelectAll, window, cx);
16140        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16141    });
16142    cx.executor().run_until_parked();
16143
16144    cx.assert_state_with_diff(
16145        "
16146            «aaa
16147          - bbb
16148            ccc
16149            ddd
16150
16151            ggg
16152            hhh
16153
16154
16155            lll
16156            mmm
16157          - nnn
16158          + NNN
16159
16160            qqq
16161            rrr
16162
16163            uuu
16164            111
16165            222
16166            333
16167
16168          + 666
16169            777
16170
16171            000
16172            !!!ˇ»"
16173            .unindent(),
16174    );
16175}
16176
16177#[gpui::test]
16178async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16179    init_test(cx, |_| {});
16180
16181    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16182    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16183
16184    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16185    let multi_buffer = cx.new(|cx| {
16186        let mut multibuffer = MultiBuffer::new(ReadWrite);
16187        multibuffer.push_excerpts(
16188            buffer.clone(),
16189            [
16190                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16191                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16192                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16193            ],
16194            cx,
16195        );
16196        multibuffer
16197    });
16198
16199    let editor =
16200        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16201    editor
16202        .update(cx, |editor, _window, cx| {
16203            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16204            editor
16205                .buffer
16206                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16207        })
16208        .unwrap();
16209
16210    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16211    cx.run_until_parked();
16212
16213    cx.update_editor(|editor, window, cx| {
16214        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16215    });
16216    cx.executor().run_until_parked();
16217
16218    // When the start of a hunk coincides with the start of its excerpt,
16219    // the hunk is expanded. When the start of a a hunk is earlier than
16220    // the start of its excerpt, the hunk is not expanded.
16221    cx.assert_state_with_diff(
16222        "
16223            ˇaaa
16224          - bbb
16225          + BBB
16226
16227          - ddd
16228          - eee
16229          + DDD
16230          + EEE
16231            fff
16232
16233            iii
16234        "
16235        .unindent(),
16236    );
16237}
16238
16239#[gpui::test]
16240async fn test_edits_around_expanded_insertion_hunks(
16241    executor: BackgroundExecutor,
16242    cx: &mut TestAppContext,
16243) {
16244    init_test(cx, |_| {});
16245
16246    let mut cx = EditorTestContext::new(cx).await;
16247
16248    let diff_base = r#"
16249        use some::mod1;
16250        use some::mod2;
16251
16252        const A: u32 = 42;
16253
16254        fn main() {
16255            println!("hello");
16256
16257            println!("world");
16258        }
16259        "#
16260    .unindent();
16261    executor.run_until_parked();
16262    cx.set_state(
16263        &r#"
16264        use some::mod1;
16265        use some::mod2;
16266
16267        const A: u32 = 42;
16268        const B: u32 = 42;
16269        const C: u32 = 42;
16270        ˇ
16271
16272        fn main() {
16273            println!("hello");
16274
16275            println!("world");
16276        }
16277        "#
16278        .unindent(),
16279    );
16280
16281    cx.set_head_text(&diff_base);
16282    executor.run_until_parked();
16283
16284    cx.update_editor(|editor, window, cx| {
16285        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16286    });
16287    executor.run_until_parked();
16288
16289    cx.assert_state_with_diff(
16290        r#"
16291        use some::mod1;
16292        use some::mod2;
16293
16294        const A: u32 = 42;
16295      + const B: u32 = 42;
16296      + const C: u32 = 42;
16297      + ˇ
16298
16299        fn main() {
16300            println!("hello");
16301
16302            println!("world");
16303        }
16304      "#
16305        .unindent(),
16306    );
16307
16308    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16309    executor.run_until_parked();
16310
16311    cx.assert_state_with_diff(
16312        r#"
16313        use some::mod1;
16314        use some::mod2;
16315
16316        const A: u32 = 42;
16317      + const B: u32 = 42;
16318      + const C: u32 = 42;
16319      + const D: u32 = 42;
16320      + ˇ
16321
16322        fn main() {
16323            println!("hello");
16324
16325            println!("world");
16326        }
16327      "#
16328        .unindent(),
16329    );
16330
16331    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16332    executor.run_until_parked();
16333
16334    cx.assert_state_with_diff(
16335        r#"
16336        use some::mod1;
16337        use some::mod2;
16338
16339        const A: u32 = 42;
16340      + const B: u32 = 42;
16341      + const C: u32 = 42;
16342      + const D: u32 = 42;
16343      + const E: u32 = 42;
16344      + ˇ
16345
16346        fn main() {
16347            println!("hello");
16348
16349            println!("world");
16350        }
16351      "#
16352        .unindent(),
16353    );
16354
16355    cx.update_editor(|editor, window, cx| {
16356        editor.delete_line(&DeleteLine, window, cx);
16357    });
16358    executor.run_until_parked();
16359
16360    cx.assert_state_with_diff(
16361        r#"
16362        use some::mod1;
16363        use some::mod2;
16364
16365        const A: u32 = 42;
16366      + const B: u32 = 42;
16367      + const C: u32 = 42;
16368      + const D: u32 = 42;
16369      + const E: u32 = 42;
16370        ˇ
16371        fn main() {
16372            println!("hello");
16373
16374            println!("world");
16375        }
16376      "#
16377        .unindent(),
16378    );
16379
16380    cx.update_editor(|editor, window, cx| {
16381        editor.move_up(&MoveUp, window, cx);
16382        editor.delete_line(&DeleteLine, window, cx);
16383        editor.move_up(&MoveUp, window, cx);
16384        editor.delete_line(&DeleteLine, window, cx);
16385        editor.move_up(&MoveUp, window, cx);
16386        editor.delete_line(&DeleteLine, window, cx);
16387    });
16388    executor.run_until_parked();
16389    cx.assert_state_with_diff(
16390        r#"
16391        use some::mod1;
16392        use some::mod2;
16393
16394        const A: u32 = 42;
16395      + const B: u32 = 42;
16396        ˇ
16397        fn main() {
16398            println!("hello");
16399
16400            println!("world");
16401        }
16402      "#
16403        .unindent(),
16404    );
16405
16406    cx.update_editor(|editor, window, cx| {
16407        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16408        editor.delete_line(&DeleteLine, window, cx);
16409    });
16410    executor.run_until_parked();
16411    cx.assert_state_with_diff(
16412        r#"
16413        ˇ
16414        fn main() {
16415            println!("hello");
16416
16417            println!("world");
16418        }
16419      "#
16420        .unindent(),
16421    );
16422}
16423
16424#[gpui::test]
16425async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16426    init_test(cx, |_| {});
16427
16428    let mut cx = EditorTestContext::new(cx).await;
16429    cx.set_head_text(indoc! { "
16430        one
16431        two
16432        three
16433        four
16434        five
16435        "
16436    });
16437    cx.set_state(indoc! { "
16438        one
16439        ˇthree
16440        five
16441    "});
16442    cx.run_until_parked();
16443    cx.update_editor(|editor, window, cx| {
16444        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16445    });
16446    cx.assert_state_with_diff(
16447        indoc! { "
16448        one
16449      - two
16450        ˇthree
16451      - four
16452        five
16453    "}
16454        .to_string(),
16455    );
16456    cx.update_editor(|editor, window, cx| {
16457        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16458    });
16459
16460    cx.assert_state_with_diff(
16461        indoc! { "
16462        one
16463        ˇthree
16464        five
16465    "}
16466        .to_string(),
16467    );
16468
16469    cx.set_state(indoc! { "
16470        one
16471        ˇTWO
16472        three
16473        four
16474        five
16475    "});
16476    cx.run_until_parked();
16477    cx.update_editor(|editor, window, cx| {
16478        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16479    });
16480
16481    cx.assert_state_with_diff(
16482        indoc! { "
16483            one
16484          - two
16485          + ˇTWO
16486            three
16487            four
16488            five
16489        "}
16490        .to_string(),
16491    );
16492    cx.update_editor(|editor, window, cx| {
16493        editor.move_up(&Default::default(), window, cx);
16494        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16495    });
16496    cx.assert_state_with_diff(
16497        indoc! { "
16498            one
16499            ˇTWO
16500            three
16501            four
16502            five
16503        "}
16504        .to_string(),
16505    );
16506}
16507
16508#[gpui::test]
16509async fn test_edits_around_expanded_deletion_hunks(
16510    executor: BackgroundExecutor,
16511    cx: &mut TestAppContext,
16512) {
16513    init_test(cx, |_| {});
16514
16515    let mut cx = EditorTestContext::new(cx).await;
16516
16517    let diff_base = r#"
16518        use some::mod1;
16519        use some::mod2;
16520
16521        const A: u32 = 42;
16522        const B: u32 = 42;
16523        const C: u32 = 42;
16524
16525
16526        fn main() {
16527            println!("hello");
16528
16529            println!("world");
16530        }
16531    "#
16532    .unindent();
16533    executor.run_until_parked();
16534    cx.set_state(
16535        &r#"
16536        use some::mod1;
16537        use some::mod2;
16538
16539        ˇconst B: u32 = 42;
16540        const C: u32 = 42;
16541
16542
16543        fn main() {
16544            println!("hello");
16545
16546            println!("world");
16547        }
16548        "#
16549        .unindent(),
16550    );
16551
16552    cx.set_head_text(&diff_base);
16553    executor.run_until_parked();
16554
16555    cx.update_editor(|editor, window, cx| {
16556        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16557    });
16558    executor.run_until_parked();
16559
16560    cx.assert_state_with_diff(
16561        r#"
16562        use some::mod1;
16563        use some::mod2;
16564
16565      - const A: u32 = 42;
16566        ˇconst B: u32 = 42;
16567        const C: u32 = 42;
16568
16569
16570        fn main() {
16571            println!("hello");
16572
16573            println!("world");
16574        }
16575      "#
16576        .unindent(),
16577    );
16578
16579    cx.update_editor(|editor, window, cx| {
16580        editor.delete_line(&DeleteLine, window, cx);
16581    });
16582    executor.run_until_parked();
16583    cx.assert_state_with_diff(
16584        r#"
16585        use some::mod1;
16586        use some::mod2;
16587
16588      - const A: u32 = 42;
16589      - const B: u32 = 42;
16590        ˇconst C: u32 = 42;
16591
16592
16593        fn main() {
16594            println!("hello");
16595
16596            println!("world");
16597        }
16598      "#
16599        .unindent(),
16600    );
16601
16602    cx.update_editor(|editor, window, cx| {
16603        editor.delete_line(&DeleteLine, window, cx);
16604    });
16605    executor.run_until_parked();
16606    cx.assert_state_with_diff(
16607        r#"
16608        use some::mod1;
16609        use some::mod2;
16610
16611      - const A: u32 = 42;
16612      - const B: u32 = 42;
16613      - const C: u32 = 42;
16614        ˇ
16615
16616        fn main() {
16617            println!("hello");
16618
16619            println!("world");
16620        }
16621      "#
16622        .unindent(),
16623    );
16624
16625    cx.update_editor(|editor, window, cx| {
16626        editor.handle_input("replacement", window, cx);
16627    });
16628    executor.run_until_parked();
16629    cx.assert_state_with_diff(
16630        r#"
16631        use some::mod1;
16632        use some::mod2;
16633
16634      - const A: u32 = 42;
16635      - const B: u32 = 42;
16636      - const C: u32 = 42;
16637      -
16638      + replacementˇ
16639
16640        fn main() {
16641            println!("hello");
16642
16643            println!("world");
16644        }
16645      "#
16646        .unindent(),
16647    );
16648}
16649
16650#[gpui::test]
16651async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16652    init_test(cx, |_| {});
16653
16654    let mut cx = EditorTestContext::new(cx).await;
16655
16656    let base_text = r#"
16657        one
16658        two
16659        three
16660        four
16661        five
16662    "#
16663    .unindent();
16664    executor.run_until_parked();
16665    cx.set_state(
16666        &r#"
16667        one
16668        two
16669        fˇour
16670        five
16671        "#
16672        .unindent(),
16673    );
16674
16675    cx.set_head_text(&base_text);
16676    executor.run_until_parked();
16677
16678    cx.update_editor(|editor, window, cx| {
16679        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16680    });
16681    executor.run_until_parked();
16682
16683    cx.assert_state_with_diff(
16684        r#"
16685          one
16686          two
16687        - three
16688          fˇour
16689          five
16690        "#
16691        .unindent(),
16692    );
16693
16694    cx.update_editor(|editor, window, cx| {
16695        editor.backspace(&Backspace, window, cx);
16696        editor.backspace(&Backspace, window, cx);
16697    });
16698    executor.run_until_parked();
16699    cx.assert_state_with_diff(
16700        r#"
16701          one
16702          two
16703        - threeˇ
16704        - four
16705        + our
16706          five
16707        "#
16708        .unindent(),
16709    );
16710}
16711
16712#[gpui::test]
16713async fn test_edit_after_expanded_modification_hunk(
16714    executor: BackgroundExecutor,
16715    cx: &mut TestAppContext,
16716) {
16717    init_test(cx, |_| {});
16718
16719    let mut cx = EditorTestContext::new(cx).await;
16720
16721    let diff_base = r#"
16722        use some::mod1;
16723        use some::mod2;
16724
16725        const A: u32 = 42;
16726        const B: u32 = 42;
16727        const C: u32 = 42;
16728        const D: u32 = 42;
16729
16730
16731        fn main() {
16732            println!("hello");
16733
16734            println!("world");
16735        }"#
16736    .unindent();
16737
16738    cx.set_state(
16739        &r#"
16740        use some::mod1;
16741        use some::mod2;
16742
16743        const A: u32 = 42;
16744        const B: u32 = 42;
16745        const C: u32 = 43ˇ
16746        const D: u32 = 42;
16747
16748
16749        fn main() {
16750            println!("hello");
16751
16752            println!("world");
16753        }"#
16754        .unindent(),
16755    );
16756
16757    cx.set_head_text(&diff_base);
16758    executor.run_until_parked();
16759    cx.update_editor(|editor, window, cx| {
16760        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16761    });
16762    executor.run_until_parked();
16763
16764    cx.assert_state_with_diff(
16765        r#"
16766        use some::mod1;
16767        use some::mod2;
16768
16769        const A: u32 = 42;
16770        const B: u32 = 42;
16771      - const C: u32 = 42;
16772      + const C: u32 = 43ˇ
16773        const D: u32 = 42;
16774
16775
16776        fn main() {
16777            println!("hello");
16778
16779            println!("world");
16780        }"#
16781        .unindent(),
16782    );
16783
16784    cx.update_editor(|editor, window, cx| {
16785        editor.handle_input("\nnew_line\n", window, cx);
16786    });
16787    executor.run_until_parked();
16788
16789    cx.assert_state_with_diff(
16790        r#"
16791        use some::mod1;
16792        use some::mod2;
16793
16794        const A: u32 = 42;
16795        const B: u32 = 42;
16796      - const C: u32 = 42;
16797      + const C: u32 = 43
16798      + new_line
16799      + ˇ
16800        const D: u32 = 42;
16801
16802
16803        fn main() {
16804            println!("hello");
16805
16806            println!("world");
16807        }"#
16808        .unindent(),
16809    );
16810}
16811
16812#[gpui::test]
16813async fn test_stage_and_unstage_added_file_hunk(
16814    executor: BackgroundExecutor,
16815    cx: &mut TestAppContext,
16816) {
16817    init_test(cx, |_| {});
16818
16819    let mut cx = EditorTestContext::new(cx).await;
16820    cx.update_editor(|editor, _, cx| {
16821        editor.set_expand_all_diff_hunks(cx);
16822    });
16823
16824    let working_copy = r#"
16825            ˇfn main() {
16826                println!("hello, world!");
16827            }
16828        "#
16829    .unindent();
16830
16831    cx.set_state(&working_copy);
16832    executor.run_until_parked();
16833
16834    cx.assert_state_with_diff(
16835        r#"
16836            + ˇfn main() {
16837            +     println!("hello, world!");
16838            + }
16839        "#
16840        .unindent(),
16841    );
16842    cx.assert_index_text(None);
16843
16844    cx.update_editor(|editor, window, cx| {
16845        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16846    });
16847    executor.run_until_parked();
16848    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16849    cx.assert_state_with_diff(
16850        r#"
16851            + ˇfn main() {
16852            +     println!("hello, world!");
16853            + }
16854        "#
16855        .unindent(),
16856    );
16857
16858    cx.update_editor(|editor, window, cx| {
16859        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16860    });
16861    executor.run_until_parked();
16862    cx.assert_index_text(None);
16863}
16864
16865async fn setup_indent_guides_editor(
16866    text: &str,
16867    cx: &mut TestAppContext,
16868) -> (BufferId, EditorTestContext) {
16869    init_test(cx, |_| {});
16870
16871    let mut cx = EditorTestContext::new(cx).await;
16872
16873    let buffer_id = cx.update_editor(|editor, window, cx| {
16874        editor.set_text(text, window, cx);
16875        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16876
16877        buffer_ids[0]
16878    });
16879
16880    (buffer_id, cx)
16881}
16882
16883fn assert_indent_guides(
16884    range: Range<u32>,
16885    expected: Vec<IndentGuide>,
16886    active_indices: Option<Vec<usize>>,
16887    cx: &mut EditorTestContext,
16888) {
16889    let indent_guides = cx.update_editor(|editor, window, cx| {
16890        let snapshot = editor.snapshot(window, cx).display_snapshot;
16891        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16892            editor,
16893            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16894            true,
16895            &snapshot,
16896            cx,
16897        );
16898
16899        indent_guides.sort_by(|a, b| {
16900            a.depth.cmp(&b.depth).then(
16901                a.start_row
16902                    .cmp(&b.start_row)
16903                    .then(a.end_row.cmp(&b.end_row)),
16904            )
16905        });
16906        indent_guides
16907    });
16908
16909    if let Some(expected) = active_indices {
16910        let active_indices = cx.update_editor(|editor, window, cx| {
16911            let snapshot = editor.snapshot(window, cx).display_snapshot;
16912            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16913        });
16914
16915        assert_eq!(
16916            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16917            expected,
16918            "Active indent guide indices do not match"
16919        );
16920    }
16921
16922    assert_eq!(indent_guides, expected, "Indent guides do not match");
16923}
16924
16925fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16926    IndentGuide {
16927        buffer_id,
16928        start_row: MultiBufferRow(start_row),
16929        end_row: MultiBufferRow(end_row),
16930        depth,
16931        tab_size: 4,
16932        settings: IndentGuideSettings {
16933            enabled: true,
16934            line_width: 1,
16935            active_line_width: 1,
16936            ..Default::default()
16937        },
16938    }
16939}
16940
16941#[gpui::test]
16942async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16943    let (buffer_id, mut cx) = setup_indent_guides_editor(
16944        &"
16945        fn main() {
16946            let a = 1;
16947        }"
16948        .unindent(),
16949        cx,
16950    )
16951    .await;
16952
16953    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16954}
16955
16956#[gpui::test]
16957async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16958    let (buffer_id, mut cx) = setup_indent_guides_editor(
16959        &"
16960        fn main() {
16961            let a = 1;
16962            let b = 2;
16963        }"
16964        .unindent(),
16965        cx,
16966    )
16967    .await;
16968
16969    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16970}
16971
16972#[gpui::test]
16973async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16974    let (buffer_id, mut cx) = setup_indent_guides_editor(
16975        &"
16976        fn main() {
16977            let a = 1;
16978            if a == 3 {
16979                let b = 2;
16980            } else {
16981                let c = 3;
16982            }
16983        }"
16984        .unindent(),
16985        cx,
16986    )
16987    .await;
16988
16989    assert_indent_guides(
16990        0..8,
16991        vec![
16992            indent_guide(buffer_id, 1, 6, 0),
16993            indent_guide(buffer_id, 3, 3, 1),
16994            indent_guide(buffer_id, 5, 5, 1),
16995        ],
16996        None,
16997        &mut cx,
16998    );
16999}
17000
17001#[gpui::test]
17002async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17003    let (buffer_id, mut cx) = setup_indent_guides_editor(
17004        &"
17005        fn main() {
17006            let a = 1;
17007                let b = 2;
17008            let c = 3;
17009        }"
17010        .unindent(),
17011        cx,
17012    )
17013    .await;
17014
17015    assert_indent_guides(
17016        0..5,
17017        vec![
17018            indent_guide(buffer_id, 1, 3, 0),
17019            indent_guide(buffer_id, 2, 2, 1),
17020        ],
17021        None,
17022        &mut cx,
17023    );
17024}
17025
17026#[gpui::test]
17027async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17028    let (buffer_id, mut cx) = setup_indent_guides_editor(
17029        &"
17030        fn main() {
17031            let a = 1;
17032
17033            let c = 3;
17034        }"
17035        .unindent(),
17036        cx,
17037    )
17038    .await;
17039
17040    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17041}
17042
17043#[gpui::test]
17044async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17045    let (buffer_id, mut cx) = setup_indent_guides_editor(
17046        &"
17047        fn main() {
17048            let a = 1;
17049
17050            let c = 3;
17051
17052            if a == 3 {
17053                let b = 2;
17054            } else {
17055                let c = 3;
17056            }
17057        }"
17058        .unindent(),
17059        cx,
17060    )
17061    .await;
17062
17063    assert_indent_guides(
17064        0..11,
17065        vec![
17066            indent_guide(buffer_id, 1, 9, 0),
17067            indent_guide(buffer_id, 6, 6, 1),
17068            indent_guide(buffer_id, 8, 8, 1),
17069        ],
17070        None,
17071        &mut cx,
17072    );
17073}
17074
17075#[gpui::test]
17076async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17077    let (buffer_id, mut cx) = setup_indent_guides_editor(
17078        &"
17079        fn main() {
17080            let a = 1;
17081
17082            let c = 3;
17083
17084            if a == 3 {
17085                let b = 2;
17086            } else {
17087                let c = 3;
17088            }
17089        }"
17090        .unindent(),
17091        cx,
17092    )
17093    .await;
17094
17095    assert_indent_guides(
17096        1..11,
17097        vec![
17098            indent_guide(buffer_id, 1, 9, 0),
17099            indent_guide(buffer_id, 6, 6, 1),
17100            indent_guide(buffer_id, 8, 8, 1),
17101        ],
17102        None,
17103        &mut cx,
17104    );
17105}
17106
17107#[gpui::test]
17108async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17109    let (buffer_id, mut cx) = setup_indent_guides_editor(
17110        &"
17111        fn main() {
17112            let a = 1;
17113
17114            let c = 3;
17115
17116            if a == 3 {
17117                let b = 2;
17118            } else {
17119                let c = 3;
17120            }
17121        }"
17122        .unindent(),
17123        cx,
17124    )
17125    .await;
17126
17127    assert_indent_guides(
17128        1..10,
17129        vec![
17130            indent_guide(buffer_id, 1, 9, 0),
17131            indent_guide(buffer_id, 6, 6, 1),
17132            indent_guide(buffer_id, 8, 8, 1),
17133        ],
17134        None,
17135        &mut cx,
17136    );
17137}
17138
17139#[gpui::test]
17140async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17141    let (buffer_id, mut cx) = setup_indent_guides_editor(
17142        &"
17143        fn main() {
17144            if a {
17145                b(
17146                    c,
17147                    d,
17148                )
17149            } else {
17150                e(
17151                    f
17152                )
17153            }
17154        }"
17155        .unindent(),
17156        cx,
17157    )
17158    .await;
17159
17160    assert_indent_guides(
17161        0..11,
17162        vec![
17163            indent_guide(buffer_id, 1, 10, 0),
17164            indent_guide(buffer_id, 2, 5, 1),
17165            indent_guide(buffer_id, 7, 9, 1),
17166            indent_guide(buffer_id, 3, 4, 2),
17167            indent_guide(buffer_id, 8, 8, 2),
17168        ],
17169        None,
17170        &mut cx,
17171    );
17172
17173    cx.update_editor(|editor, window, cx| {
17174        editor.fold_at(MultiBufferRow(2), window, cx);
17175        assert_eq!(
17176            editor.display_text(cx),
17177            "
17178            fn main() {
17179                if a {
17180                    b(⋯
17181                    )
17182                } else {
17183                    e(
17184                        f
17185                    )
17186                }
17187            }"
17188            .unindent()
17189        );
17190    });
17191
17192    assert_indent_guides(
17193        0..11,
17194        vec![
17195            indent_guide(buffer_id, 1, 10, 0),
17196            indent_guide(buffer_id, 2, 5, 1),
17197            indent_guide(buffer_id, 7, 9, 1),
17198            indent_guide(buffer_id, 8, 8, 2),
17199        ],
17200        None,
17201        &mut cx,
17202    );
17203}
17204
17205#[gpui::test]
17206async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17207    let (buffer_id, mut cx) = setup_indent_guides_editor(
17208        &"
17209        block1
17210            block2
17211                block3
17212                    block4
17213            block2
17214        block1
17215        block1"
17216            .unindent(),
17217        cx,
17218    )
17219    .await;
17220
17221    assert_indent_guides(
17222        1..10,
17223        vec![
17224            indent_guide(buffer_id, 1, 4, 0),
17225            indent_guide(buffer_id, 2, 3, 1),
17226            indent_guide(buffer_id, 3, 3, 2),
17227        ],
17228        None,
17229        &mut cx,
17230    );
17231}
17232
17233#[gpui::test]
17234async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17235    let (buffer_id, mut cx) = setup_indent_guides_editor(
17236        &"
17237        block1
17238            block2
17239                block3
17240
17241        block1
17242        block1"
17243            .unindent(),
17244        cx,
17245    )
17246    .await;
17247
17248    assert_indent_guides(
17249        0..6,
17250        vec![
17251            indent_guide(buffer_id, 1, 2, 0),
17252            indent_guide(buffer_id, 2, 2, 1),
17253        ],
17254        None,
17255        &mut cx,
17256    );
17257}
17258
17259#[gpui::test]
17260async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17261    let (buffer_id, mut cx) = setup_indent_guides_editor(
17262        &"
17263        function component() {
17264        \treturn (
17265        \t\t\t
17266        \t\t<div>
17267        \t\t\t<abc></abc>
17268        \t\t</div>
17269        \t)
17270        }"
17271        .unindent(),
17272        cx,
17273    )
17274    .await;
17275
17276    assert_indent_guides(
17277        0..8,
17278        vec![
17279            indent_guide(buffer_id, 1, 6, 0),
17280            indent_guide(buffer_id, 2, 5, 1),
17281            indent_guide(buffer_id, 4, 4, 2),
17282        ],
17283        None,
17284        &mut cx,
17285    );
17286}
17287
17288#[gpui::test]
17289async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17290    let (buffer_id, mut cx) = setup_indent_guides_editor(
17291        &"
17292        function component() {
17293        \treturn (
17294        \t
17295        \t\t<div>
17296        \t\t\t<abc></abc>
17297        \t\t</div>
17298        \t)
17299        }"
17300        .unindent(),
17301        cx,
17302    )
17303    .await;
17304
17305    assert_indent_guides(
17306        0..8,
17307        vec![
17308            indent_guide(buffer_id, 1, 6, 0),
17309            indent_guide(buffer_id, 2, 5, 1),
17310            indent_guide(buffer_id, 4, 4, 2),
17311        ],
17312        None,
17313        &mut cx,
17314    );
17315}
17316
17317#[gpui::test]
17318async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17319    let (buffer_id, mut cx) = setup_indent_guides_editor(
17320        &"
17321        block1
17322
17323
17324
17325            block2
17326        "
17327        .unindent(),
17328        cx,
17329    )
17330    .await;
17331
17332    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17333}
17334
17335#[gpui::test]
17336async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17337    let (buffer_id, mut cx) = setup_indent_guides_editor(
17338        &"
17339        def a:
17340        \tb = 3
17341        \tif True:
17342        \t\tc = 4
17343        \t\td = 5
17344        \tprint(b)
17345        "
17346        .unindent(),
17347        cx,
17348    )
17349    .await;
17350
17351    assert_indent_guides(
17352        0..6,
17353        vec![
17354            indent_guide(buffer_id, 1, 5, 0),
17355            indent_guide(buffer_id, 3, 4, 1),
17356        ],
17357        None,
17358        &mut cx,
17359    );
17360}
17361
17362#[gpui::test]
17363async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17364    let (buffer_id, mut cx) = setup_indent_guides_editor(
17365        &"
17366    fn main() {
17367        let a = 1;
17368    }"
17369        .unindent(),
17370        cx,
17371    )
17372    .await;
17373
17374    cx.update_editor(|editor, window, cx| {
17375        editor.change_selections(None, window, cx, |s| {
17376            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17377        });
17378    });
17379
17380    assert_indent_guides(
17381        0..3,
17382        vec![indent_guide(buffer_id, 1, 1, 0)],
17383        Some(vec![0]),
17384        &mut cx,
17385    );
17386}
17387
17388#[gpui::test]
17389async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17390    let (buffer_id, mut cx) = setup_indent_guides_editor(
17391        &"
17392    fn main() {
17393        if 1 == 2 {
17394            let a = 1;
17395        }
17396    }"
17397        .unindent(),
17398        cx,
17399    )
17400    .await;
17401
17402    cx.update_editor(|editor, window, cx| {
17403        editor.change_selections(None, window, cx, |s| {
17404            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17405        });
17406    });
17407
17408    assert_indent_guides(
17409        0..4,
17410        vec![
17411            indent_guide(buffer_id, 1, 3, 0),
17412            indent_guide(buffer_id, 2, 2, 1),
17413        ],
17414        Some(vec![1]),
17415        &mut cx,
17416    );
17417
17418    cx.update_editor(|editor, window, cx| {
17419        editor.change_selections(None, window, cx, |s| {
17420            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17421        });
17422    });
17423
17424    assert_indent_guides(
17425        0..4,
17426        vec![
17427            indent_guide(buffer_id, 1, 3, 0),
17428            indent_guide(buffer_id, 2, 2, 1),
17429        ],
17430        Some(vec![1]),
17431        &mut cx,
17432    );
17433
17434    cx.update_editor(|editor, window, cx| {
17435        editor.change_selections(None, window, cx, |s| {
17436            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17437        });
17438    });
17439
17440    assert_indent_guides(
17441        0..4,
17442        vec![
17443            indent_guide(buffer_id, 1, 3, 0),
17444            indent_guide(buffer_id, 2, 2, 1),
17445        ],
17446        Some(vec![0]),
17447        &mut cx,
17448    );
17449}
17450
17451#[gpui::test]
17452async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17453    let (buffer_id, mut cx) = setup_indent_guides_editor(
17454        &"
17455    fn main() {
17456        let a = 1;
17457
17458        let b = 2;
17459    }"
17460        .unindent(),
17461        cx,
17462    )
17463    .await;
17464
17465    cx.update_editor(|editor, window, cx| {
17466        editor.change_selections(None, window, cx, |s| {
17467            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17468        });
17469    });
17470
17471    assert_indent_guides(
17472        0..5,
17473        vec![indent_guide(buffer_id, 1, 3, 0)],
17474        Some(vec![0]),
17475        &mut cx,
17476    );
17477}
17478
17479#[gpui::test]
17480async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17481    let (buffer_id, mut cx) = setup_indent_guides_editor(
17482        &"
17483    def m:
17484        a = 1
17485        pass"
17486            .unindent(),
17487        cx,
17488    )
17489    .await;
17490
17491    cx.update_editor(|editor, window, cx| {
17492        editor.change_selections(None, window, cx, |s| {
17493            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17494        });
17495    });
17496
17497    assert_indent_guides(
17498        0..3,
17499        vec![indent_guide(buffer_id, 1, 2, 0)],
17500        Some(vec![0]),
17501        &mut cx,
17502    );
17503}
17504
17505#[gpui::test]
17506async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17507    init_test(cx, |_| {});
17508    let mut cx = EditorTestContext::new(cx).await;
17509    let text = indoc! {
17510        "
17511        impl A {
17512            fn b() {
17513                0;
17514                3;
17515                5;
17516                6;
17517                7;
17518            }
17519        }
17520        "
17521    };
17522    let base_text = indoc! {
17523        "
17524        impl A {
17525            fn b() {
17526                0;
17527                1;
17528                2;
17529                3;
17530                4;
17531            }
17532            fn c() {
17533                5;
17534                6;
17535                7;
17536            }
17537        }
17538        "
17539    };
17540
17541    cx.update_editor(|editor, window, cx| {
17542        editor.set_text(text, window, cx);
17543
17544        editor.buffer().update(cx, |multibuffer, cx| {
17545            let buffer = multibuffer.as_singleton().unwrap();
17546            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17547
17548            multibuffer.set_all_diff_hunks_expanded(cx);
17549            multibuffer.add_diff(diff, cx);
17550
17551            buffer.read(cx).remote_id()
17552        })
17553    });
17554    cx.run_until_parked();
17555
17556    cx.assert_state_with_diff(
17557        indoc! { "
17558          impl A {
17559              fn b() {
17560                  0;
17561        -         1;
17562        -         2;
17563                  3;
17564        -         4;
17565        -     }
17566        -     fn c() {
17567                  5;
17568                  6;
17569                  7;
17570              }
17571          }
17572          ˇ"
17573        }
17574        .to_string(),
17575    );
17576
17577    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17578        editor
17579            .snapshot(window, cx)
17580            .buffer_snapshot
17581            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17582            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17583            .collect::<Vec<_>>()
17584    });
17585    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17586    assert_eq!(
17587        actual_guides,
17588        vec![
17589            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17590            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17591            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17592        ]
17593    );
17594}
17595
17596#[gpui::test]
17597async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17598    init_test(cx, |_| {});
17599    let mut cx = EditorTestContext::new(cx).await;
17600
17601    let diff_base = r#"
17602        a
17603        b
17604        c
17605        "#
17606    .unindent();
17607
17608    cx.set_state(
17609        &r#"
17610        ˇA
17611        b
17612        C
17613        "#
17614        .unindent(),
17615    );
17616    cx.set_head_text(&diff_base);
17617    cx.update_editor(|editor, window, cx| {
17618        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17619    });
17620    executor.run_until_parked();
17621
17622    let both_hunks_expanded = r#"
17623        - a
17624        + ˇA
17625          b
17626        - c
17627        + C
17628        "#
17629    .unindent();
17630
17631    cx.assert_state_with_diff(both_hunks_expanded.clone());
17632
17633    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17634        let snapshot = editor.snapshot(window, cx);
17635        let hunks = editor
17636            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17637            .collect::<Vec<_>>();
17638        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17639        let buffer_id = hunks[0].buffer_id;
17640        hunks
17641            .into_iter()
17642            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17643            .collect::<Vec<_>>()
17644    });
17645    assert_eq!(hunk_ranges.len(), 2);
17646
17647    cx.update_editor(|editor, _, cx| {
17648        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17649    });
17650    executor.run_until_parked();
17651
17652    let second_hunk_expanded = r#"
17653          ˇA
17654          b
17655        - c
17656        + C
17657        "#
17658    .unindent();
17659
17660    cx.assert_state_with_diff(second_hunk_expanded);
17661
17662    cx.update_editor(|editor, _, cx| {
17663        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17664    });
17665    executor.run_until_parked();
17666
17667    cx.assert_state_with_diff(both_hunks_expanded.clone());
17668
17669    cx.update_editor(|editor, _, cx| {
17670        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17671    });
17672    executor.run_until_parked();
17673
17674    let first_hunk_expanded = r#"
17675        - a
17676        + ˇA
17677          b
17678          C
17679        "#
17680    .unindent();
17681
17682    cx.assert_state_with_diff(first_hunk_expanded);
17683
17684    cx.update_editor(|editor, _, cx| {
17685        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17686    });
17687    executor.run_until_parked();
17688
17689    cx.assert_state_with_diff(both_hunks_expanded);
17690
17691    cx.set_state(
17692        &r#"
17693        ˇA
17694        b
17695        "#
17696        .unindent(),
17697    );
17698    cx.run_until_parked();
17699
17700    // TODO this cursor position seems bad
17701    cx.assert_state_with_diff(
17702        r#"
17703        - ˇa
17704        + A
17705          b
17706        "#
17707        .unindent(),
17708    );
17709
17710    cx.update_editor(|editor, window, cx| {
17711        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17712    });
17713
17714    cx.assert_state_with_diff(
17715        r#"
17716            - ˇa
17717            + A
17718              b
17719            - c
17720            "#
17721        .unindent(),
17722    );
17723
17724    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17725        let snapshot = editor.snapshot(window, cx);
17726        let hunks = editor
17727            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17728            .collect::<Vec<_>>();
17729        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17730        let buffer_id = hunks[0].buffer_id;
17731        hunks
17732            .into_iter()
17733            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17734            .collect::<Vec<_>>()
17735    });
17736    assert_eq!(hunk_ranges.len(), 2);
17737
17738    cx.update_editor(|editor, _, cx| {
17739        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17740    });
17741    executor.run_until_parked();
17742
17743    cx.assert_state_with_diff(
17744        r#"
17745        - ˇa
17746        + A
17747          b
17748        "#
17749        .unindent(),
17750    );
17751}
17752
17753#[gpui::test]
17754async fn test_toggle_deletion_hunk_at_start_of_file(
17755    executor: BackgroundExecutor,
17756    cx: &mut TestAppContext,
17757) {
17758    init_test(cx, |_| {});
17759    let mut cx = EditorTestContext::new(cx).await;
17760
17761    let diff_base = r#"
17762        a
17763        b
17764        c
17765        "#
17766    .unindent();
17767
17768    cx.set_state(
17769        &r#"
17770        ˇb
17771        c
17772        "#
17773        .unindent(),
17774    );
17775    cx.set_head_text(&diff_base);
17776    cx.update_editor(|editor, window, cx| {
17777        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17778    });
17779    executor.run_until_parked();
17780
17781    let hunk_expanded = r#"
17782        - a
17783          ˇb
17784          c
17785        "#
17786    .unindent();
17787
17788    cx.assert_state_with_diff(hunk_expanded.clone());
17789
17790    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17791        let snapshot = editor.snapshot(window, cx);
17792        let hunks = editor
17793            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17794            .collect::<Vec<_>>();
17795        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17796        let buffer_id = hunks[0].buffer_id;
17797        hunks
17798            .into_iter()
17799            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17800            .collect::<Vec<_>>()
17801    });
17802    assert_eq!(hunk_ranges.len(), 1);
17803
17804    cx.update_editor(|editor, _, cx| {
17805        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17806    });
17807    executor.run_until_parked();
17808
17809    let hunk_collapsed = r#"
17810          ˇb
17811          c
17812        "#
17813    .unindent();
17814
17815    cx.assert_state_with_diff(hunk_collapsed);
17816
17817    cx.update_editor(|editor, _, cx| {
17818        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17819    });
17820    executor.run_until_parked();
17821
17822    cx.assert_state_with_diff(hunk_expanded.clone());
17823}
17824
17825#[gpui::test]
17826async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17827    init_test(cx, |_| {});
17828
17829    let fs = FakeFs::new(cx.executor());
17830    fs.insert_tree(
17831        path!("/test"),
17832        json!({
17833            ".git": {},
17834            "file-1": "ONE\n",
17835            "file-2": "TWO\n",
17836            "file-3": "THREE\n",
17837        }),
17838    )
17839    .await;
17840
17841    fs.set_head_for_repo(
17842        path!("/test/.git").as_ref(),
17843        &[
17844            ("file-1".into(), "one\n".into()),
17845            ("file-2".into(), "two\n".into()),
17846            ("file-3".into(), "three\n".into()),
17847        ],
17848    );
17849
17850    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17851    let mut buffers = vec![];
17852    for i in 1..=3 {
17853        let buffer = project
17854            .update(cx, |project, cx| {
17855                let path = format!(path!("/test/file-{}"), i);
17856                project.open_local_buffer(path, cx)
17857            })
17858            .await
17859            .unwrap();
17860        buffers.push(buffer);
17861    }
17862
17863    let multibuffer = cx.new(|cx| {
17864        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17865        multibuffer.set_all_diff_hunks_expanded(cx);
17866        for buffer in &buffers {
17867            let snapshot = buffer.read(cx).snapshot();
17868            multibuffer.set_excerpts_for_path(
17869                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17870                buffer.clone(),
17871                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17872                DEFAULT_MULTIBUFFER_CONTEXT,
17873                cx,
17874            );
17875        }
17876        multibuffer
17877    });
17878
17879    let editor = cx.add_window(|window, cx| {
17880        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17881    });
17882    cx.run_until_parked();
17883
17884    let snapshot = editor
17885        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17886        .unwrap();
17887    let hunks = snapshot
17888        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17889        .map(|hunk| match hunk {
17890            DisplayDiffHunk::Unfolded {
17891                display_row_range, ..
17892            } => display_row_range,
17893            DisplayDiffHunk::Folded { .. } => unreachable!(),
17894        })
17895        .collect::<Vec<_>>();
17896    assert_eq!(
17897        hunks,
17898        [
17899            DisplayRow(2)..DisplayRow(4),
17900            DisplayRow(7)..DisplayRow(9),
17901            DisplayRow(12)..DisplayRow(14),
17902        ]
17903    );
17904}
17905
17906#[gpui::test]
17907async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17908    init_test(cx, |_| {});
17909
17910    let mut cx = EditorTestContext::new(cx).await;
17911    cx.set_head_text(indoc! { "
17912        one
17913        two
17914        three
17915        four
17916        five
17917        "
17918    });
17919    cx.set_index_text(indoc! { "
17920        one
17921        two
17922        three
17923        four
17924        five
17925        "
17926    });
17927    cx.set_state(indoc! {"
17928        one
17929        TWO
17930        ˇTHREE
17931        FOUR
17932        five
17933    "});
17934    cx.run_until_parked();
17935    cx.update_editor(|editor, window, cx| {
17936        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17937    });
17938    cx.run_until_parked();
17939    cx.assert_index_text(Some(indoc! {"
17940        one
17941        TWO
17942        THREE
17943        FOUR
17944        five
17945    "}));
17946    cx.set_state(indoc! { "
17947        one
17948        TWO
17949        ˇTHREE-HUNDRED
17950        FOUR
17951        five
17952    "});
17953    cx.run_until_parked();
17954    cx.update_editor(|editor, window, cx| {
17955        let snapshot = editor.snapshot(window, cx);
17956        let hunks = editor
17957            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17958            .collect::<Vec<_>>();
17959        assert_eq!(hunks.len(), 1);
17960        assert_eq!(
17961            hunks[0].status(),
17962            DiffHunkStatus {
17963                kind: DiffHunkStatusKind::Modified,
17964                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17965            }
17966        );
17967
17968        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17969    });
17970    cx.run_until_parked();
17971    cx.assert_index_text(Some(indoc! {"
17972        one
17973        TWO
17974        THREE-HUNDRED
17975        FOUR
17976        five
17977    "}));
17978}
17979
17980#[gpui::test]
17981fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17982    init_test(cx, |_| {});
17983
17984    let editor = cx.add_window(|window, cx| {
17985        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17986        build_editor(buffer, window, cx)
17987    });
17988
17989    let render_args = Arc::new(Mutex::new(None));
17990    let snapshot = editor
17991        .update(cx, |editor, window, cx| {
17992            let snapshot = editor.buffer().read(cx).snapshot(cx);
17993            let range =
17994                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17995
17996            struct RenderArgs {
17997                row: MultiBufferRow,
17998                folded: bool,
17999                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18000            }
18001
18002            let crease = Crease::inline(
18003                range,
18004                FoldPlaceholder::test(),
18005                {
18006                    let toggle_callback = render_args.clone();
18007                    move |row, folded, callback, _window, _cx| {
18008                        *toggle_callback.lock() = Some(RenderArgs {
18009                            row,
18010                            folded,
18011                            callback,
18012                        });
18013                        div()
18014                    }
18015                },
18016                |_row, _folded, _window, _cx| div(),
18017            );
18018
18019            editor.insert_creases(Some(crease), cx);
18020            let snapshot = editor.snapshot(window, cx);
18021            let _div = snapshot.render_crease_toggle(
18022                MultiBufferRow(1),
18023                false,
18024                cx.entity().clone(),
18025                window,
18026                cx,
18027            );
18028            snapshot
18029        })
18030        .unwrap();
18031
18032    let render_args = render_args.lock().take().unwrap();
18033    assert_eq!(render_args.row, MultiBufferRow(1));
18034    assert!(!render_args.folded);
18035    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18036
18037    cx.update_window(*editor, |_, window, cx| {
18038        (render_args.callback)(true, window, cx)
18039    })
18040    .unwrap();
18041    let snapshot = editor
18042        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18043        .unwrap();
18044    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18045
18046    cx.update_window(*editor, |_, window, cx| {
18047        (render_args.callback)(false, window, cx)
18048    })
18049    .unwrap();
18050    let snapshot = editor
18051        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18052        .unwrap();
18053    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18054}
18055
18056#[gpui::test]
18057async fn test_input_text(cx: &mut TestAppContext) {
18058    init_test(cx, |_| {});
18059    let mut cx = EditorTestContext::new(cx).await;
18060
18061    cx.set_state(
18062        &r#"ˇone
18063        two
18064
18065        three
18066        fourˇ
18067        five
18068
18069        siˇx"#
18070            .unindent(),
18071    );
18072
18073    cx.dispatch_action(HandleInput(String::new()));
18074    cx.assert_editor_state(
18075        &r#"ˇone
18076        two
18077
18078        three
18079        fourˇ
18080        five
18081
18082        siˇx"#
18083            .unindent(),
18084    );
18085
18086    cx.dispatch_action(HandleInput("AAAA".to_string()));
18087    cx.assert_editor_state(
18088        &r#"AAAAˇone
18089        two
18090
18091        three
18092        fourAAAAˇ
18093        five
18094
18095        siAAAAˇx"#
18096            .unindent(),
18097    );
18098}
18099
18100#[gpui::test]
18101async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18102    init_test(cx, |_| {});
18103
18104    let mut cx = EditorTestContext::new(cx).await;
18105    cx.set_state(
18106        r#"let foo = 1;
18107let foo = 2;
18108let foo = 3;
18109let fooˇ = 4;
18110let foo = 5;
18111let foo = 6;
18112let foo = 7;
18113let foo = 8;
18114let foo = 9;
18115let foo = 10;
18116let foo = 11;
18117let foo = 12;
18118let foo = 13;
18119let foo = 14;
18120let foo = 15;"#,
18121    );
18122
18123    cx.update_editor(|e, window, cx| {
18124        assert_eq!(
18125            e.next_scroll_position,
18126            NextScrollCursorCenterTopBottom::Center,
18127            "Default next scroll direction is center",
18128        );
18129
18130        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18131        assert_eq!(
18132            e.next_scroll_position,
18133            NextScrollCursorCenterTopBottom::Top,
18134            "After center, next scroll direction should be top",
18135        );
18136
18137        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18138        assert_eq!(
18139            e.next_scroll_position,
18140            NextScrollCursorCenterTopBottom::Bottom,
18141            "After top, next scroll direction should be bottom",
18142        );
18143
18144        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18145        assert_eq!(
18146            e.next_scroll_position,
18147            NextScrollCursorCenterTopBottom::Center,
18148            "After bottom, scrolling should start over",
18149        );
18150
18151        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18152        assert_eq!(
18153            e.next_scroll_position,
18154            NextScrollCursorCenterTopBottom::Top,
18155            "Scrolling continues if retriggered fast enough"
18156        );
18157    });
18158
18159    cx.executor()
18160        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18161    cx.executor().run_until_parked();
18162    cx.update_editor(|e, _, _| {
18163        assert_eq!(
18164            e.next_scroll_position,
18165            NextScrollCursorCenterTopBottom::Center,
18166            "If scrolling is not triggered fast enough, it should reset"
18167        );
18168    });
18169}
18170
18171#[gpui::test]
18172async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18173    init_test(cx, |_| {});
18174    let mut cx = EditorLspTestContext::new_rust(
18175        lsp::ServerCapabilities {
18176            definition_provider: Some(lsp::OneOf::Left(true)),
18177            references_provider: Some(lsp::OneOf::Left(true)),
18178            ..lsp::ServerCapabilities::default()
18179        },
18180        cx,
18181    )
18182    .await;
18183
18184    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18185        let go_to_definition = cx
18186            .lsp
18187            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18188                move |params, _| async move {
18189                    if empty_go_to_definition {
18190                        Ok(None)
18191                    } else {
18192                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18193                            uri: params.text_document_position_params.text_document.uri,
18194                            range: lsp::Range::new(
18195                                lsp::Position::new(4, 3),
18196                                lsp::Position::new(4, 6),
18197                            ),
18198                        })))
18199                    }
18200                },
18201            );
18202        let references = cx
18203            .lsp
18204            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18205                Ok(Some(vec![lsp::Location {
18206                    uri: params.text_document_position.text_document.uri,
18207                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18208                }]))
18209            });
18210        (go_to_definition, references)
18211    };
18212
18213    cx.set_state(
18214        &r#"fn one() {
18215            let mut a = ˇtwo();
18216        }
18217
18218        fn two() {}"#
18219            .unindent(),
18220    );
18221    set_up_lsp_handlers(false, &mut cx);
18222    let navigated = cx
18223        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18224        .await
18225        .expect("Failed to navigate to definition");
18226    assert_eq!(
18227        navigated,
18228        Navigated::Yes,
18229        "Should have navigated to definition from the GetDefinition response"
18230    );
18231    cx.assert_editor_state(
18232        &r#"fn one() {
18233            let mut a = two();
18234        }
18235
18236        fn «twoˇ»() {}"#
18237            .unindent(),
18238    );
18239
18240    let editors = cx.update_workspace(|workspace, _, cx| {
18241        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18242    });
18243    cx.update_editor(|_, _, test_editor_cx| {
18244        assert_eq!(
18245            editors.len(),
18246            1,
18247            "Initially, only one, test, editor should be open in the workspace"
18248        );
18249        assert_eq!(
18250            test_editor_cx.entity(),
18251            editors.last().expect("Asserted len is 1").clone()
18252        );
18253    });
18254
18255    set_up_lsp_handlers(true, &mut cx);
18256    let navigated = cx
18257        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18258        .await
18259        .expect("Failed to navigate to lookup references");
18260    assert_eq!(
18261        navigated,
18262        Navigated::Yes,
18263        "Should have navigated to references as a fallback after empty GoToDefinition response"
18264    );
18265    // We should not change the selections in the existing file,
18266    // if opening another milti buffer with the references
18267    cx.assert_editor_state(
18268        &r#"fn one() {
18269            let mut a = two();
18270        }
18271
18272        fn «twoˇ»() {}"#
18273            .unindent(),
18274    );
18275    let editors = cx.update_workspace(|workspace, _, cx| {
18276        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18277    });
18278    cx.update_editor(|_, _, test_editor_cx| {
18279        assert_eq!(
18280            editors.len(),
18281            2,
18282            "After falling back to references search, we open a new editor with the results"
18283        );
18284        let references_fallback_text = editors
18285            .into_iter()
18286            .find(|new_editor| *new_editor != test_editor_cx.entity())
18287            .expect("Should have one non-test editor now")
18288            .read(test_editor_cx)
18289            .text(test_editor_cx);
18290        assert_eq!(
18291            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18292            "Should use the range from the references response and not the GoToDefinition one"
18293        );
18294    });
18295}
18296
18297#[gpui::test]
18298async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18299    init_test(cx, |_| {});
18300    cx.update(|cx| {
18301        let mut editor_settings = EditorSettings::get_global(cx).clone();
18302        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18303        EditorSettings::override_global(editor_settings, cx);
18304    });
18305    let mut cx = EditorLspTestContext::new_rust(
18306        lsp::ServerCapabilities {
18307            definition_provider: Some(lsp::OneOf::Left(true)),
18308            references_provider: Some(lsp::OneOf::Left(true)),
18309            ..lsp::ServerCapabilities::default()
18310        },
18311        cx,
18312    )
18313    .await;
18314    let original_state = r#"fn one() {
18315        let mut a = ˇtwo();
18316    }
18317
18318    fn two() {}"#
18319        .unindent();
18320    cx.set_state(&original_state);
18321
18322    let mut go_to_definition = cx
18323        .lsp
18324        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18325            move |_, _| async move { Ok(None) },
18326        );
18327    let _references = cx
18328        .lsp
18329        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18330            panic!("Should not call for references with no go to definition fallback")
18331        });
18332
18333    let navigated = cx
18334        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18335        .await
18336        .expect("Failed to navigate to lookup references");
18337    go_to_definition
18338        .next()
18339        .await
18340        .expect("Should have called the go_to_definition handler");
18341
18342    assert_eq!(
18343        navigated,
18344        Navigated::No,
18345        "Should have navigated to references as a fallback after empty GoToDefinition response"
18346    );
18347    cx.assert_editor_state(&original_state);
18348    let editors = cx.update_workspace(|workspace, _, cx| {
18349        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18350    });
18351    cx.update_editor(|_, _, _| {
18352        assert_eq!(
18353            editors.len(),
18354            1,
18355            "After unsuccessful fallback, no other editor should have been opened"
18356        );
18357    });
18358}
18359
18360#[gpui::test]
18361async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18362    init_test(cx, |_| {});
18363
18364    let language = Arc::new(Language::new(
18365        LanguageConfig::default(),
18366        Some(tree_sitter_rust::LANGUAGE.into()),
18367    ));
18368
18369    let text = r#"
18370        #[cfg(test)]
18371        mod tests() {
18372            #[test]
18373            fn runnable_1() {
18374                let a = 1;
18375            }
18376
18377            #[test]
18378            fn runnable_2() {
18379                let a = 1;
18380                let b = 2;
18381            }
18382        }
18383    "#
18384    .unindent();
18385
18386    let fs = FakeFs::new(cx.executor());
18387    fs.insert_file("/file.rs", Default::default()).await;
18388
18389    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18390    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18391    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18392    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18393    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18394
18395    let editor = cx.new_window_entity(|window, cx| {
18396        Editor::new(
18397            EditorMode::full(),
18398            multi_buffer,
18399            Some(project.clone()),
18400            window,
18401            cx,
18402        )
18403    });
18404
18405    editor.update_in(cx, |editor, window, cx| {
18406        let snapshot = editor.buffer().read(cx).snapshot(cx);
18407        editor.tasks.insert(
18408            (buffer.read(cx).remote_id(), 3),
18409            RunnableTasks {
18410                templates: vec![],
18411                offset: snapshot.anchor_before(43),
18412                column: 0,
18413                extra_variables: HashMap::default(),
18414                context_range: BufferOffset(43)..BufferOffset(85),
18415            },
18416        );
18417        editor.tasks.insert(
18418            (buffer.read(cx).remote_id(), 8),
18419            RunnableTasks {
18420                templates: vec![],
18421                offset: snapshot.anchor_before(86),
18422                column: 0,
18423                extra_variables: HashMap::default(),
18424                context_range: BufferOffset(86)..BufferOffset(191),
18425            },
18426        );
18427
18428        // Test finding task when cursor is inside function body
18429        editor.change_selections(None, window, cx, |s| {
18430            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18431        });
18432        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18433        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18434
18435        // Test finding task when cursor is on function name
18436        editor.change_selections(None, window, cx, |s| {
18437            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18438        });
18439        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18440        assert_eq!(row, 8, "Should find task when cursor is on function name");
18441    });
18442}
18443
18444#[gpui::test]
18445async fn test_folding_buffers(cx: &mut TestAppContext) {
18446    init_test(cx, |_| {});
18447
18448    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18449    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18450    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18451
18452    let fs = FakeFs::new(cx.executor());
18453    fs.insert_tree(
18454        path!("/a"),
18455        json!({
18456            "first.rs": sample_text_1,
18457            "second.rs": sample_text_2,
18458            "third.rs": sample_text_3,
18459        }),
18460    )
18461    .await;
18462    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18463    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18464    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18465    let worktree = project.update(cx, |project, cx| {
18466        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18467        assert_eq!(worktrees.len(), 1);
18468        worktrees.pop().unwrap()
18469    });
18470    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18471
18472    let buffer_1 = project
18473        .update(cx, |project, cx| {
18474            project.open_buffer((worktree_id, "first.rs"), cx)
18475        })
18476        .await
18477        .unwrap();
18478    let buffer_2 = project
18479        .update(cx, |project, cx| {
18480            project.open_buffer((worktree_id, "second.rs"), cx)
18481        })
18482        .await
18483        .unwrap();
18484    let buffer_3 = project
18485        .update(cx, |project, cx| {
18486            project.open_buffer((worktree_id, "third.rs"), cx)
18487        })
18488        .await
18489        .unwrap();
18490
18491    let multi_buffer = cx.new(|cx| {
18492        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18493        multi_buffer.push_excerpts(
18494            buffer_1.clone(),
18495            [
18496                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18497                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18498                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18499            ],
18500            cx,
18501        );
18502        multi_buffer.push_excerpts(
18503            buffer_2.clone(),
18504            [
18505                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18506                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18507                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18508            ],
18509            cx,
18510        );
18511        multi_buffer.push_excerpts(
18512            buffer_3.clone(),
18513            [
18514                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18515                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18516                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18517            ],
18518            cx,
18519        );
18520        multi_buffer
18521    });
18522    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18523        Editor::new(
18524            EditorMode::full(),
18525            multi_buffer.clone(),
18526            Some(project.clone()),
18527            window,
18528            cx,
18529        )
18530    });
18531
18532    assert_eq!(
18533        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18534        "\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",
18535    );
18536
18537    multi_buffer_editor.update(cx, |editor, cx| {
18538        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18539    });
18540    assert_eq!(
18541        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18542        "\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",
18543        "After folding the first buffer, its text should not be displayed"
18544    );
18545
18546    multi_buffer_editor.update(cx, |editor, cx| {
18547        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18548    });
18549    assert_eq!(
18550        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18551        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18552        "After folding the second buffer, its text should not be displayed"
18553    );
18554
18555    multi_buffer_editor.update(cx, |editor, cx| {
18556        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18557    });
18558    assert_eq!(
18559        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18560        "\n\n\n\n\n",
18561        "After folding the third buffer, its text should not be displayed"
18562    );
18563
18564    // Emulate selection inside the fold logic, that should work
18565    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18566        editor
18567            .snapshot(window, cx)
18568            .next_line_boundary(Point::new(0, 4));
18569    });
18570
18571    multi_buffer_editor.update(cx, |editor, cx| {
18572        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18573    });
18574    assert_eq!(
18575        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18576        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18577        "After unfolding the second buffer, its text should be displayed"
18578    );
18579
18580    // Typing inside of buffer 1 causes that buffer to be unfolded.
18581    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18582        assert_eq!(
18583            multi_buffer
18584                .read(cx)
18585                .snapshot(cx)
18586                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18587                .collect::<String>(),
18588            "bbbb"
18589        );
18590        editor.change_selections(None, window, cx, |selections| {
18591            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18592        });
18593        editor.handle_input("B", window, cx);
18594    });
18595
18596    assert_eq!(
18597        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18598        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18599        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18600    );
18601
18602    multi_buffer_editor.update(cx, |editor, cx| {
18603        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18604    });
18605    assert_eq!(
18606        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18607        "\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",
18608        "After unfolding the all buffers, all original text should be displayed"
18609    );
18610}
18611
18612#[gpui::test]
18613async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18614    init_test(cx, |_| {});
18615
18616    let sample_text_1 = "1111\n2222\n3333".to_string();
18617    let sample_text_2 = "4444\n5555\n6666".to_string();
18618    let sample_text_3 = "7777\n8888\n9999".to_string();
18619
18620    let fs = FakeFs::new(cx.executor());
18621    fs.insert_tree(
18622        path!("/a"),
18623        json!({
18624            "first.rs": sample_text_1,
18625            "second.rs": sample_text_2,
18626            "third.rs": sample_text_3,
18627        }),
18628    )
18629    .await;
18630    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18631    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18632    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18633    let worktree = project.update(cx, |project, cx| {
18634        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18635        assert_eq!(worktrees.len(), 1);
18636        worktrees.pop().unwrap()
18637    });
18638    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18639
18640    let buffer_1 = project
18641        .update(cx, |project, cx| {
18642            project.open_buffer((worktree_id, "first.rs"), cx)
18643        })
18644        .await
18645        .unwrap();
18646    let buffer_2 = project
18647        .update(cx, |project, cx| {
18648            project.open_buffer((worktree_id, "second.rs"), cx)
18649        })
18650        .await
18651        .unwrap();
18652    let buffer_3 = project
18653        .update(cx, |project, cx| {
18654            project.open_buffer((worktree_id, "third.rs"), cx)
18655        })
18656        .await
18657        .unwrap();
18658
18659    let multi_buffer = cx.new(|cx| {
18660        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18661        multi_buffer.push_excerpts(
18662            buffer_1.clone(),
18663            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18664            cx,
18665        );
18666        multi_buffer.push_excerpts(
18667            buffer_2.clone(),
18668            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18669            cx,
18670        );
18671        multi_buffer.push_excerpts(
18672            buffer_3.clone(),
18673            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18674            cx,
18675        );
18676        multi_buffer
18677    });
18678
18679    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18680        Editor::new(
18681            EditorMode::full(),
18682            multi_buffer,
18683            Some(project.clone()),
18684            window,
18685            cx,
18686        )
18687    });
18688
18689    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18690    assert_eq!(
18691        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18692        full_text,
18693    );
18694
18695    multi_buffer_editor.update(cx, |editor, cx| {
18696        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18697    });
18698    assert_eq!(
18699        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18700        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18701        "After folding the first buffer, its text should not be displayed"
18702    );
18703
18704    multi_buffer_editor.update(cx, |editor, cx| {
18705        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18706    });
18707
18708    assert_eq!(
18709        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18710        "\n\n\n\n\n\n7777\n8888\n9999",
18711        "After folding the second buffer, its text should not be displayed"
18712    );
18713
18714    multi_buffer_editor.update(cx, |editor, cx| {
18715        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18716    });
18717    assert_eq!(
18718        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18719        "\n\n\n\n\n",
18720        "After folding the third buffer, its text should not be displayed"
18721    );
18722
18723    multi_buffer_editor.update(cx, |editor, cx| {
18724        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18725    });
18726    assert_eq!(
18727        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18728        "\n\n\n\n4444\n5555\n6666\n\n",
18729        "After unfolding the second buffer, its text should be displayed"
18730    );
18731
18732    multi_buffer_editor.update(cx, |editor, cx| {
18733        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18734    });
18735    assert_eq!(
18736        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18737        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18738        "After unfolding the first buffer, its text should be displayed"
18739    );
18740
18741    multi_buffer_editor.update(cx, |editor, cx| {
18742        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18743    });
18744    assert_eq!(
18745        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18746        full_text,
18747        "After unfolding all buffers, all original text should be displayed"
18748    );
18749}
18750
18751#[gpui::test]
18752async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18753    init_test(cx, |_| {});
18754
18755    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18756
18757    let fs = FakeFs::new(cx.executor());
18758    fs.insert_tree(
18759        path!("/a"),
18760        json!({
18761            "main.rs": sample_text,
18762        }),
18763    )
18764    .await;
18765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18766    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18768    let worktree = project.update(cx, |project, cx| {
18769        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18770        assert_eq!(worktrees.len(), 1);
18771        worktrees.pop().unwrap()
18772    });
18773    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18774
18775    let buffer_1 = project
18776        .update(cx, |project, cx| {
18777            project.open_buffer((worktree_id, "main.rs"), cx)
18778        })
18779        .await
18780        .unwrap();
18781
18782    let multi_buffer = cx.new(|cx| {
18783        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18784        multi_buffer.push_excerpts(
18785            buffer_1.clone(),
18786            [ExcerptRange::new(
18787                Point::new(0, 0)
18788                    ..Point::new(
18789                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18790                        0,
18791                    ),
18792            )],
18793            cx,
18794        );
18795        multi_buffer
18796    });
18797    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18798        Editor::new(
18799            EditorMode::full(),
18800            multi_buffer,
18801            Some(project.clone()),
18802            window,
18803            cx,
18804        )
18805    });
18806
18807    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18808    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18809        enum TestHighlight {}
18810        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18811        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18812        editor.highlight_text::<TestHighlight>(
18813            vec![highlight_range.clone()],
18814            HighlightStyle::color(Hsla::green()),
18815            cx,
18816        );
18817        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18818    });
18819
18820    let full_text = format!("\n\n{sample_text}");
18821    assert_eq!(
18822        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18823        full_text,
18824    );
18825}
18826
18827#[gpui::test]
18828async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18829    init_test(cx, |_| {});
18830    cx.update(|cx| {
18831        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18832            "keymaps/default-linux.json",
18833            cx,
18834        )
18835        .unwrap();
18836        cx.bind_keys(default_key_bindings);
18837    });
18838
18839    let (editor, cx) = cx.add_window_view(|window, cx| {
18840        let multi_buffer = MultiBuffer::build_multi(
18841            [
18842                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18843                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18844                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18845                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18846            ],
18847            cx,
18848        );
18849        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18850
18851        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18852        // fold all but the second buffer, so that we test navigating between two
18853        // adjacent folded buffers, as well as folded buffers at the start and
18854        // end the multibuffer
18855        editor.fold_buffer(buffer_ids[0], cx);
18856        editor.fold_buffer(buffer_ids[2], cx);
18857        editor.fold_buffer(buffer_ids[3], cx);
18858
18859        editor
18860    });
18861    cx.simulate_resize(size(px(1000.), px(1000.)));
18862
18863    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18864    cx.assert_excerpts_with_selections(indoc! {"
18865        [EXCERPT]
18866        ˇ[FOLDED]
18867        [EXCERPT]
18868        a1
18869        b1
18870        [EXCERPT]
18871        [FOLDED]
18872        [EXCERPT]
18873        [FOLDED]
18874        "
18875    });
18876    cx.simulate_keystroke("down");
18877    cx.assert_excerpts_with_selections(indoc! {"
18878        [EXCERPT]
18879        [FOLDED]
18880        [EXCERPT]
18881        ˇa1
18882        b1
18883        [EXCERPT]
18884        [FOLDED]
18885        [EXCERPT]
18886        [FOLDED]
18887        "
18888    });
18889    cx.simulate_keystroke("down");
18890    cx.assert_excerpts_with_selections(indoc! {"
18891        [EXCERPT]
18892        [FOLDED]
18893        [EXCERPT]
18894        a1
18895        ˇb1
18896        [EXCERPT]
18897        [FOLDED]
18898        [EXCERPT]
18899        [FOLDED]
18900        "
18901    });
18902    cx.simulate_keystroke("down");
18903    cx.assert_excerpts_with_selections(indoc! {"
18904        [EXCERPT]
18905        [FOLDED]
18906        [EXCERPT]
18907        a1
18908        b1
18909        ˇ[EXCERPT]
18910        [FOLDED]
18911        [EXCERPT]
18912        [FOLDED]
18913        "
18914    });
18915    cx.simulate_keystroke("down");
18916    cx.assert_excerpts_with_selections(indoc! {"
18917        [EXCERPT]
18918        [FOLDED]
18919        [EXCERPT]
18920        a1
18921        b1
18922        [EXCERPT]
18923        ˇ[FOLDED]
18924        [EXCERPT]
18925        [FOLDED]
18926        "
18927    });
18928    for _ in 0..5 {
18929        cx.simulate_keystroke("down");
18930        cx.assert_excerpts_with_selections(indoc! {"
18931            [EXCERPT]
18932            [FOLDED]
18933            [EXCERPT]
18934            a1
18935            b1
18936            [EXCERPT]
18937            [FOLDED]
18938            [EXCERPT]
18939            ˇ[FOLDED]
18940            "
18941        });
18942    }
18943
18944    cx.simulate_keystroke("up");
18945    cx.assert_excerpts_with_selections(indoc! {"
18946        [EXCERPT]
18947        [FOLDED]
18948        [EXCERPT]
18949        a1
18950        b1
18951        [EXCERPT]
18952        ˇ[FOLDED]
18953        [EXCERPT]
18954        [FOLDED]
18955        "
18956    });
18957    cx.simulate_keystroke("up");
18958    cx.assert_excerpts_with_selections(indoc! {"
18959        [EXCERPT]
18960        [FOLDED]
18961        [EXCERPT]
18962        a1
18963        b1
18964        ˇ[EXCERPT]
18965        [FOLDED]
18966        [EXCERPT]
18967        [FOLDED]
18968        "
18969    });
18970    cx.simulate_keystroke("up");
18971    cx.assert_excerpts_with_selections(indoc! {"
18972        [EXCERPT]
18973        [FOLDED]
18974        [EXCERPT]
18975        a1
18976        ˇb1
18977        [EXCERPT]
18978        [FOLDED]
18979        [EXCERPT]
18980        [FOLDED]
18981        "
18982    });
18983    cx.simulate_keystroke("up");
18984    cx.assert_excerpts_with_selections(indoc! {"
18985        [EXCERPT]
18986        [FOLDED]
18987        [EXCERPT]
18988        ˇa1
18989        b1
18990        [EXCERPT]
18991        [FOLDED]
18992        [EXCERPT]
18993        [FOLDED]
18994        "
18995    });
18996    for _ in 0..5 {
18997        cx.simulate_keystroke("up");
18998        cx.assert_excerpts_with_selections(indoc! {"
18999            [EXCERPT]
19000            ˇ[FOLDED]
19001            [EXCERPT]
19002            a1
19003            b1
19004            [EXCERPT]
19005            [FOLDED]
19006            [EXCERPT]
19007            [FOLDED]
19008            "
19009        });
19010    }
19011}
19012
19013#[gpui::test]
19014async fn test_inline_completion_text(cx: &mut TestAppContext) {
19015    init_test(cx, |_| {});
19016
19017    // Simple insertion
19018    assert_highlighted_edits(
19019        "Hello, world!",
19020        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19021        true,
19022        cx,
19023        |highlighted_edits, cx| {
19024            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19025            assert_eq!(highlighted_edits.highlights.len(), 1);
19026            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19027            assert_eq!(
19028                highlighted_edits.highlights[0].1.background_color,
19029                Some(cx.theme().status().created_background)
19030            );
19031        },
19032    )
19033    .await;
19034
19035    // Replacement
19036    assert_highlighted_edits(
19037        "This is a test.",
19038        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19039        false,
19040        cx,
19041        |highlighted_edits, cx| {
19042            assert_eq!(highlighted_edits.text, "That is a test.");
19043            assert_eq!(highlighted_edits.highlights.len(), 1);
19044            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19045            assert_eq!(
19046                highlighted_edits.highlights[0].1.background_color,
19047                Some(cx.theme().status().created_background)
19048            );
19049        },
19050    )
19051    .await;
19052
19053    // Multiple edits
19054    assert_highlighted_edits(
19055        "Hello, world!",
19056        vec![
19057            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19058            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19059        ],
19060        false,
19061        cx,
19062        |highlighted_edits, cx| {
19063            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19064            assert_eq!(highlighted_edits.highlights.len(), 2);
19065            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19066            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19067            assert_eq!(
19068                highlighted_edits.highlights[0].1.background_color,
19069                Some(cx.theme().status().created_background)
19070            );
19071            assert_eq!(
19072                highlighted_edits.highlights[1].1.background_color,
19073                Some(cx.theme().status().created_background)
19074            );
19075        },
19076    )
19077    .await;
19078
19079    // Multiple lines with edits
19080    assert_highlighted_edits(
19081        "First line\nSecond line\nThird line\nFourth line",
19082        vec![
19083            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19084            (
19085                Point::new(2, 0)..Point::new(2, 10),
19086                "New third line".to_string(),
19087            ),
19088            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19089        ],
19090        false,
19091        cx,
19092        |highlighted_edits, cx| {
19093            assert_eq!(
19094                highlighted_edits.text,
19095                "Second modified\nNew third line\nFourth updated line"
19096            );
19097            assert_eq!(highlighted_edits.highlights.len(), 3);
19098            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19099            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19100            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19101            for highlight in &highlighted_edits.highlights {
19102                assert_eq!(
19103                    highlight.1.background_color,
19104                    Some(cx.theme().status().created_background)
19105                );
19106            }
19107        },
19108    )
19109    .await;
19110}
19111
19112#[gpui::test]
19113async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19114    init_test(cx, |_| {});
19115
19116    // Deletion
19117    assert_highlighted_edits(
19118        "Hello, world!",
19119        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19120        true,
19121        cx,
19122        |highlighted_edits, cx| {
19123            assert_eq!(highlighted_edits.text, "Hello, world!");
19124            assert_eq!(highlighted_edits.highlights.len(), 1);
19125            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19126            assert_eq!(
19127                highlighted_edits.highlights[0].1.background_color,
19128                Some(cx.theme().status().deleted_background)
19129            );
19130        },
19131    )
19132    .await;
19133
19134    // Insertion
19135    assert_highlighted_edits(
19136        "Hello, world!",
19137        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19138        true,
19139        cx,
19140        |highlighted_edits, cx| {
19141            assert_eq!(highlighted_edits.highlights.len(), 1);
19142            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19143            assert_eq!(
19144                highlighted_edits.highlights[0].1.background_color,
19145                Some(cx.theme().status().created_background)
19146            );
19147        },
19148    )
19149    .await;
19150}
19151
19152async fn assert_highlighted_edits(
19153    text: &str,
19154    edits: Vec<(Range<Point>, String)>,
19155    include_deletions: bool,
19156    cx: &mut TestAppContext,
19157    assertion_fn: impl Fn(HighlightedText, &App),
19158) {
19159    let window = cx.add_window(|window, cx| {
19160        let buffer = MultiBuffer::build_simple(text, cx);
19161        Editor::new(EditorMode::full(), buffer, None, window, cx)
19162    });
19163    let cx = &mut VisualTestContext::from_window(*window, cx);
19164
19165    let (buffer, snapshot) = window
19166        .update(cx, |editor, _window, cx| {
19167            (
19168                editor.buffer().clone(),
19169                editor.buffer().read(cx).snapshot(cx),
19170            )
19171        })
19172        .unwrap();
19173
19174    let edits = edits
19175        .into_iter()
19176        .map(|(range, edit)| {
19177            (
19178                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19179                edit,
19180            )
19181        })
19182        .collect::<Vec<_>>();
19183
19184    let text_anchor_edits = edits
19185        .clone()
19186        .into_iter()
19187        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19188        .collect::<Vec<_>>();
19189
19190    let edit_preview = window
19191        .update(cx, |_, _window, cx| {
19192            buffer
19193                .read(cx)
19194                .as_singleton()
19195                .unwrap()
19196                .read(cx)
19197                .preview_edits(text_anchor_edits.into(), cx)
19198        })
19199        .unwrap()
19200        .await;
19201
19202    cx.update(|_window, cx| {
19203        let highlighted_edits = inline_completion_edit_text(
19204            &snapshot.as_singleton().unwrap().2,
19205            &edits,
19206            &edit_preview,
19207            include_deletions,
19208            cx,
19209        );
19210        assertion_fn(highlighted_edits, cx)
19211    });
19212}
19213
19214#[track_caller]
19215fn assert_breakpoint(
19216    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19217    path: &Arc<Path>,
19218    expected: Vec<(u32, Breakpoint)>,
19219) {
19220    if expected.len() == 0usize {
19221        assert!(!breakpoints.contains_key(path), "{}", path.display());
19222    } else {
19223        let mut breakpoint = breakpoints
19224            .get(path)
19225            .unwrap()
19226            .into_iter()
19227            .map(|breakpoint| {
19228                (
19229                    breakpoint.row,
19230                    Breakpoint {
19231                        message: breakpoint.message.clone(),
19232                        state: breakpoint.state,
19233                        condition: breakpoint.condition.clone(),
19234                        hit_condition: breakpoint.hit_condition.clone(),
19235                    },
19236                )
19237            })
19238            .collect::<Vec<_>>();
19239
19240        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19241
19242        assert_eq!(expected, breakpoint);
19243    }
19244}
19245
19246fn add_log_breakpoint_at_cursor(
19247    editor: &mut Editor,
19248    log_message: &str,
19249    window: &mut Window,
19250    cx: &mut Context<Editor>,
19251) {
19252    let (anchor, bp) = editor
19253        .breakpoints_at_cursors(window, cx)
19254        .first()
19255        .and_then(|(anchor, bp)| {
19256            if let Some(bp) = bp {
19257                Some((*anchor, bp.clone()))
19258            } else {
19259                None
19260            }
19261        })
19262        .unwrap_or_else(|| {
19263            let cursor_position: Point = editor.selections.newest(cx).head();
19264
19265            let breakpoint_position = editor
19266                .snapshot(window, cx)
19267                .display_snapshot
19268                .buffer_snapshot
19269                .anchor_before(Point::new(cursor_position.row, 0));
19270
19271            (breakpoint_position, Breakpoint::new_log(&log_message))
19272        });
19273
19274    editor.edit_breakpoint_at_anchor(
19275        anchor,
19276        bp,
19277        BreakpointEditAction::EditLogMessage(log_message.into()),
19278        cx,
19279    );
19280}
19281
19282#[gpui::test]
19283async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19284    init_test(cx, |_| {});
19285
19286    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19287    let fs = FakeFs::new(cx.executor());
19288    fs.insert_tree(
19289        path!("/a"),
19290        json!({
19291            "main.rs": sample_text,
19292        }),
19293    )
19294    .await;
19295    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19296    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19297    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19298
19299    let fs = FakeFs::new(cx.executor());
19300    fs.insert_tree(
19301        path!("/a"),
19302        json!({
19303            "main.rs": sample_text,
19304        }),
19305    )
19306    .await;
19307    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19308    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19309    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19310    let worktree_id = workspace
19311        .update(cx, |workspace, _window, cx| {
19312            workspace.project().update(cx, |project, cx| {
19313                project.worktrees(cx).next().unwrap().read(cx).id()
19314            })
19315        })
19316        .unwrap();
19317
19318    let buffer = project
19319        .update(cx, |project, cx| {
19320            project.open_buffer((worktree_id, "main.rs"), cx)
19321        })
19322        .await
19323        .unwrap();
19324
19325    let (editor, cx) = cx.add_window_view(|window, cx| {
19326        Editor::new(
19327            EditorMode::full(),
19328            MultiBuffer::build_from_buffer(buffer, cx),
19329            Some(project.clone()),
19330            window,
19331            cx,
19332        )
19333    });
19334
19335    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19336    let abs_path = project.read_with(cx, |project, cx| {
19337        project
19338            .absolute_path(&project_path, cx)
19339            .map(|path_buf| Arc::from(path_buf.to_owned()))
19340            .unwrap()
19341    });
19342
19343    // assert we can add breakpoint on the first line
19344    editor.update_in(cx, |editor, window, cx| {
19345        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19346        editor.move_to_end(&MoveToEnd, window, cx);
19347        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19348    });
19349
19350    let breakpoints = editor.update(cx, |editor, cx| {
19351        editor
19352            .breakpoint_store()
19353            .as_ref()
19354            .unwrap()
19355            .read(cx)
19356            .all_source_breakpoints(cx)
19357            .clone()
19358    });
19359
19360    assert_eq!(1, breakpoints.len());
19361    assert_breakpoint(
19362        &breakpoints,
19363        &abs_path,
19364        vec![
19365            (0, Breakpoint::new_standard()),
19366            (3, Breakpoint::new_standard()),
19367        ],
19368    );
19369
19370    editor.update_in(cx, |editor, window, cx| {
19371        editor.move_to_beginning(&MoveToBeginning, window, cx);
19372        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19373    });
19374
19375    let breakpoints = editor.update(cx, |editor, cx| {
19376        editor
19377            .breakpoint_store()
19378            .as_ref()
19379            .unwrap()
19380            .read(cx)
19381            .all_source_breakpoints(cx)
19382            .clone()
19383    });
19384
19385    assert_eq!(1, breakpoints.len());
19386    assert_breakpoint(
19387        &breakpoints,
19388        &abs_path,
19389        vec![(3, Breakpoint::new_standard())],
19390    );
19391
19392    editor.update_in(cx, |editor, window, cx| {
19393        editor.move_to_end(&MoveToEnd, window, cx);
19394        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19395    });
19396
19397    let breakpoints = editor.update(cx, |editor, cx| {
19398        editor
19399            .breakpoint_store()
19400            .as_ref()
19401            .unwrap()
19402            .read(cx)
19403            .all_source_breakpoints(cx)
19404            .clone()
19405    });
19406
19407    assert_eq!(0, breakpoints.len());
19408    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19409}
19410
19411#[gpui::test]
19412async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19413    init_test(cx, |_| {});
19414
19415    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19416
19417    let fs = FakeFs::new(cx.executor());
19418    fs.insert_tree(
19419        path!("/a"),
19420        json!({
19421            "main.rs": sample_text,
19422        }),
19423    )
19424    .await;
19425    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19426    let (workspace, cx) =
19427        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19428
19429    let worktree_id = workspace.update(cx, |workspace, cx| {
19430        workspace.project().update(cx, |project, cx| {
19431            project.worktrees(cx).next().unwrap().read(cx).id()
19432        })
19433    });
19434
19435    let buffer = project
19436        .update(cx, |project, cx| {
19437            project.open_buffer((worktree_id, "main.rs"), cx)
19438        })
19439        .await
19440        .unwrap();
19441
19442    let (editor, cx) = cx.add_window_view(|window, cx| {
19443        Editor::new(
19444            EditorMode::full(),
19445            MultiBuffer::build_from_buffer(buffer, cx),
19446            Some(project.clone()),
19447            window,
19448            cx,
19449        )
19450    });
19451
19452    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19453    let abs_path = project.read_with(cx, |project, cx| {
19454        project
19455            .absolute_path(&project_path, cx)
19456            .map(|path_buf| Arc::from(path_buf.to_owned()))
19457            .unwrap()
19458    });
19459
19460    editor.update_in(cx, |editor, window, cx| {
19461        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19462    });
19463
19464    let breakpoints = editor.update(cx, |editor, cx| {
19465        editor
19466            .breakpoint_store()
19467            .as_ref()
19468            .unwrap()
19469            .read(cx)
19470            .all_source_breakpoints(cx)
19471            .clone()
19472    });
19473
19474    assert_breakpoint(
19475        &breakpoints,
19476        &abs_path,
19477        vec![(0, Breakpoint::new_log("hello world"))],
19478    );
19479
19480    // Removing a log message from a log breakpoint should remove it
19481    editor.update_in(cx, |editor, window, cx| {
19482        add_log_breakpoint_at_cursor(editor, "", window, cx);
19483    });
19484
19485    let breakpoints = editor.update(cx, |editor, cx| {
19486        editor
19487            .breakpoint_store()
19488            .as_ref()
19489            .unwrap()
19490            .read(cx)
19491            .all_source_breakpoints(cx)
19492            .clone()
19493    });
19494
19495    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19496
19497    editor.update_in(cx, |editor, window, cx| {
19498        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19499        editor.move_to_end(&MoveToEnd, window, cx);
19500        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19501        // Not adding a log message to a standard breakpoint shouldn't remove it
19502        add_log_breakpoint_at_cursor(editor, "", window, cx);
19503    });
19504
19505    let breakpoints = editor.update(cx, |editor, cx| {
19506        editor
19507            .breakpoint_store()
19508            .as_ref()
19509            .unwrap()
19510            .read(cx)
19511            .all_source_breakpoints(cx)
19512            .clone()
19513    });
19514
19515    assert_breakpoint(
19516        &breakpoints,
19517        &abs_path,
19518        vec![
19519            (0, Breakpoint::new_standard()),
19520            (3, Breakpoint::new_standard()),
19521        ],
19522    );
19523
19524    editor.update_in(cx, |editor, window, cx| {
19525        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19526    });
19527
19528    let breakpoints = editor.update(cx, |editor, cx| {
19529        editor
19530            .breakpoint_store()
19531            .as_ref()
19532            .unwrap()
19533            .read(cx)
19534            .all_source_breakpoints(cx)
19535            .clone()
19536    });
19537
19538    assert_breakpoint(
19539        &breakpoints,
19540        &abs_path,
19541        vec![
19542            (0, Breakpoint::new_standard()),
19543            (3, Breakpoint::new_log("hello world")),
19544        ],
19545    );
19546
19547    editor.update_in(cx, |editor, window, cx| {
19548        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19549    });
19550
19551    let breakpoints = editor.update(cx, |editor, cx| {
19552        editor
19553            .breakpoint_store()
19554            .as_ref()
19555            .unwrap()
19556            .read(cx)
19557            .all_source_breakpoints(cx)
19558            .clone()
19559    });
19560
19561    assert_breakpoint(
19562        &breakpoints,
19563        &abs_path,
19564        vec![
19565            (0, Breakpoint::new_standard()),
19566            (3, Breakpoint::new_log("hello Earth!!")),
19567        ],
19568    );
19569}
19570
19571/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19572/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19573/// or when breakpoints were placed out of order. This tests for a regression too
19574#[gpui::test]
19575async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19576    init_test(cx, |_| {});
19577
19578    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19579    let fs = FakeFs::new(cx.executor());
19580    fs.insert_tree(
19581        path!("/a"),
19582        json!({
19583            "main.rs": sample_text,
19584        }),
19585    )
19586    .await;
19587    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19588    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19589    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19590
19591    let fs = FakeFs::new(cx.executor());
19592    fs.insert_tree(
19593        path!("/a"),
19594        json!({
19595            "main.rs": sample_text,
19596        }),
19597    )
19598    .await;
19599    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19600    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19601    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19602    let worktree_id = workspace
19603        .update(cx, |workspace, _window, cx| {
19604            workspace.project().update(cx, |project, cx| {
19605                project.worktrees(cx).next().unwrap().read(cx).id()
19606            })
19607        })
19608        .unwrap();
19609
19610    let buffer = project
19611        .update(cx, |project, cx| {
19612            project.open_buffer((worktree_id, "main.rs"), cx)
19613        })
19614        .await
19615        .unwrap();
19616
19617    let (editor, cx) = cx.add_window_view(|window, cx| {
19618        Editor::new(
19619            EditorMode::full(),
19620            MultiBuffer::build_from_buffer(buffer, cx),
19621            Some(project.clone()),
19622            window,
19623            cx,
19624        )
19625    });
19626
19627    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19628    let abs_path = project.read_with(cx, |project, cx| {
19629        project
19630            .absolute_path(&project_path, cx)
19631            .map(|path_buf| Arc::from(path_buf.to_owned()))
19632            .unwrap()
19633    });
19634
19635    // assert we can add breakpoint on the first line
19636    editor.update_in(cx, |editor, window, cx| {
19637        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19638        editor.move_to_end(&MoveToEnd, window, cx);
19639        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19640        editor.move_up(&MoveUp, window, cx);
19641        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19642    });
19643
19644    let breakpoints = editor.update(cx, |editor, cx| {
19645        editor
19646            .breakpoint_store()
19647            .as_ref()
19648            .unwrap()
19649            .read(cx)
19650            .all_source_breakpoints(cx)
19651            .clone()
19652    });
19653
19654    assert_eq!(1, breakpoints.len());
19655    assert_breakpoint(
19656        &breakpoints,
19657        &abs_path,
19658        vec![
19659            (0, Breakpoint::new_standard()),
19660            (2, Breakpoint::new_standard()),
19661            (3, Breakpoint::new_standard()),
19662        ],
19663    );
19664
19665    editor.update_in(cx, |editor, window, cx| {
19666        editor.move_to_beginning(&MoveToBeginning, window, cx);
19667        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19668        editor.move_to_end(&MoveToEnd, window, cx);
19669        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19670        // Disabling a breakpoint that doesn't exist should do nothing
19671        editor.move_up(&MoveUp, window, cx);
19672        editor.move_up(&MoveUp, window, cx);
19673        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19674    });
19675
19676    let breakpoints = editor.update(cx, |editor, cx| {
19677        editor
19678            .breakpoint_store()
19679            .as_ref()
19680            .unwrap()
19681            .read(cx)
19682            .all_source_breakpoints(cx)
19683            .clone()
19684    });
19685
19686    let disable_breakpoint = {
19687        let mut bp = Breakpoint::new_standard();
19688        bp.state = BreakpointState::Disabled;
19689        bp
19690    };
19691
19692    assert_eq!(1, breakpoints.len());
19693    assert_breakpoint(
19694        &breakpoints,
19695        &abs_path,
19696        vec![
19697            (0, disable_breakpoint.clone()),
19698            (2, Breakpoint::new_standard()),
19699            (3, disable_breakpoint.clone()),
19700        ],
19701    );
19702
19703    editor.update_in(cx, |editor, window, cx| {
19704        editor.move_to_beginning(&MoveToBeginning, window, cx);
19705        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19706        editor.move_to_end(&MoveToEnd, window, cx);
19707        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19708        editor.move_up(&MoveUp, window, cx);
19709        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19710    });
19711
19712    let breakpoints = editor.update(cx, |editor, cx| {
19713        editor
19714            .breakpoint_store()
19715            .as_ref()
19716            .unwrap()
19717            .read(cx)
19718            .all_source_breakpoints(cx)
19719            .clone()
19720    });
19721
19722    assert_eq!(1, breakpoints.len());
19723    assert_breakpoint(
19724        &breakpoints,
19725        &abs_path,
19726        vec![
19727            (0, Breakpoint::new_standard()),
19728            (2, disable_breakpoint),
19729            (3, Breakpoint::new_standard()),
19730        ],
19731    );
19732}
19733
19734#[gpui::test]
19735async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19736    init_test(cx, |_| {});
19737    let capabilities = lsp::ServerCapabilities {
19738        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19739            prepare_provider: Some(true),
19740            work_done_progress_options: Default::default(),
19741        })),
19742        ..Default::default()
19743    };
19744    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19745
19746    cx.set_state(indoc! {"
19747        struct Fˇoo {}
19748    "});
19749
19750    cx.update_editor(|editor, _, cx| {
19751        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19752        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19753        editor.highlight_background::<DocumentHighlightRead>(
19754            &[highlight_range],
19755            |c| c.editor_document_highlight_read_background,
19756            cx,
19757        );
19758    });
19759
19760    let mut prepare_rename_handler = cx
19761        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19762            move |_, _, _| async move {
19763                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19764                    start: lsp::Position {
19765                        line: 0,
19766                        character: 7,
19767                    },
19768                    end: lsp::Position {
19769                        line: 0,
19770                        character: 10,
19771                    },
19772                })))
19773            },
19774        );
19775    let prepare_rename_task = cx
19776        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19777        .expect("Prepare rename was not started");
19778    prepare_rename_handler.next().await.unwrap();
19779    prepare_rename_task.await.expect("Prepare rename failed");
19780
19781    let mut rename_handler =
19782        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19783            let edit = lsp::TextEdit {
19784                range: lsp::Range {
19785                    start: lsp::Position {
19786                        line: 0,
19787                        character: 7,
19788                    },
19789                    end: lsp::Position {
19790                        line: 0,
19791                        character: 10,
19792                    },
19793                },
19794                new_text: "FooRenamed".to_string(),
19795            };
19796            Ok(Some(lsp::WorkspaceEdit::new(
19797                // Specify the same edit twice
19798                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19799            )))
19800        });
19801    let rename_task = cx
19802        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19803        .expect("Confirm rename was not started");
19804    rename_handler.next().await.unwrap();
19805    rename_task.await.expect("Confirm rename failed");
19806    cx.run_until_parked();
19807
19808    // Despite two edits, only one is actually applied as those are identical
19809    cx.assert_editor_state(indoc! {"
19810        struct FooRenamedˇ {}
19811    "});
19812}
19813
19814#[gpui::test]
19815async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19816    init_test(cx, |_| {});
19817    // These capabilities indicate that the server does not support prepare rename.
19818    let capabilities = lsp::ServerCapabilities {
19819        rename_provider: Some(lsp::OneOf::Left(true)),
19820        ..Default::default()
19821    };
19822    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19823
19824    cx.set_state(indoc! {"
19825        struct Fˇoo {}
19826    "});
19827
19828    cx.update_editor(|editor, _window, cx| {
19829        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19830        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19831        editor.highlight_background::<DocumentHighlightRead>(
19832            &[highlight_range],
19833            |c| c.editor_document_highlight_read_background,
19834            cx,
19835        );
19836    });
19837
19838    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19839        .expect("Prepare rename was not started")
19840        .await
19841        .expect("Prepare rename failed");
19842
19843    let mut rename_handler =
19844        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19845            let edit = lsp::TextEdit {
19846                range: lsp::Range {
19847                    start: lsp::Position {
19848                        line: 0,
19849                        character: 7,
19850                    },
19851                    end: lsp::Position {
19852                        line: 0,
19853                        character: 10,
19854                    },
19855                },
19856                new_text: "FooRenamed".to_string(),
19857            };
19858            Ok(Some(lsp::WorkspaceEdit::new(
19859                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19860            )))
19861        });
19862    let rename_task = cx
19863        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19864        .expect("Confirm rename was not started");
19865    rename_handler.next().await.unwrap();
19866    rename_task.await.expect("Confirm rename failed");
19867    cx.run_until_parked();
19868
19869    // Correct range is renamed, as `surrounding_word` is used to find it.
19870    cx.assert_editor_state(indoc! {"
19871        struct FooRenamedˇ {}
19872    "});
19873}
19874
19875#[gpui::test]
19876async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19877    init_test(cx, |_| {});
19878    let mut cx = EditorTestContext::new(cx).await;
19879
19880    let language = Arc::new(
19881        Language::new(
19882            LanguageConfig::default(),
19883            Some(tree_sitter_html::LANGUAGE.into()),
19884        )
19885        .with_brackets_query(
19886            r#"
19887            ("<" @open "/>" @close)
19888            ("</" @open ">" @close)
19889            ("<" @open ">" @close)
19890            ("\"" @open "\"" @close)
19891            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19892        "#,
19893        )
19894        .unwrap(),
19895    );
19896    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19897
19898    cx.set_state(indoc! {"
19899        <span>ˇ</span>
19900    "});
19901    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19902    cx.assert_editor_state(indoc! {"
19903        <span>
19904        ˇ
19905        </span>
19906    "});
19907
19908    cx.set_state(indoc! {"
19909        <span><span></span>ˇ</span>
19910    "});
19911    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19912    cx.assert_editor_state(indoc! {"
19913        <span><span></span>
19914        ˇ</span>
19915    "});
19916
19917    cx.set_state(indoc! {"
19918        <span>ˇ
19919        </span>
19920    "});
19921    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19922    cx.assert_editor_state(indoc! {"
19923        <span>
19924        ˇ
19925        </span>
19926    "});
19927}
19928
19929#[gpui::test(iterations = 10)]
19930async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19931    init_test(cx, |_| {});
19932
19933    let fs = FakeFs::new(cx.executor());
19934    fs.insert_tree(
19935        path!("/dir"),
19936        json!({
19937            "a.ts": "a",
19938        }),
19939    )
19940    .await;
19941
19942    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19944    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19945
19946    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19947    language_registry.add(Arc::new(Language::new(
19948        LanguageConfig {
19949            name: "TypeScript".into(),
19950            matcher: LanguageMatcher {
19951                path_suffixes: vec!["ts".to_string()],
19952                ..Default::default()
19953            },
19954            ..Default::default()
19955        },
19956        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19957    )));
19958    let mut fake_language_servers = language_registry.register_fake_lsp(
19959        "TypeScript",
19960        FakeLspAdapter {
19961            capabilities: lsp::ServerCapabilities {
19962                code_lens_provider: Some(lsp::CodeLensOptions {
19963                    resolve_provider: Some(true),
19964                }),
19965                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19966                    commands: vec!["_the/command".to_string()],
19967                    ..lsp::ExecuteCommandOptions::default()
19968                }),
19969                ..lsp::ServerCapabilities::default()
19970            },
19971            ..FakeLspAdapter::default()
19972        },
19973    );
19974
19975    let (buffer, _handle) = project
19976        .update(cx, |p, cx| {
19977            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19978        })
19979        .await
19980        .unwrap();
19981    cx.executor().run_until_parked();
19982
19983    let fake_server = fake_language_servers.next().await.unwrap();
19984
19985    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19986    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19987    drop(buffer_snapshot);
19988    let actions = cx
19989        .update_window(*workspace, |_, window, cx| {
19990            project.code_actions(&buffer, anchor..anchor, window, cx)
19991        })
19992        .unwrap();
19993
19994    fake_server
19995        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19996            Ok(Some(vec![
19997                lsp::CodeLens {
19998                    range: lsp::Range::default(),
19999                    command: Some(lsp::Command {
20000                        title: "Code lens command".to_owned(),
20001                        command: "_the/command".to_owned(),
20002                        arguments: None,
20003                    }),
20004                    data: None,
20005                },
20006                lsp::CodeLens {
20007                    range: lsp::Range::default(),
20008                    command: Some(lsp::Command {
20009                        title: "Command not in capabilities".to_owned(),
20010                        command: "not in capabilities".to_owned(),
20011                        arguments: None,
20012                    }),
20013                    data: None,
20014                },
20015                lsp::CodeLens {
20016                    range: lsp::Range {
20017                        start: lsp::Position {
20018                            line: 1,
20019                            character: 1,
20020                        },
20021                        end: lsp::Position {
20022                            line: 1,
20023                            character: 1,
20024                        },
20025                    },
20026                    command: Some(lsp::Command {
20027                        title: "Command not in range".to_owned(),
20028                        command: "_the/command".to_owned(),
20029                        arguments: None,
20030                    }),
20031                    data: None,
20032                },
20033            ]))
20034        })
20035        .next()
20036        .await;
20037
20038    let actions = actions.await.unwrap();
20039    assert_eq!(
20040        actions.len(),
20041        1,
20042        "Should have only one valid action for the 0..0 range"
20043    );
20044    let action = actions[0].clone();
20045    let apply = project.update(cx, |project, cx| {
20046        project.apply_code_action(buffer.clone(), action, true, cx)
20047    });
20048
20049    // Resolving the code action does not populate its edits. In absence of
20050    // edits, we must execute the given command.
20051    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20052        |mut lens, _| async move {
20053            let lens_command = lens.command.as_mut().expect("should have a command");
20054            assert_eq!(lens_command.title, "Code lens command");
20055            lens_command.arguments = Some(vec![json!("the-argument")]);
20056            Ok(lens)
20057        },
20058    );
20059
20060    // While executing the command, the language server sends the editor
20061    // a `workspaceEdit` request.
20062    fake_server
20063        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20064            let fake = fake_server.clone();
20065            move |params, _| {
20066                assert_eq!(params.command, "_the/command");
20067                let fake = fake.clone();
20068                async move {
20069                    fake.server
20070                        .request::<lsp::request::ApplyWorkspaceEdit>(
20071                            lsp::ApplyWorkspaceEditParams {
20072                                label: None,
20073                                edit: lsp::WorkspaceEdit {
20074                                    changes: Some(
20075                                        [(
20076                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20077                                            vec![lsp::TextEdit {
20078                                                range: lsp::Range::new(
20079                                                    lsp::Position::new(0, 0),
20080                                                    lsp::Position::new(0, 0),
20081                                                ),
20082                                                new_text: "X".into(),
20083                                            }],
20084                                        )]
20085                                        .into_iter()
20086                                        .collect(),
20087                                    ),
20088                                    ..Default::default()
20089                                },
20090                            },
20091                        )
20092                        .await
20093                        .into_response()
20094                        .unwrap();
20095                    Ok(Some(json!(null)))
20096                }
20097            }
20098        })
20099        .next()
20100        .await;
20101
20102    // Applying the code lens command returns a project transaction containing the edits
20103    // sent by the language server in its `workspaceEdit` request.
20104    let transaction = apply.await.unwrap();
20105    assert!(transaction.0.contains_key(&buffer));
20106    buffer.update(cx, |buffer, cx| {
20107        assert_eq!(buffer.text(), "Xa");
20108        buffer.undo(cx);
20109        assert_eq!(buffer.text(), "a");
20110    });
20111}
20112
20113#[gpui::test]
20114async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20115    init_test(cx, |_| {});
20116
20117    let fs = FakeFs::new(cx.executor());
20118    let main_text = r#"fn main() {
20119println!("1");
20120println!("2");
20121println!("3");
20122println!("4");
20123println!("5");
20124}"#;
20125    let lib_text = "mod foo {}";
20126    fs.insert_tree(
20127        path!("/a"),
20128        json!({
20129            "lib.rs": lib_text,
20130            "main.rs": main_text,
20131        }),
20132    )
20133    .await;
20134
20135    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20136    let (workspace, cx) =
20137        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20138    let worktree_id = workspace.update(cx, |workspace, cx| {
20139        workspace.project().update(cx, |project, cx| {
20140            project.worktrees(cx).next().unwrap().read(cx).id()
20141        })
20142    });
20143
20144    let expected_ranges = vec![
20145        Point::new(0, 0)..Point::new(0, 0),
20146        Point::new(1, 0)..Point::new(1, 1),
20147        Point::new(2, 0)..Point::new(2, 2),
20148        Point::new(3, 0)..Point::new(3, 3),
20149    ];
20150
20151    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20152    let editor_1 = workspace
20153        .update_in(cx, |workspace, window, cx| {
20154            workspace.open_path(
20155                (worktree_id, "main.rs"),
20156                Some(pane_1.downgrade()),
20157                true,
20158                window,
20159                cx,
20160            )
20161        })
20162        .unwrap()
20163        .await
20164        .downcast::<Editor>()
20165        .unwrap();
20166    pane_1.update(cx, |pane, cx| {
20167        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20168        open_editor.update(cx, |editor, cx| {
20169            assert_eq!(
20170                editor.display_text(cx),
20171                main_text,
20172                "Original main.rs text on initial open",
20173            );
20174            assert_eq!(
20175                editor
20176                    .selections
20177                    .all::<Point>(cx)
20178                    .into_iter()
20179                    .map(|s| s.range())
20180                    .collect::<Vec<_>>(),
20181                vec![Point::zero()..Point::zero()],
20182                "Default selections on initial open",
20183            );
20184        })
20185    });
20186    editor_1.update_in(cx, |editor, window, cx| {
20187        editor.change_selections(None, window, cx, |s| {
20188            s.select_ranges(expected_ranges.clone());
20189        });
20190    });
20191
20192    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20193        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20194    });
20195    let editor_2 = workspace
20196        .update_in(cx, |workspace, window, cx| {
20197            workspace.open_path(
20198                (worktree_id, "main.rs"),
20199                Some(pane_2.downgrade()),
20200                true,
20201                window,
20202                cx,
20203            )
20204        })
20205        .unwrap()
20206        .await
20207        .downcast::<Editor>()
20208        .unwrap();
20209    pane_2.update(cx, |pane, cx| {
20210        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20211        open_editor.update(cx, |editor, cx| {
20212            assert_eq!(
20213                editor.display_text(cx),
20214                main_text,
20215                "Original main.rs text on initial open in another panel",
20216            );
20217            assert_eq!(
20218                editor
20219                    .selections
20220                    .all::<Point>(cx)
20221                    .into_iter()
20222                    .map(|s| s.range())
20223                    .collect::<Vec<_>>(),
20224                vec![Point::zero()..Point::zero()],
20225                "Default selections on initial open in another panel",
20226            );
20227        })
20228    });
20229
20230    editor_2.update_in(cx, |editor, window, cx| {
20231        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20232    });
20233
20234    let _other_editor_1 = workspace
20235        .update_in(cx, |workspace, window, cx| {
20236            workspace.open_path(
20237                (worktree_id, "lib.rs"),
20238                Some(pane_1.downgrade()),
20239                true,
20240                window,
20241                cx,
20242            )
20243        })
20244        .unwrap()
20245        .await
20246        .downcast::<Editor>()
20247        .unwrap();
20248    pane_1
20249        .update_in(cx, |pane, window, cx| {
20250            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20251        })
20252        .await
20253        .unwrap();
20254    drop(editor_1);
20255    pane_1.update(cx, |pane, cx| {
20256        pane.active_item()
20257            .unwrap()
20258            .downcast::<Editor>()
20259            .unwrap()
20260            .update(cx, |editor, cx| {
20261                assert_eq!(
20262                    editor.display_text(cx),
20263                    lib_text,
20264                    "Other file should be open and active",
20265                );
20266            });
20267        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20268    });
20269
20270    let _other_editor_2 = workspace
20271        .update_in(cx, |workspace, window, cx| {
20272            workspace.open_path(
20273                (worktree_id, "lib.rs"),
20274                Some(pane_2.downgrade()),
20275                true,
20276                window,
20277                cx,
20278            )
20279        })
20280        .unwrap()
20281        .await
20282        .downcast::<Editor>()
20283        .unwrap();
20284    pane_2
20285        .update_in(cx, |pane, window, cx| {
20286            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20287        })
20288        .await
20289        .unwrap();
20290    drop(editor_2);
20291    pane_2.update(cx, |pane, cx| {
20292        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20293        open_editor.update(cx, |editor, cx| {
20294            assert_eq!(
20295                editor.display_text(cx),
20296                lib_text,
20297                "Other file should be open and active in another panel too",
20298            );
20299        });
20300        assert_eq!(
20301            pane.items().count(),
20302            1,
20303            "No other editors should be open in another pane",
20304        );
20305    });
20306
20307    let _editor_1_reopened = workspace
20308        .update_in(cx, |workspace, window, cx| {
20309            workspace.open_path(
20310                (worktree_id, "main.rs"),
20311                Some(pane_1.downgrade()),
20312                true,
20313                window,
20314                cx,
20315            )
20316        })
20317        .unwrap()
20318        .await
20319        .downcast::<Editor>()
20320        .unwrap();
20321    let _editor_2_reopened = workspace
20322        .update_in(cx, |workspace, window, cx| {
20323            workspace.open_path(
20324                (worktree_id, "main.rs"),
20325                Some(pane_2.downgrade()),
20326                true,
20327                window,
20328                cx,
20329            )
20330        })
20331        .unwrap()
20332        .await
20333        .downcast::<Editor>()
20334        .unwrap();
20335    pane_1.update(cx, |pane, cx| {
20336        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20337        open_editor.update(cx, |editor, cx| {
20338            assert_eq!(
20339                editor.display_text(cx),
20340                main_text,
20341                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20342            );
20343            assert_eq!(
20344                editor
20345                    .selections
20346                    .all::<Point>(cx)
20347                    .into_iter()
20348                    .map(|s| s.range())
20349                    .collect::<Vec<_>>(),
20350                expected_ranges,
20351                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20352            );
20353        })
20354    });
20355    pane_2.update(cx, |pane, cx| {
20356        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20357        open_editor.update(cx, |editor, cx| {
20358            assert_eq!(
20359                editor.display_text(cx),
20360                r#"fn main() {
20361⋯rintln!("1");
20362⋯intln!("2");
20363⋯ntln!("3");
20364println!("4");
20365println!("5");
20366}"#,
20367                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20368            );
20369            assert_eq!(
20370                editor
20371                    .selections
20372                    .all::<Point>(cx)
20373                    .into_iter()
20374                    .map(|s| s.range())
20375                    .collect::<Vec<_>>(),
20376                vec![Point::zero()..Point::zero()],
20377                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20378            );
20379        })
20380    });
20381}
20382
20383#[gpui::test]
20384async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20385    init_test(cx, |_| {});
20386
20387    let fs = FakeFs::new(cx.executor());
20388    let main_text = r#"fn main() {
20389println!("1");
20390println!("2");
20391println!("3");
20392println!("4");
20393println!("5");
20394}"#;
20395    let lib_text = "mod foo {}";
20396    fs.insert_tree(
20397        path!("/a"),
20398        json!({
20399            "lib.rs": lib_text,
20400            "main.rs": main_text,
20401        }),
20402    )
20403    .await;
20404
20405    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20406    let (workspace, cx) =
20407        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20408    let worktree_id = workspace.update(cx, |workspace, cx| {
20409        workspace.project().update(cx, |project, cx| {
20410            project.worktrees(cx).next().unwrap().read(cx).id()
20411        })
20412    });
20413
20414    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20415    let editor = workspace
20416        .update_in(cx, |workspace, window, cx| {
20417            workspace.open_path(
20418                (worktree_id, "main.rs"),
20419                Some(pane.downgrade()),
20420                true,
20421                window,
20422                cx,
20423            )
20424        })
20425        .unwrap()
20426        .await
20427        .downcast::<Editor>()
20428        .unwrap();
20429    pane.update(cx, |pane, cx| {
20430        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20431        open_editor.update(cx, |editor, cx| {
20432            assert_eq!(
20433                editor.display_text(cx),
20434                main_text,
20435                "Original main.rs text on initial open",
20436            );
20437        })
20438    });
20439    editor.update_in(cx, |editor, window, cx| {
20440        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20441    });
20442
20443    cx.update_global(|store: &mut SettingsStore, cx| {
20444        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20445            s.restore_on_file_reopen = Some(false);
20446        });
20447    });
20448    editor.update_in(cx, |editor, window, cx| {
20449        editor.fold_ranges(
20450            vec![
20451                Point::new(1, 0)..Point::new(1, 1),
20452                Point::new(2, 0)..Point::new(2, 2),
20453                Point::new(3, 0)..Point::new(3, 3),
20454            ],
20455            false,
20456            window,
20457            cx,
20458        );
20459    });
20460    pane.update_in(cx, |pane, window, cx| {
20461        pane.close_all_items(&CloseAllItems::default(), window, cx)
20462    })
20463    .await
20464    .unwrap();
20465    pane.update(cx, |pane, _| {
20466        assert!(pane.active_item().is_none());
20467    });
20468    cx.update_global(|store: &mut SettingsStore, cx| {
20469        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20470            s.restore_on_file_reopen = Some(true);
20471        });
20472    });
20473
20474    let _editor_reopened = workspace
20475        .update_in(cx, |workspace, window, cx| {
20476            workspace.open_path(
20477                (worktree_id, "main.rs"),
20478                Some(pane.downgrade()),
20479                true,
20480                window,
20481                cx,
20482            )
20483        })
20484        .unwrap()
20485        .await
20486        .downcast::<Editor>()
20487        .unwrap();
20488    pane.update(cx, |pane, cx| {
20489        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20490        open_editor.update(cx, |editor, cx| {
20491            assert_eq!(
20492                editor.display_text(cx),
20493                main_text,
20494                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20495            );
20496        })
20497    });
20498}
20499
20500#[gpui::test]
20501async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20502    struct EmptyModalView {
20503        focus_handle: gpui::FocusHandle,
20504    }
20505    impl EventEmitter<DismissEvent> for EmptyModalView {}
20506    impl Render for EmptyModalView {
20507        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20508            div()
20509        }
20510    }
20511    impl Focusable for EmptyModalView {
20512        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20513            self.focus_handle.clone()
20514        }
20515    }
20516    impl workspace::ModalView for EmptyModalView {}
20517    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20518        EmptyModalView {
20519            focus_handle: cx.focus_handle(),
20520        }
20521    }
20522
20523    init_test(cx, |_| {});
20524
20525    let fs = FakeFs::new(cx.executor());
20526    let project = Project::test(fs, [], cx).await;
20527    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20528    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20529    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20530    let editor = cx.new_window_entity(|window, cx| {
20531        Editor::new(
20532            EditorMode::full(),
20533            buffer,
20534            Some(project.clone()),
20535            window,
20536            cx,
20537        )
20538    });
20539    workspace
20540        .update(cx, |workspace, window, cx| {
20541            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20542        })
20543        .unwrap();
20544    editor.update_in(cx, |editor, window, cx| {
20545        editor.open_context_menu(&OpenContextMenu, window, cx);
20546        assert!(editor.mouse_context_menu.is_some());
20547    });
20548    workspace
20549        .update(cx, |workspace, window, cx| {
20550            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20551        })
20552        .unwrap();
20553    cx.read(|cx| {
20554        assert!(editor.read(cx).mouse_context_menu.is_none());
20555    });
20556}
20557
20558#[gpui::test]
20559async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20560    init_test(cx, |_| {});
20561
20562    let fs = FakeFs::new(cx.executor());
20563    fs.insert_file(path!("/file.html"), Default::default())
20564        .await;
20565
20566    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20567
20568    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20569    let html_language = Arc::new(Language::new(
20570        LanguageConfig {
20571            name: "HTML".into(),
20572            matcher: LanguageMatcher {
20573                path_suffixes: vec!["html".to_string()],
20574                ..LanguageMatcher::default()
20575            },
20576            brackets: BracketPairConfig {
20577                pairs: vec![BracketPair {
20578                    start: "<".into(),
20579                    end: ">".into(),
20580                    close: true,
20581                    ..Default::default()
20582                }],
20583                ..Default::default()
20584            },
20585            ..Default::default()
20586        },
20587        Some(tree_sitter_html::LANGUAGE.into()),
20588    ));
20589    language_registry.add(html_language);
20590    let mut fake_servers = language_registry.register_fake_lsp(
20591        "HTML",
20592        FakeLspAdapter {
20593            capabilities: lsp::ServerCapabilities {
20594                completion_provider: Some(lsp::CompletionOptions {
20595                    resolve_provider: Some(true),
20596                    ..Default::default()
20597                }),
20598                ..Default::default()
20599            },
20600            ..Default::default()
20601        },
20602    );
20603
20604    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20605    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20606
20607    let worktree_id = workspace
20608        .update(cx, |workspace, _window, cx| {
20609            workspace.project().update(cx, |project, cx| {
20610                project.worktrees(cx).next().unwrap().read(cx).id()
20611            })
20612        })
20613        .unwrap();
20614    project
20615        .update(cx, |project, cx| {
20616            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20617        })
20618        .await
20619        .unwrap();
20620    let editor = workspace
20621        .update(cx, |workspace, window, cx| {
20622            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20623        })
20624        .unwrap()
20625        .await
20626        .unwrap()
20627        .downcast::<Editor>()
20628        .unwrap();
20629
20630    let fake_server = fake_servers.next().await.unwrap();
20631    editor.update_in(cx, |editor, window, cx| {
20632        editor.set_text("<ad></ad>", window, cx);
20633        editor.change_selections(None, window, cx, |selections| {
20634            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20635        });
20636        let Some((buffer, _)) = editor
20637            .buffer
20638            .read(cx)
20639            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20640        else {
20641            panic!("Failed to get buffer for selection position");
20642        };
20643        let buffer = buffer.read(cx);
20644        let buffer_id = buffer.remote_id();
20645        let opening_range =
20646            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20647        let closing_range =
20648            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20649        let mut linked_ranges = HashMap::default();
20650        linked_ranges.insert(
20651            buffer_id,
20652            vec![(opening_range.clone(), vec![closing_range.clone()])],
20653        );
20654        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20655    });
20656    let mut completion_handle =
20657        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20658            Ok(Some(lsp::CompletionResponse::Array(vec![
20659                lsp::CompletionItem {
20660                    label: "head".to_string(),
20661                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20662                        lsp::InsertReplaceEdit {
20663                            new_text: "head".to_string(),
20664                            insert: lsp::Range::new(
20665                                lsp::Position::new(0, 1),
20666                                lsp::Position::new(0, 3),
20667                            ),
20668                            replace: lsp::Range::new(
20669                                lsp::Position::new(0, 1),
20670                                lsp::Position::new(0, 3),
20671                            ),
20672                        },
20673                    )),
20674                    ..Default::default()
20675                },
20676            ])))
20677        });
20678    editor.update_in(cx, |editor, window, cx| {
20679        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20680    });
20681    cx.run_until_parked();
20682    completion_handle.next().await.unwrap();
20683    editor.update(cx, |editor, _| {
20684        assert!(
20685            editor.context_menu_visible(),
20686            "Completion menu should be visible"
20687        );
20688    });
20689    editor.update_in(cx, |editor, window, cx| {
20690        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20691    });
20692    cx.executor().run_until_parked();
20693    editor.update(cx, |editor, cx| {
20694        assert_eq!(editor.text(cx), "<head></head>");
20695    });
20696}
20697
20698#[gpui::test]
20699async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20700    init_test(cx, |_| {});
20701
20702    let fs = FakeFs::new(cx.executor());
20703    fs.insert_tree(
20704        path!("/root"),
20705        json!({
20706            "a": {
20707                "main.rs": "fn main() {}",
20708            },
20709            "foo": {
20710                "bar": {
20711                    "external_file.rs": "pub mod external {}",
20712                }
20713            }
20714        }),
20715    )
20716    .await;
20717
20718    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20719    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20720    language_registry.add(rust_lang());
20721    let _fake_servers = language_registry.register_fake_lsp(
20722        "Rust",
20723        FakeLspAdapter {
20724            ..FakeLspAdapter::default()
20725        },
20726    );
20727    let (workspace, cx) =
20728        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20729    let worktree_id = workspace.update(cx, |workspace, cx| {
20730        workspace.project().update(cx, |project, cx| {
20731            project.worktrees(cx).next().unwrap().read(cx).id()
20732        })
20733    });
20734
20735    let assert_language_servers_count =
20736        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20737            project.update(cx, |project, cx| {
20738                let current = project
20739                    .lsp_store()
20740                    .read(cx)
20741                    .as_local()
20742                    .unwrap()
20743                    .language_servers
20744                    .len();
20745                assert_eq!(expected, current, "{context}");
20746            });
20747        };
20748
20749    assert_language_servers_count(
20750        0,
20751        "No servers should be running before any file is open",
20752        cx,
20753    );
20754    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20755    let main_editor = workspace
20756        .update_in(cx, |workspace, window, cx| {
20757            workspace.open_path(
20758                (worktree_id, "main.rs"),
20759                Some(pane.downgrade()),
20760                true,
20761                window,
20762                cx,
20763            )
20764        })
20765        .unwrap()
20766        .await
20767        .downcast::<Editor>()
20768        .unwrap();
20769    pane.update(cx, |pane, cx| {
20770        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20771        open_editor.update(cx, |editor, cx| {
20772            assert_eq!(
20773                editor.display_text(cx),
20774                "fn main() {}",
20775                "Original main.rs text on initial open",
20776            );
20777        });
20778        assert_eq!(open_editor, main_editor);
20779    });
20780    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20781
20782    let external_editor = workspace
20783        .update_in(cx, |workspace, window, cx| {
20784            workspace.open_abs_path(
20785                PathBuf::from("/root/foo/bar/external_file.rs"),
20786                OpenOptions::default(),
20787                window,
20788                cx,
20789            )
20790        })
20791        .await
20792        .expect("opening external file")
20793        .downcast::<Editor>()
20794        .expect("downcasted external file's open element to editor");
20795    pane.update(cx, |pane, cx| {
20796        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20797        open_editor.update(cx, |editor, cx| {
20798            assert_eq!(
20799                editor.display_text(cx),
20800                "pub mod external {}",
20801                "External file is open now",
20802            );
20803        });
20804        assert_eq!(open_editor, external_editor);
20805    });
20806    assert_language_servers_count(
20807        1,
20808        "Second, external, *.rs file should join the existing server",
20809        cx,
20810    );
20811
20812    pane.update_in(cx, |pane, window, cx| {
20813        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20814    })
20815    .await
20816    .unwrap();
20817    pane.update_in(cx, |pane, window, cx| {
20818        pane.navigate_backward(window, cx);
20819    });
20820    cx.run_until_parked();
20821    pane.update(cx, |pane, cx| {
20822        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20823        open_editor.update(cx, |editor, cx| {
20824            assert_eq!(
20825                editor.display_text(cx),
20826                "pub mod external {}",
20827                "External file is open now",
20828            );
20829        });
20830    });
20831    assert_language_servers_count(
20832        1,
20833        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20834        cx,
20835    );
20836
20837    cx.update(|_, cx| {
20838        workspace::reload(&workspace::Reload::default(), cx);
20839    });
20840    assert_language_servers_count(
20841        1,
20842        "After reloading the worktree with local and external files opened, only one project should be started",
20843        cx,
20844    );
20845}
20846
20847#[gpui::test]
20848async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20849    init_test(cx, |_| {});
20850
20851    let mut cx = EditorTestContext::new(cx).await;
20852    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20854
20855    // test cursor move to start of each line on tab
20856    // for `if`, `elif`, `else`, `while`, `with` and `for`
20857    cx.set_state(indoc! {"
20858        def main():
20859        ˇ    for item in items:
20860        ˇ        while item.active:
20861        ˇ            if item.value > 10:
20862        ˇ                continue
20863        ˇ            elif item.value < 0:
20864        ˇ                break
20865        ˇ            else:
20866        ˇ                with item.context() as ctx:
20867        ˇ                    yield count
20868        ˇ        else:
20869        ˇ            log('while else')
20870        ˇ    else:
20871        ˇ        log('for else')
20872    "});
20873    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20874    cx.assert_editor_state(indoc! {"
20875        def main():
20876            ˇfor item in items:
20877                ˇwhile item.active:
20878                    ˇif item.value > 10:
20879                        ˇcontinue
20880                    ˇelif item.value < 0:
20881                        ˇbreak
20882                    ˇelse:
20883                        ˇwith item.context() as ctx:
20884                            ˇyield count
20885                ˇelse:
20886                    ˇlog('while else')
20887            ˇelse:
20888                ˇlog('for else')
20889    "});
20890    // test relative indent is preserved when tab
20891    // for `if`, `elif`, `else`, `while`, `with` and `for`
20892    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20893    cx.assert_editor_state(indoc! {"
20894        def main():
20895                ˇfor item in items:
20896                    ˇwhile item.active:
20897                        ˇif item.value > 10:
20898                            ˇcontinue
20899                        ˇelif item.value < 0:
20900                            ˇbreak
20901                        ˇelse:
20902                            ˇwith item.context() as ctx:
20903                                ˇyield count
20904                    ˇelse:
20905                        ˇlog('while else')
20906                ˇelse:
20907                    ˇlog('for else')
20908    "});
20909
20910    // test cursor move to start of each line on tab
20911    // for `try`, `except`, `else`, `finally`, `match` and `def`
20912    cx.set_state(indoc! {"
20913        def main():
20914        ˇ    try:
20915        ˇ       fetch()
20916        ˇ    except ValueError:
20917        ˇ       handle_error()
20918        ˇ    else:
20919        ˇ        match value:
20920        ˇ            case _:
20921        ˇ    finally:
20922        ˇ        def status():
20923        ˇ            return 0
20924    "});
20925    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20926    cx.assert_editor_state(indoc! {"
20927        def main():
20928            ˇtry:
20929                ˇfetch()
20930            ˇexcept ValueError:
20931                ˇhandle_error()
20932            ˇelse:
20933                ˇmatch value:
20934                    ˇcase _:
20935            ˇfinally:
20936                ˇdef status():
20937                    ˇreturn 0
20938    "});
20939    // test relative indent is preserved when tab
20940    // for `try`, `except`, `else`, `finally`, `match` and `def`
20941    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20942    cx.assert_editor_state(indoc! {"
20943        def main():
20944                ˇtry:
20945                    ˇfetch()
20946                ˇexcept ValueError:
20947                    ˇhandle_error()
20948                ˇelse:
20949                    ˇmatch value:
20950                        ˇcase _:
20951                ˇfinally:
20952                    ˇdef status():
20953                        ˇreturn 0
20954    "});
20955}
20956
20957#[gpui::test]
20958async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
20959    init_test(cx, |_| {});
20960
20961    let mut cx = EditorTestContext::new(cx).await;
20962    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20963    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20964
20965    // test `else` auto outdents when typed inside `if` block
20966    cx.set_state(indoc! {"
20967        def main():
20968            if i == 2:
20969                return
20970                ˇ
20971    "});
20972    cx.update_editor(|editor, window, cx| {
20973        editor.handle_input("else:", window, cx);
20974    });
20975    cx.assert_editor_state(indoc! {"
20976        def main():
20977            if i == 2:
20978                return
20979            else:ˇ
20980    "});
20981
20982    // test `except` auto outdents when typed inside `try` block
20983    cx.set_state(indoc! {"
20984        def main():
20985            try:
20986                i = 2
20987                ˇ
20988    "});
20989    cx.update_editor(|editor, window, cx| {
20990        editor.handle_input("except:", window, cx);
20991    });
20992    cx.assert_editor_state(indoc! {"
20993        def main():
20994            try:
20995                i = 2
20996            except:ˇ
20997    "});
20998
20999    // test `else` auto outdents when typed inside `except` block
21000    cx.set_state(indoc! {"
21001        def main():
21002            try:
21003                i = 2
21004            except:
21005                j = 2
21006                ˇ
21007    "});
21008    cx.update_editor(|editor, window, cx| {
21009        editor.handle_input("else:", window, cx);
21010    });
21011    cx.assert_editor_state(indoc! {"
21012        def main():
21013            try:
21014                i = 2
21015            except:
21016                j = 2
21017            else:ˇ
21018    "});
21019
21020    // test `finally` auto outdents when typed inside `else` block
21021    cx.set_state(indoc! {"
21022        def main():
21023            try:
21024                i = 2
21025            except:
21026                j = 2
21027            else:
21028                k = 2
21029                ˇ
21030    "});
21031    cx.update_editor(|editor, window, cx| {
21032        editor.handle_input("finally:", window, cx);
21033    });
21034    cx.assert_editor_state(indoc! {"
21035        def main():
21036            try:
21037                i = 2
21038            except:
21039                j = 2
21040            else:
21041                k = 2
21042            finally:ˇ
21043    "});
21044
21045    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21046    // cx.set_state(indoc! {"
21047    //     def main():
21048    //         try:
21049    //             for i in range(n):
21050    //                 pass
21051    //             ˇ
21052    // "});
21053    // cx.update_editor(|editor, window, cx| {
21054    //     editor.handle_input("except:", window, cx);
21055    // });
21056    // cx.assert_editor_state(indoc! {"
21057    //     def main():
21058    //         try:
21059    //             for i in range(n):
21060    //                 pass
21061    //         except:ˇ
21062    // "});
21063
21064    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21065    // cx.set_state(indoc! {"
21066    //     def main():
21067    //         try:
21068    //             i = 2
21069    //         except:
21070    //             for i in range(n):
21071    //                 pass
21072    //             ˇ
21073    // "});
21074    // cx.update_editor(|editor, window, cx| {
21075    //     editor.handle_input("else:", window, cx);
21076    // });
21077    // cx.assert_editor_state(indoc! {"
21078    //     def main():
21079    //         try:
21080    //             i = 2
21081    //         except:
21082    //             for i in range(n):
21083    //                 pass
21084    //         else:ˇ
21085    // "});
21086
21087    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21088    // cx.set_state(indoc! {"
21089    //     def main():
21090    //         try:
21091    //             i = 2
21092    //         except:
21093    //             j = 2
21094    //         else:
21095    //             for i in range(n):
21096    //                 pass
21097    //             ˇ
21098    // "});
21099    // cx.update_editor(|editor, window, cx| {
21100    //     editor.handle_input("finally:", window, cx);
21101    // });
21102    // cx.assert_editor_state(indoc! {"
21103    //     def main():
21104    //         try:
21105    //             i = 2
21106    //         except:
21107    //             j = 2
21108    //         else:
21109    //             for i in range(n):
21110    //                 pass
21111    //         finally:ˇ
21112    // "});
21113
21114    // test `else` stays at correct indent when typed after `for` block
21115    cx.set_state(indoc! {"
21116        def main():
21117            for i in range(10):
21118                if i == 3:
21119                    break
21120            ˇ
21121    "});
21122    cx.update_editor(|editor, window, cx| {
21123        editor.handle_input("else:", window, cx);
21124    });
21125    cx.assert_editor_state(indoc! {"
21126        def main():
21127            for i in range(10):
21128                if i == 3:
21129                    break
21130            else:ˇ
21131    "});
21132
21133    // test does not outdent on typing after line with square brackets
21134    cx.set_state(indoc! {"
21135        def f() -> list[str]:
21136            ˇ
21137    "});
21138    cx.update_editor(|editor, window, cx| {
21139        editor.handle_input("a", window, cx);
21140    });
21141    cx.assert_editor_state(indoc! {"
21142        def f() -> list[str]:
2114321144    "});
21145}
21146
21147#[gpui::test]
21148async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21149    init_test(cx, |_| {});
21150    update_test_language_settings(cx, |settings| {
21151        settings.defaults.extend_comment_on_newline = Some(false);
21152    });
21153    let mut cx = EditorTestContext::new(cx).await;
21154    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21156
21157    // test correct indent after newline on comment
21158    cx.set_state(indoc! {"
21159        # COMMENT:ˇ
21160    "});
21161    cx.update_editor(|editor, window, cx| {
21162        editor.newline(&Newline, window, cx);
21163    });
21164    cx.assert_editor_state(indoc! {"
21165        # COMMENT:
21166        ˇ
21167    "});
21168
21169    // test correct indent after newline in brackets
21170    cx.set_state(indoc! {"
21171        {ˇ}
21172    "});
21173    cx.update_editor(|editor, window, cx| {
21174        editor.newline(&Newline, window, cx);
21175    });
21176    cx.run_until_parked();
21177    cx.assert_editor_state(indoc! {"
21178        {
21179            ˇ
21180        }
21181    "});
21182
21183    cx.set_state(indoc! {"
21184        (ˇ)
21185    "});
21186    cx.update_editor(|editor, window, cx| {
21187        editor.newline(&Newline, window, cx);
21188    });
21189    cx.run_until_parked();
21190    cx.assert_editor_state(indoc! {"
21191        (
21192            ˇ
21193        )
21194    "});
21195
21196    // do not indent after empty lists or dictionaries
21197    cx.set_state(indoc! {"
21198        a = []ˇ
21199    "});
21200    cx.update_editor(|editor, window, cx| {
21201        editor.newline(&Newline, window, cx);
21202    });
21203    cx.run_until_parked();
21204    cx.assert_editor_state(indoc! {"
21205        a = []
21206        ˇ
21207    "});
21208}
21209
21210fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21211    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21212    point..point
21213}
21214
21215fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21216    let (text, ranges) = marked_text_ranges(marked_text, true);
21217    assert_eq!(editor.text(cx), text);
21218    assert_eq!(
21219        editor.selections.ranges(cx),
21220        ranges,
21221        "Assert selections are {}",
21222        marked_text
21223    );
21224}
21225
21226pub fn handle_signature_help_request(
21227    cx: &mut EditorLspTestContext,
21228    mocked_response: lsp::SignatureHelp,
21229) -> impl Future<Output = ()> + use<> {
21230    let mut request =
21231        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21232            let mocked_response = mocked_response.clone();
21233            async move { Ok(Some(mocked_response)) }
21234        });
21235
21236    async move {
21237        request.next().await;
21238    }
21239}
21240
21241#[track_caller]
21242pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21243    cx.update_editor(|editor, _, _| {
21244        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21245            let entries = menu.entries.borrow();
21246            let entries = entries
21247                .iter()
21248                .map(|entry| entry.string.as_str())
21249                .collect::<Vec<_>>();
21250            assert_eq!(entries, expected);
21251        } else {
21252            panic!("Expected completions menu");
21253        }
21254    });
21255}
21256
21257/// Handle completion request passing a marked string specifying where the completion
21258/// should be triggered from using '|' character, what range should be replaced, and what completions
21259/// should be returned using '<' and '>' to delimit the range.
21260///
21261/// Also see `handle_completion_request_with_insert_and_replace`.
21262#[track_caller]
21263pub fn handle_completion_request(
21264    marked_string: &str,
21265    completions: Vec<&'static str>,
21266    is_incomplete: bool,
21267    counter: Arc<AtomicUsize>,
21268    cx: &mut EditorLspTestContext,
21269) -> impl Future<Output = ()> {
21270    let complete_from_marker: TextRangeMarker = '|'.into();
21271    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21272    let (_, mut marked_ranges) = marked_text_ranges_by(
21273        marked_string,
21274        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21275    );
21276
21277    let complete_from_position =
21278        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21279    let replace_range =
21280        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21281
21282    let mut request =
21283        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21284            let completions = completions.clone();
21285            counter.fetch_add(1, atomic::Ordering::Release);
21286            async move {
21287                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21288                assert_eq!(
21289                    params.text_document_position.position,
21290                    complete_from_position
21291                );
21292                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21293                    is_incomplete: is_incomplete,
21294                    item_defaults: None,
21295                    items: completions
21296                        .iter()
21297                        .map(|completion_text| lsp::CompletionItem {
21298                            label: completion_text.to_string(),
21299                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21300                                range: replace_range,
21301                                new_text: completion_text.to_string(),
21302                            })),
21303                            ..Default::default()
21304                        })
21305                        .collect(),
21306                })))
21307            }
21308        });
21309
21310    async move {
21311        request.next().await;
21312    }
21313}
21314
21315/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21316/// given instead, which also contains an `insert` range.
21317///
21318/// This function uses markers to define ranges:
21319/// - `|` marks the cursor position
21320/// - `<>` marks the replace range
21321/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21322pub fn handle_completion_request_with_insert_and_replace(
21323    cx: &mut EditorLspTestContext,
21324    marked_string: &str,
21325    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21326    counter: Arc<AtomicUsize>,
21327) -> impl Future<Output = ()> {
21328    let complete_from_marker: TextRangeMarker = '|'.into();
21329    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21330    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21331
21332    let (_, mut marked_ranges) = marked_text_ranges_by(
21333        marked_string,
21334        vec![
21335            complete_from_marker.clone(),
21336            replace_range_marker.clone(),
21337            insert_range_marker.clone(),
21338        ],
21339    );
21340
21341    let complete_from_position =
21342        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21343    let replace_range =
21344        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21345
21346    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21347        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21348        _ => lsp::Range {
21349            start: replace_range.start,
21350            end: complete_from_position,
21351        },
21352    };
21353
21354    let mut request =
21355        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21356            let completions = completions.clone();
21357            counter.fetch_add(1, atomic::Ordering::Release);
21358            async move {
21359                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21360                assert_eq!(
21361                    params.text_document_position.position, complete_from_position,
21362                    "marker `|` position doesn't match",
21363                );
21364                Ok(Some(lsp::CompletionResponse::Array(
21365                    completions
21366                        .iter()
21367                        .map(|(label, new_text)| lsp::CompletionItem {
21368                            label: label.to_string(),
21369                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21370                                lsp::InsertReplaceEdit {
21371                                    insert: insert_range,
21372                                    replace: replace_range,
21373                                    new_text: new_text.to_string(),
21374                                },
21375                            )),
21376                            ..Default::default()
21377                        })
21378                        .collect(),
21379                )))
21380            }
21381        });
21382
21383    async move {
21384        request.next().await;
21385    }
21386}
21387
21388fn handle_resolve_completion_request(
21389    cx: &mut EditorLspTestContext,
21390    edits: Option<Vec<(&'static str, &'static str)>>,
21391) -> impl Future<Output = ()> {
21392    let edits = edits.map(|edits| {
21393        edits
21394            .iter()
21395            .map(|(marked_string, new_text)| {
21396                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21397                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21398                lsp::TextEdit::new(replace_range, new_text.to_string())
21399            })
21400            .collect::<Vec<_>>()
21401    });
21402
21403    let mut request =
21404        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21405            let edits = edits.clone();
21406            async move {
21407                Ok(lsp::CompletionItem {
21408                    additional_text_edits: edits,
21409                    ..Default::default()
21410                })
21411            }
21412        });
21413
21414    async move {
21415        request.next().await;
21416    }
21417}
21418
21419pub(crate) fn update_test_language_settings(
21420    cx: &mut TestAppContext,
21421    f: impl Fn(&mut AllLanguageSettingsContent),
21422) {
21423    cx.update(|cx| {
21424        SettingsStore::update_global(cx, |store, cx| {
21425            store.update_user_settings::<AllLanguageSettings>(cx, f);
21426        });
21427    });
21428}
21429
21430pub(crate) fn update_test_project_settings(
21431    cx: &mut TestAppContext,
21432    f: impl Fn(&mut ProjectSettings),
21433) {
21434    cx.update(|cx| {
21435        SettingsStore::update_global(cx, |store, cx| {
21436            store.update_user_settings::<ProjectSettings>(cx, f);
21437        });
21438    });
21439}
21440
21441pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21442    cx.update(|cx| {
21443        assets::Assets.load_test_fonts(cx);
21444        let store = SettingsStore::test(cx);
21445        cx.set_global(store);
21446        theme::init(theme::LoadThemes::JustBase, cx);
21447        release_channel::init(SemanticVersion::default(), cx);
21448        client::init_settings(cx);
21449        language::init(cx);
21450        Project::init_settings(cx);
21451        workspace::init_settings(cx);
21452        crate::init(cx);
21453    });
21454
21455    update_test_language_settings(cx, f);
21456}
21457
21458#[track_caller]
21459fn assert_hunk_revert(
21460    not_reverted_text_with_selections: &str,
21461    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21462    expected_reverted_text_with_selections: &str,
21463    base_text: &str,
21464    cx: &mut EditorLspTestContext,
21465) {
21466    cx.set_state(not_reverted_text_with_selections);
21467    cx.set_head_text(base_text);
21468    cx.executor().run_until_parked();
21469
21470    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21471        let snapshot = editor.snapshot(window, cx);
21472        let reverted_hunk_statuses = snapshot
21473            .buffer_snapshot
21474            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21475            .map(|hunk| hunk.status().kind)
21476            .collect::<Vec<_>>();
21477
21478        editor.git_restore(&Default::default(), window, cx);
21479        reverted_hunk_statuses
21480    });
21481    cx.executor().run_until_parked();
21482    cx.assert_editor_state(expected_reverted_text_with_selections);
21483    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21484}