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 mut cx = EditorTestContext::new(cx).await;
 8517
 8518    cx.set_state(indoc! {"
 8519        a.ˇ b
 8520        a.ˇ b
 8521        a.ˇ b
 8522    "});
 8523
 8524    cx.update_editor(|editor, window, cx| {
 8525        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8526        let insertion_ranges = editor
 8527            .selections
 8528            .all(cx)
 8529            .iter()
 8530            .map(|s| s.range().clone())
 8531            .collect::<Vec<_>>();
 8532        editor
 8533            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8534            .unwrap();
 8535    });
 8536
 8537    cx.assert_editor_state(indoc! {"
 8538        a.f(«oneˇ», two, «threeˇ») b
 8539        a.f(«oneˇ», two, «threeˇ») b
 8540        a.f(«oneˇ», two, «threeˇ») b
 8541    "});
 8542
 8543    // Can't move earlier than the first tab stop
 8544    cx.update_editor(|editor, window, cx| {
 8545        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8546    });
 8547    cx.assert_editor_state(indoc! {"
 8548        a.f(«oneˇ», two, «threeˇ») b
 8549        a.f(«oneˇ», two, «threeˇ») b
 8550        a.f(«oneˇ», two, «threeˇ») b
 8551    "});
 8552
 8553    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8554    cx.assert_editor_state(indoc! {"
 8555        a.f(one, «twoˇ», three) b
 8556        a.f(one, «twoˇ», three) b
 8557        a.f(one, «twoˇ», three) b
 8558    "});
 8559
 8560    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 8561    cx.assert_editor_state(indoc! {"
 8562        a.f(«oneˇ», two, «threeˇ») b
 8563        a.f(«oneˇ», two, «threeˇ») b
 8564        a.f(«oneˇ», two, «threeˇ») b
 8565    "});
 8566
 8567    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8568    cx.assert_editor_state(indoc! {"
 8569        a.f(one, «twoˇ», three) b
 8570        a.f(one, «twoˇ», three) b
 8571        a.f(one, «twoˇ», three) b
 8572    "});
 8573    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8574    cx.assert_editor_state(indoc! {"
 8575        a.f(one, two, three)ˇ b
 8576        a.f(one, two, three)ˇ b
 8577        a.f(one, two, three)ˇ b
 8578    "});
 8579
 8580    // As soon as the last tab stop is reached, snippet state is gone
 8581    cx.update_editor(|editor, window, cx| {
 8582        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8583    });
 8584    cx.assert_editor_state(indoc! {"
 8585        a.f(one, two, three)ˇ b
 8586        a.f(one, two, three)ˇ b
 8587        a.f(one, two, three)ˇ b
 8588    "});
 8589}
 8590
 8591#[gpui::test]
 8592async fn test_snippet_indentation(cx: &mut TestAppContext) {
 8593    init_test(cx, |_| {});
 8594
 8595    let mut cx = EditorTestContext::new(cx).await;
 8596
 8597    cx.update_editor(|editor, window, cx| {
 8598        let snippet = Snippet::parse(indoc! {"
 8599            /*
 8600             * Multiline comment with leading indentation
 8601             *
 8602             * $1
 8603             */
 8604            $0"})
 8605        .unwrap();
 8606        let insertion_ranges = editor
 8607            .selections
 8608            .all(cx)
 8609            .iter()
 8610            .map(|s| s.range().clone())
 8611            .collect::<Vec<_>>();
 8612        editor
 8613            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8614            .unwrap();
 8615    });
 8616
 8617    cx.assert_editor_state(indoc! {"
 8618        /*
 8619         * Multiline comment with leading indentation
 8620         *
 8621         * ˇ
 8622         */
 8623    "});
 8624
 8625    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8626    cx.assert_editor_state(indoc! {"
 8627        /*
 8628         * Multiline comment with leading indentation
 8629         *
 8630         *•
 8631         */
 8632        ˇ"});
 8633}
 8634
 8635#[gpui::test]
 8636async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8637    init_test(cx, |_| {});
 8638
 8639    let fs = FakeFs::new(cx.executor());
 8640    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8641
 8642    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8643
 8644    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8645    language_registry.add(rust_lang());
 8646    let mut fake_servers = language_registry.register_fake_lsp(
 8647        "Rust",
 8648        FakeLspAdapter {
 8649            capabilities: lsp::ServerCapabilities {
 8650                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8651                ..Default::default()
 8652            },
 8653            ..Default::default()
 8654        },
 8655    );
 8656
 8657    let buffer = project
 8658        .update(cx, |project, cx| {
 8659            project.open_local_buffer(path!("/file.rs"), cx)
 8660        })
 8661        .await
 8662        .unwrap();
 8663
 8664    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8665    let (editor, cx) = cx.add_window_view(|window, cx| {
 8666        build_editor_with_project(project.clone(), buffer, window, cx)
 8667    });
 8668    editor.update_in(cx, |editor, window, cx| {
 8669        editor.set_text("one\ntwo\nthree\n", window, cx)
 8670    });
 8671    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8672
 8673    cx.executor().start_waiting();
 8674    let fake_server = fake_servers.next().await.unwrap();
 8675
 8676    {
 8677        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8678            move |params, _| async move {
 8679                assert_eq!(
 8680                    params.text_document.uri,
 8681                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8682                );
 8683                assert_eq!(params.options.tab_size, 4);
 8684                Ok(Some(vec![lsp::TextEdit::new(
 8685                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8686                    ", ".to_string(),
 8687                )]))
 8688            },
 8689        );
 8690        let save = editor
 8691            .update_in(cx, |editor, window, cx| {
 8692                editor.save(true, project.clone(), window, cx)
 8693            })
 8694            .unwrap();
 8695        cx.executor().start_waiting();
 8696        save.await;
 8697
 8698        assert_eq!(
 8699            editor.update(cx, |editor, cx| editor.text(cx)),
 8700            "one, two\nthree\n"
 8701        );
 8702        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8703    }
 8704
 8705    {
 8706        editor.update_in(cx, |editor, window, cx| {
 8707            editor.set_text("one\ntwo\nthree\n", window, cx)
 8708        });
 8709        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8710
 8711        // Ensure we can still save even if formatting hangs.
 8712        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8713            move |params, _| async move {
 8714                assert_eq!(
 8715                    params.text_document.uri,
 8716                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8717                );
 8718                futures::future::pending::<()>().await;
 8719                unreachable!()
 8720            },
 8721        );
 8722        let save = editor
 8723            .update_in(cx, |editor, window, cx| {
 8724                editor.save(true, project.clone(), window, cx)
 8725            })
 8726            .unwrap();
 8727        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8728        cx.executor().start_waiting();
 8729        save.await;
 8730        assert_eq!(
 8731            editor.update(cx, |editor, cx| editor.text(cx)),
 8732            "one\ntwo\nthree\n"
 8733        );
 8734    }
 8735
 8736    // For non-dirty buffer, no formatting request should be sent
 8737    {
 8738        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8739
 8740        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8741            panic!("Should not be invoked on non-dirty buffer");
 8742        });
 8743        let save = editor
 8744            .update_in(cx, |editor, window, cx| {
 8745                editor.save(true, project.clone(), window, cx)
 8746            })
 8747            .unwrap();
 8748        cx.executor().start_waiting();
 8749        save.await;
 8750    }
 8751
 8752    // Set rust language override and assert overridden tabsize is sent to language server
 8753    update_test_language_settings(cx, |settings| {
 8754        settings.languages.insert(
 8755            "Rust".into(),
 8756            LanguageSettingsContent {
 8757                tab_size: NonZeroU32::new(8),
 8758                ..Default::default()
 8759            },
 8760        );
 8761    });
 8762
 8763    {
 8764        editor.update_in(cx, |editor, window, cx| {
 8765            editor.set_text("somehting_new\n", window, cx)
 8766        });
 8767        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8768        let _formatting_request_signal = fake_server
 8769            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8770                assert_eq!(
 8771                    params.text_document.uri,
 8772                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8773                );
 8774                assert_eq!(params.options.tab_size, 8);
 8775                Ok(Some(vec![]))
 8776            });
 8777        let save = editor
 8778            .update_in(cx, |editor, window, cx| {
 8779                editor.save(true, project.clone(), window, cx)
 8780            })
 8781            .unwrap();
 8782        cx.executor().start_waiting();
 8783        save.await;
 8784    }
 8785}
 8786
 8787#[gpui::test]
 8788async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8789    init_test(cx, |_| {});
 8790
 8791    let cols = 4;
 8792    let rows = 10;
 8793    let sample_text_1 = sample_text(rows, cols, 'a');
 8794    assert_eq!(
 8795        sample_text_1,
 8796        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8797    );
 8798    let sample_text_2 = sample_text(rows, cols, 'l');
 8799    assert_eq!(
 8800        sample_text_2,
 8801        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8802    );
 8803    let sample_text_3 = sample_text(rows, cols, 'v');
 8804    assert_eq!(
 8805        sample_text_3,
 8806        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8807    );
 8808
 8809    let fs = FakeFs::new(cx.executor());
 8810    fs.insert_tree(
 8811        path!("/a"),
 8812        json!({
 8813            "main.rs": sample_text_1,
 8814            "other.rs": sample_text_2,
 8815            "lib.rs": sample_text_3,
 8816        }),
 8817    )
 8818    .await;
 8819
 8820    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8821    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8822    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8823
 8824    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8825    language_registry.add(rust_lang());
 8826    let mut fake_servers = language_registry.register_fake_lsp(
 8827        "Rust",
 8828        FakeLspAdapter {
 8829            capabilities: lsp::ServerCapabilities {
 8830                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8831                ..Default::default()
 8832            },
 8833            ..Default::default()
 8834        },
 8835    );
 8836
 8837    let worktree = project.update(cx, |project, cx| {
 8838        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8839        assert_eq!(worktrees.len(), 1);
 8840        worktrees.pop().unwrap()
 8841    });
 8842    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8843
 8844    let buffer_1 = project
 8845        .update(cx, |project, cx| {
 8846            project.open_buffer((worktree_id, "main.rs"), cx)
 8847        })
 8848        .await
 8849        .unwrap();
 8850    let buffer_2 = project
 8851        .update(cx, |project, cx| {
 8852            project.open_buffer((worktree_id, "other.rs"), cx)
 8853        })
 8854        .await
 8855        .unwrap();
 8856    let buffer_3 = project
 8857        .update(cx, |project, cx| {
 8858            project.open_buffer((worktree_id, "lib.rs"), cx)
 8859        })
 8860        .await
 8861        .unwrap();
 8862
 8863    let multi_buffer = cx.new(|cx| {
 8864        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8865        multi_buffer.push_excerpts(
 8866            buffer_1.clone(),
 8867            [
 8868                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8869                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8870                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8871            ],
 8872            cx,
 8873        );
 8874        multi_buffer.push_excerpts(
 8875            buffer_2.clone(),
 8876            [
 8877                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8878                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8879                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8880            ],
 8881            cx,
 8882        );
 8883        multi_buffer.push_excerpts(
 8884            buffer_3.clone(),
 8885            [
 8886                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8887                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8888                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8889            ],
 8890            cx,
 8891        );
 8892        multi_buffer
 8893    });
 8894    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8895        Editor::new(
 8896            EditorMode::full(),
 8897            multi_buffer,
 8898            Some(project.clone()),
 8899            window,
 8900            cx,
 8901        )
 8902    });
 8903
 8904    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8905        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8906            s.select_ranges(Some(1..2))
 8907        });
 8908        editor.insert("|one|two|three|", window, cx);
 8909    });
 8910    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8911    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8912        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8913            s.select_ranges(Some(60..70))
 8914        });
 8915        editor.insert("|four|five|six|", window, cx);
 8916    });
 8917    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8918
 8919    // First two buffers should be edited, but not the third one.
 8920    assert_eq!(
 8921        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8922        "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}",
 8923    );
 8924    buffer_1.update(cx, |buffer, _| {
 8925        assert!(buffer.is_dirty());
 8926        assert_eq!(
 8927            buffer.text(),
 8928            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8929        )
 8930    });
 8931    buffer_2.update(cx, |buffer, _| {
 8932        assert!(buffer.is_dirty());
 8933        assert_eq!(
 8934            buffer.text(),
 8935            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8936        )
 8937    });
 8938    buffer_3.update(cx, |buffer, _| {
 8939        assert!(!buffer.is_dirty());
 8940        assert_eq!(buffer.text(), sample_text_3,)
 8941    });
 8942    cx.executor().run_until_parked();
 8943
 8944    cx.executor().start_waiting();
 8945    let save = multi_buffer_editor
 8946        .update_in(cx, |editor, window, cx| {
 8947            editor.save(true, project.clone(), window, cx)
 8948        })
 8949        .unwrap();
 8950
 8951    let fake_server = fake_servers.next().await.unwrap();
 8952    fake_server
 8953        .server
 8954        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8955            Ok(Some(vec![lsp::TextEdit::new(
 8956                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8957                format!("[{} formatted]", params.text_document.uri),
 8958            )]))
 8959        })
 8960        .detach();
 8961    save.await;
 8962
 8963    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8964    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8965    assert_eq!(
 8966        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8967        uri!(
 8968            "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}"
 8969        ),
 8970    );
 8971    buffer_1.update(cx, |buffer, _| {
 8972        assert!(!buffer.is_dirty());
 8973        assert_eq!(
 8974            buffer.text(),
 8975            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8976        )
 8977    });
 8978    buffer_2.update(cx, |buffer, _| {
 8979        assert!(!buffer.is_dirty());
 8980        assert_eq!(
 8981            buffer.text(),
 8982            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8983        )
 8984    });
 8985    buffer_3.update(cx, |buffer, _| {
 8986        assert!(!buffer.is_dirty());
 8987        assert_eq!(buffer.text(), sample_text_3,)
 8988    });
 8989}
 8990
 8991#[gpui::test]
 8992async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8993    init_test(cx, |_| {});
 8994
 8995    let fs = FakeFs::new(cx.executor());
 8996    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8997
 8998    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8999
 9000    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9001    language_registry.add(rust_lang());
 9002    let mut fake_servers = language_registry.register_fake_lsp(
 9003        "Rust",
 9004        FakeLspAdapter {
 9005            capabilities: lsp::ServerCapabilities {
 9006                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9007                ..Default::default()
 9008            },
 9009            ..Default::default()
 9010        },
 9011    );
 9012
 9013    let buffer = project
 9014        .update(cx, |project, cx| {
 9015            project.open_local_buffer(path!("/file.rs"), cx)
 9016        })
 9017        .await
 9018        .unwrap();
 9019
 9020    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9021    let (editor, cx) = cx.add_window_view(|window, cx| {
 9022        build_editor_with_project(project.clone(), buffer, window, cx)
 9023    });
 9024    editor.update_in(cx, |editor, window, cx| {
 9025        editor.set_text("one\ntwo\nthree\n", window, cx)
 9026    });
 9027    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9028
 9029    cx.executor().start_waiting();
 9030    let fake_server = fake_servers.next().await.unwrap();
 9031
 9032    let save = editor
 9033        .update_in(cx, |editor, window, cx| {
 9034            editor.save(true, project.clone(), window, cx)
 9035        })
 9036        .unwrap();
 9037    fake_server
 9038        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9039            assert_eq!(
 9040                params.text_document.uri,
 9041                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9042            );
 9043            assert_eq!(params.options.tab_size, 4);
 9044            Ok(Some(vec![lsp::TextEdit::new(
 9045                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9046                ", ".to_string(),
 9047            )]))
 9048        })
 9049        .next()
 9050        .await;
 9051    cx.executor().start_waiting();
 9052    save.await;
 9053    assert_eq!(
 9054        editor.update(cx, |editor, cx| editor.text(cx)),
 9055        "one, two\nthree\n"
 9056    );
 9057    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9058
 9059    editor.update_in(cx, |editor, window, cx| {
 9060        editor.set_text("one\ntwo\nthree\n", window, cx)
 9061    });
 9062    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9063
 9064    // Ensure we can still save even if formatting hangs.
 9065    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9066        move |params, _| async move {
 9067            assert_eq!(
 9068                params.text_document.uri,
 9069                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9070            );
 9071            futures::future::pending::<()>().await;
 9072            unreachable!()
 9073        },
 9074    );
 9075    let save = editor
 9076        .update_in(cx, |editor, window, cx| {
 9077            editor.save(true, project.clone(), window, cx)
 9078        })
 9079        .unwrap();
 9080    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9081    cx.executor().start_waiting();
 9082    save.await;
 9083    assert_eq!(
 9084        editor.update(cx, |editor, cx| editor.text(cx)),
 9085        "one\ntwo\nthree\n"
 9086    );
 9087    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9088
 9089    // For non-dirty buffer, no formatting request should be sent
 9090    let save = editor
 9091        .update_in(cx, |editor, window, cx| {
 9092            editor.save(true, project.clone(), window, cx)
 9093        })
 9094        .unwrap();
 9095    let _pending_format_request = fake_server
 9096        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9097            panic!("Should not be invoked on non-dirty buffer");
 9098        })
 9099        .next();
 9100    cx.executor().start_waiting();
 9101    save.await;
 9102
 9103    // Set Rust language override and assert overridden tabsize is sent to language server
 9104    update_test_language_settings(cx, |settings| {
 9105        settings.languages.insert(
 9106            "Rust".into(),
 9107            LanguageSettingsContent {
 9108                tab_size: NonZeroU32::new(8),
 9109                ..Default::default()
 9110            },
 9111        );
 9112    });
 9113
 9114    editor.update_in(cx, |editor, window, cx| {
 9115        editor.set_text("somehting_new\n", window, cx)
 9116    });
 9117    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9118    let save = editor
 9119        .update_in(cx, |editor, window, cx| {
 9120            editor.save(true, project.clone(), window, cx)
 9121        })
 9122        .unwrap();
 9123    fake_server
 9124        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9125            assert_eq!(
 9126                params.text_document.uri,
 9127                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9128            );
 9129            assert_eq!(params.options.tab_size, 8);
 9130            Ok(Some(Vec::new()))
 9131        })
 9132        .next()
 9133        .await;
 9134    save.await;
 9135}
 9136
 9137#[gpui::test]
 9138async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9139    init_test(cx, |settings| {
 9140        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9141            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9142        ))
 9143    });
 9144
 9145    let fs = FakeFs::new(cx.executor());
 9146    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9147
 9148    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9149
 9150    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9151    language_registry.add(Arc::new(Language::new(
 9152        LanguageConfig {
 9153            name: "Rust".into(),
 9154            matcher: LanguageMatcher {
 9155                path_suffixes: vec!["rs".to_string()],
 9156                ..Default::default()
 9157            },
 9158            ..LanguageConfig::default()
 9159        },
 9160        Some(tree_sitter_rust::LANGUAGE.into()),
 9161    )));
 9162    update_test_language_settings(cx, |settings| {
 9163        // Enable Prettier formatting for the same buffer, and ensure
 9164        // LSP is called instead of Prettier.
 9165        settings.defaults.prettier = Some(PrettierSettings {
 9166            allowed: true,
 9167            ..PrettierSettings::default()
 9168        });
 9169    });
 9170    let mut fake_servers = language_registry.register_fake_lsp(
 9171        "Rust",
 9172        FakeLspAdapter {
 9173            capabilities: lsp::ServerCapabilities {
 9174                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9175                ..Default::default()
 9176            },
 9177            ..Default::default()
 9178        },
 9179    );
 9180
 9181    let buffer = project
 9182        .update(cx, |project, cx| {
 9183            project.open_local_buffer(path!("/file.rs"), cx)
 9184        })
 9185        .await
 9186        .unwrap();
 9187
 9188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9189    let (editor, cx) = cx.add_window_view(|window, cx| {
 9190        build_editor_with_project(project.clone(), buffer, window, cx)
 9191    });
 9192    editor.update_in(cx, |editor, window, cx| {
 9193        editor.set_text("one\ntwo\nthree\n", window, cx)
 9194    });
 9195
 9196    cx.executor().start_waiting();
 9197    let fake_server = fake_servers.next().await.unwrap();
 9198
 9199    let format = editor
 9200        .update_in(cx, |editor, window, cx| {
 9201            editor.perform_format(
 9202                project.clone(),
 9203                FormatTrigger::Manual,
 9204                FormatTarget::Buffers,
 9205                window,
 9206                cx,
 9207            )
 9208        })
 9209        .unwrap();
 9210    fake_server
 9211        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9212            assert_eq!(
 9213                params.text_document.uri,
 9214                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9215            );
 9216            assert_eq!(params.options.tab_size, 4);
 9217            Ok(Some(vec![lsp::TextEdit::new(
 9218                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9219                ", ".to_string(),
 9220            )]))
 9221        })
 9222        .next()
 9223        .await;
 9224    cx.executor().start_waiting();
 9225    format.await;
 9226    assert_eq!(
 9227        editor.update(cx, |editor, cx| editor.text(cx)),
 9228        "one, two\nthree\n"
 9229    );
 9230
 9231    editor.update_in(cx, |editor, window, cx| {
 9232        editor.set_text("one\ntwo\nthree\n", window, cx)
 9233    });
 9234    // Ensure we don't lock if formatting hangs.
 9235    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9236        move |params, _| async move {
 9237            assert_eq!(
 9238                params.text_document.uri,
 9239                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9240            );
 9241            futures::future::pending::<()>().await;
 9242            unreachable!()
 9243        },
 9244    );
 9245    let format = editor
 9246        .update_in(cx, |editor, window, cx| {
 9247            editor.perform_format(
 9248                project,
 9249                FormatTrigger::Manual,
 9250                FormatTarget::Buffers,
 9251                window,
 9252                cx,
 9253            )
 9254        })
 9255        .unwrap();
 9256    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9257    cx.executor().start_waiting();
 9258    format.await;
 9259    assert_eq!(
 9260        editor.update(cx, |editor, cx| editor.text(cx)),
 9261        "one\ntwo\nthree\n"
 9262    );
 9263}
 9264
 9265#[gpui::test]
 9266async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9267    init_test(cx, |settings| {
 9268        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9269        settings.defaults.formatter =
 9270            Some(language_settings::SelectedFormatter::List(FormatterList(
 9271                vec![
 9272                    Formatter::LanguageServer { name: None },
 9273                    Formatter::CodeActions(
 9274                        [
 9275                            ("code-action-1".into(), true),
 9276                            ("code-action-2".into(), true),
 9277                        ]
 9278                        .into_iter()
 9279                        .collect(),
 9280                    ),
 9281                ]
 9282                .into(),
 9283            )))
 9284    });
 9285
 9286    let fs = FakeFs::new(cx.executor());
 9287    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9288        .await;
 9289
 9290    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9291    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9292    language_registry.add(rust_lang());
 9293
 9294    let mut fake_servers = language_registry.register_fake_lsp(
 9295        "Rust",
 9296        FakeLspAdapter {
 9297            capabilities: lsp::ServerCapabilities {
 9298                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9299                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9300                    commands: vec!["the-command-for-code-action-1".into()],
 9301                    ..Default::default()
 9302                }),
 9303                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9304                ..Default::default()
 9305            },
 9306            ..Default::default()
 9307        },
 9308    );
 9309
 9310    let buffer = project
 9311        .update(cx, |project, cx| {
 9312            project.open_local_buffer(path!("/file.rs"), cx)
 9313        })
 9314        .await
 9315        .unwrap();
 9316
 9317    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9318    let (editor, cx) = cx.add_window_view(|window, cx| {
 9319        build_editor_with_project(project.clone(), buffer, window, cx)
 9320    });
 9321
 9322    cx.executor().start_waiting();
 9323
 9324    let fake_server = fake_servers.next().await.unwrap();
 9325    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9326        move |_params, _| async move {
 9327            Ok(Some(vec![lsp::TextEdit::new(
 9328                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9329                "applied-formatting\n".to_string(),
 9330            )]))
 9331        },
 9332    );
 9333    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9334        move |params, _| async move {
 9335            assert_eq!(
 9336                params.context.only,
 9337                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9338            );
 9339            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9340            Ok(Some(vec![
 9341                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9342                    kind: Some("code-action-1".into()),
 9343                    edit: Some(lsp::WorkspaceEdit::new(
 9344                        [(
 9345                            uri.clone(),
 9346                            vec![lsp::TextEdit::new(
 9347                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9348                                "applied-code-action-1-edit\n".to_string(),
 9349                            )],
 9350                        )]
 9351                        .into_iter()
 9352                        .collect(),
 9353                    )),
 9354                    command: Some(lsp::Command {
 9355                        command: "the-command-for-code-action-1".into(),
 9356                        ..Default::default()
 9357                    }),
 9358                    ..Default::default()
 9359                }),
 9360                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9361                    kind: Some("code-action-2".into()),
 9362                    edit: Some(lsp::WorkspaceEdit::new(
 9363                        [(
 9364                            uri.clone(),
 9365                            vec![lsp::TextEdit::new(
 9366                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9367                                "applied-code-action-2-edit\n".to_string(),
 9368                            )],
 9369                        )]
 9370                        .into_iter()
 9371                        .collect(),
 9372                    )),
 9373                    ..Default::default()
 9374                }),
 9375            ]))
 9376        },
 9377    );
 9378
 9379    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9380        move |params, _| async move { Ok(params) }
 9381    });
 9382
 9383    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9384    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9385        let fake = fake_server.clone();
 9386        let lock = command_lock.clone();
 9387        move |params, _| {
 9388            assert_eq!(params.command, "the-command-for-code-action-1");
 9389            let fake = fake.clone();
 9390            let lock = lock.clone();
 9391            async move {
 9392                lock.lock().await;
 9393                fake.server
 9394                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9395                        label: None,
 9396                        edit: lsp::WorkspaceEdit {
 9397                            changes: Some(
 9398                                [(
 9399                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9400                                    vec![lsp::TextEdit {
 9401                                        range: lsp::Range::new(
 9402                                            lsp::Position::new(0, 0),
 9403                                            lsp::Position::new(0, 0),
 9404                                        ),
 9405                                        new_text: "applied-code-action-1-command\n".into(),
 9406                                    }],
 9407                                )]
 9408                                .into_iter()
 9409                                .collect(),
 9410                            ),
 9411                            ..Default::default()
 9412                        },
 9413                    })
 9414                    .await
 9415                    .into_response()
 9416                    .unwrap();
 9417                Ok(Some(json!(null)))
 9418            }
 9419        }
 9420    });
 9421
 9422    cx.executor().start_waiting();
 9423    editor
 9424        .update_in(cx, |editor, window, cx| {
 9425            editor.perform_format(
 9426                project.clone(),
 9427                FormatTrigger::Manual,
 9428                FormatTarget::Buffers,
 9429                window,
 9430                cx,
 9431            )
 9432        })
 9433        .unwrap()
 9434        .await;
 9435    editor.update(cx, |editor, cx| {
 9436        assert_eq!(
 9437            editor.text(cx),
 9438            r#"
 9439                applied-code-action-2-edit
 9440                applied-code-action-1-command
 9441                applied-code-action-1-edit
 9442                applied-formatting
 9443                one
 9444                two
 9445                three
 9446            "#
 9447            .unindent()
 9448        );
 9449    });
 9450
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.undo(&Default::default(), window, cx);
 9453        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9454    });
 9455
 9456    // Perform a manual edit while waiting for an LSP command
 9457    // that's being run as part of a formatting code action.
 9458    let lock_guard = command_lock.lock().await;
 9459    let format = editor
 9460        .update_in(cx, |editor, window, cx| {
 9461            editor.perform_format(
 9462                project.clone(),
 9463                FormatTrigger::Manual,
 9464                FormatTarget::Buffers,
 9465                window,
 9466                cx,
 9467            )
 9468        })
 9469        .unwrap();
 9470    cx.run_until_parked();
 9471    editor.update(cx, |editor, cx| {
 9472        assert_eq!(
 9473            editor.text(cx),
 9474            r#"
 9475                applied-code-action-1-edit
 9476                applied-formatting
 9477                one
 9478                two
 9479                three
 9480            "#
 9481            .unindent()
 9482        );
 9483
 9484        editor.buffer.update(cx, |buffer, cx| {
 9485            let ix = buffer.len(cx);
 9486            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9487        });
 9488    });
 9489
 9490    // Allow the LSP command to proceed. Because the buffer was edited,
 9491    // the second code action will not be run.
 9492    drop(lock_guard);
 9493    format.await;
 9494    editor.update_in(cx, |editor, window, cx| {
 9495        assert_eq!(
 9496            editor.text(cx),
 9497            r#"
 9498                applied-code-action-1-command
 9499                applied-code-action-1-edit
 9500                applied-formatting
 9501                one
 9502                two
 9503                three
 9504                edited
 9505            "#
 9506            .unindent()
 9507        );
 9508
 9509        // The manual edit is undone first, because it is the last thing the user did
 9510        // (even though the command completed afterwards).
 9511        editor.undo(&Default::default(), window, cx);
 9512        assert_eq!(
 9513            editor.text(cx),
 9514            r#"
 9515                applied-code-action-1-command
 9516                applied-code-action-1-edit
 9517                applied-formatting
 9518                one
 9519                two
 9520                three
 9521            "#
 9522            .unindent()
 9523        );
 9524
 9525        // All the formatting (including the command, which completed after the manual edit)
 9526        // is undone together.
 9527        editor.undo(&Default::default(), window, cx);
 9528        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9529    });
 9530}
 9531
 9532#[gpui::test]
 9533async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9534    init_test(cx, |settings| {
 9535        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9536            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9537        ))
 9538    });
 9539
 9540    let fs = FakeFs::new(cx.executor());
 9541    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9542
 9543    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9544
 9545    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9546    language_registry.add(Arc::new(Language::new(
 9547        LanguageConfig {
 9548            name: "TypeScript".into(),
 9549            matcher: LanguageMatcher {
 9550                path_suffixes: vec!["ts".to_string()],
 9551                ..Default::default()
 9552            },
 9553            ..LanguageConfig::default()
 9554        },
 9555        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9556    )));
 9557    update_test_language_settings(cx, |settings| {
 9558        settings.defaults.prettier = Some(PrettierSettings {
 9559            allowed: true,
 9560            ..PrettierSettings::default()
 9561        });
 9562    });
 9563    let mut fake_servers = language_registry.register_fake_lsp(
 9564        "TypeScript",
 9565        FakeLspAdapter {
 9566            capabilities: lsp::ServerCapabilities {
 9567                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9568                ..Default::default()
 9569            },
 9570            ..Default::default()
 9571        },
 9572    );
 9573
 9574    let buffer = project
 9575        .update(cx, |project, cx| {
 9576            project.open_local_buffer(path!("/file.ts"), cx)
 9577        })
 9578        .await
 9579        .unwrap();
 9580
 9581    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9582    let (editor, cx) = cx.add_window_view(|window, cx| {
 9583        build_editor_with_project(project.clone(), buffer, window, cx)
 9584    });
 9585    editor.update_in(cx, |editor, window, cx| {
 9586        editor.set_text(
 9587            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9588            window,
 9589            cx,
 9590        )
 9591    });
 9592
 9593    cx.executor().start_waiting();
 9594    let fake_server = fake_servers.next().await.unwrap();
 9595
 9596    let format = editor
 9597        .update_in(cx, |editor, window, cx| {
 9598            editor.perform_code_action_kind(
 9599                project.clone(),
 9600                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9601                window,
 9602                cx,
 9603            )
 9604        })
 9605        .unwrap();
 9606    fake_server
 9607        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9608            assert_eq!(
 9609                params.text_document.uri,
 9610                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9611            );
 9612            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9613                lsp::CodeAction {
 9614                    title: "Organize Imports".to_string(),
 9615                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9616                    edit: Some(lsp::WorkspaceEdit {
 9617                        changes: Some(
 9618                            [(
 9619                                params.text_document.uri.clone(),
 9620                                vec![lsp::TextEdit::new(
 9621                                    lsp::Range::new(
 9622                                        lsp::Position::new(1, 0),
 9623                                        lsp::Position::new(2, 0),
 9624                                    ),
 9625                                    "".to_string(),
 9626                                )],
 9627                            )]
 9628                            .into_iter()
 9629                            .collect(),
 9630                        ),
 9631                        ..Default::default()
 9632                    }),
 9633                    ..Default::default()
 9634                },
 9635            )]))
 9636        })
 9637        .next()
 9638        .await;
 9639    cx.executor().start_waiting();
 9640    format.await;
 9641    assert_eq!(
 9642        editor.update(cx, |editor, cx| editor.text(cx)),
 9643        "import { a } from 'module';\n\nconst x = a;\n"
 9644    );
 9645
 9646    editor.update_in(cx, |editor, window, cx| {
 9647        editor.set_text(
 9648            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9649            window,
 9650            cx,
 9651        )
 9652    });
 9653    // Ensure we don't lock if code action hangs.
 9654    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9655        move |params, _| async move {
 9656            assert_eq!(
 9657                params.text_document.uri,
 9658                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9659            );
 9660            futures::future::pending::<()>().await;
 9661            unreachable!()
 9662        },
 9663    );
 9664    let format = editor
 9665        .update_in(cx, |editor, window, cx| {
 9666            editor.perform_code_action_kind(
 9667                project,
 9668                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9669                window,
 9670                cx,
 9671            )
 9672        })
 9673        .unwrap();
 9674    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9675    cx.executor().start_waiting();
 9676    format.await;
 9677    assert_eq!(
 9678        editor.update(cx, |editor, cx| editor.text(cx)),
 9679        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9680    );
 9681}
 9682
 9683#[gpui::test]
 9684async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9685    init_test(cx, |_| {});
 9686
 9687    let mut cx = EditorLspTestContext::new_rust(
 9688        lsp::ServerCapabilities {
 9689            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9690            ..Default::default()
 9691        },
 9692        cx,
 9693    )
 9694    .await;
 9695
 9696    cx.set_state(indoc! {"
 9697        one.twoˇ
 9698    "});
 9699
 9700    // The format request takes a long time. When it completes, it inserts
 9701    // a newline and an indent before the `.`
 9702    cx.lsp
 9703        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9704            let executor = cx.background_executor().clone();
 9705            async move {
 9706                executor.timer(Duration::from_millis(100)).await;
 9707                Ok(Some(vec![lsp::TextEdit {
 9708                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9709                    new_text: "\n    ".into(),
 9710                }]))
 9711            }
 9712        });
 9713
 9714    // Submit a format request.
 9715    let format_1 = cx
 9716        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9717        .unwrap();
 9718    cx.executor().run_until_parked();
 9719
 9720    // Submit a second format request.
 9721    let format_2 = cx
 9722        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9723        .unwrap();
 9724    cx.executor().run_until_parked();
 9725
 9726    // Wait for both format requests to complete
 9727    cx.executor().advance_clock(Duration::from_millis(200));
 9728    cx.executor().start_waiting();
 9729    format_1.await.unwrap();
 9730    cx.executor().start_waiting();
 9731    format_2.await.unwrap();
 9732
 9733    // The formatting edits only happens once.
 9734    cx.assert_editor_state(indoc! {"
 9735        one
 9736            .twoˇ
 9737    "});
 9738}
 9739
 9740#[gpui::test]
 9741async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9742    init_test(cx, |settings| {
 9743        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9744    });
 9745
 9746    let mut cx = EditorLspTestContext::new_rust(
 9747        lsp::ServerCapabilities {
 9748            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9749            ..Default::default()
 9750        },
 9751        cx,
 9752    )
 9753    .await;
 9754
 9755    // Set up a buffer white some trailing whitespace and no trailing newline.
 9756    cx.set_state(
 9757        &[
 9758            "one ",   //
 9759            "twoˇ",   //
 9760            "three ", //
 9761            "four",   //
 9762        ]
 9763        .join("\n"),
 9764    );
 9765
 9766    // Submit a format request.
 9767    let format = cx
 9768        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9769        .unwrap();
 9770
 9771    // Record which buffer changes have been sent to the language server
 9772    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9773    cx.lsp
 9774        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9775            let buffer_changes = buffer_changes.clone();
 9776            move |params, _| {
 9777                buffer_changes.lock().extend(
 9778                    params
 9779                        .content_changes
 9780                        .into_iter()
 9781                        .map(|e| (e.range.unwrap(), e.text)),
 9782                );
 9783            }
 9784        });
 9785
 9786    // Handle formatting requests to the language server.
 9787    cx.lsp
 9788        .set_request_handler::<lsp::request::Formatting, _, _>({
 9789            let buffer_changes = buffer_changes.clone();
 9790            move |_, _| {
 9791                // When formatting is requested, trailing whitespace has already been stripped,
 9792                // and the trailing newline has already been added.
 9793                assert_eq!(
 9794                    &buffer_changes.lock()[1..],
 9795                    &[
 9796                        (
 9797                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9798                            "".into()
 9799                        ),
 9800                        (
 9801                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9802                            "".into()
 9803                        ),
 9804                        (
 9805                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9806                            "\n".into()
 9807                        ),
 9808                    ]
 9809                );
 9810
 9811                // Insert blank lines between each line of the buffer.
 9812                async move {
 9813                    Ok(Some(vec![
 9814                        lsp::TextEdit {
 9815                            range: lsp::Range::new(
 9816                                lsp::Position::new(1, 0),
 9817                                lsp::Position::new(1, 0),
 9818                            ),
 9819                            new_text: "\n".into(),
 9820                        },
 9821                        lsp::TextEdit {
 9822                            range: lsp::Range::new(
 9823                                lsp::Position::new(2, 0),
 9824                                lsp::Position::new(2, 0),
 9825                            ),
 9826                            new_text: "\n".into(),
 9827                        },
 9828                    ]))
 9829                }
 9830            }
 9831        });
 9832
 9833    // After formatting the buffer, the trailing whitespace is stripped,
 9834    // a newline is appended, and the edits provided by the language server
 9835    // have been applied.
 9836    format.await.unwrap();
 9837    cx.assert_editor_state(
 9838        &[
 9839            "one",   //
 9840            "",      //
 9841            "twoˇ",  //
 9842            "",      //
 9843            "three", //
 9844            "four",  //
 9845            "",      //
 9846        ]
 9847        .join("\n"),
 9848    );
 9849
 9850    // Undoing the formatting undoes the trailing whitespace removal, the
 9851    // trailing newline, and the LSP edits.
 9852    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9853    cx.assert_editor_state(
 9854        &[
 9855            "one ",   //
 9856            "twoˇ",   //
 9857            "three ", //
 9858            "four",   //
 9859        ]
 9860        .join("\n"),
 9861    );
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9866    cx: &mut TestAppContext,
 9867) {
 9868    init_test(cx, |_| {});
 9869
 9870    cx.update(|cx| {
 9871        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9872            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9873                settings.auto_signature_help = Some(true);
 9874            });
 9875        });
 9876    });
 9877
 9878    let mut cx = EditorLspTestContext::new_rust(
 9879        lsp::ServerCapabilities {
 9880            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9881                ..Default::default()
 9882            }),
 9883            ..Default::default()
 9884        },
 9885        cx,
 9886    )
 9887    .await;
 9888
 9889    let language = Language::new(
 9890        LanguageConfig {
 9891            name: "Rust".into(),
 9892            brackets: BracketPairConfig {
 9893                pairs: vec![
 9894                    BracketPair {
 9895                        start: "{".to_string(),
 9896                        end: "}".to_string(),
 9897                        close: true,
 9898                        surround: true,
 9899                        newline: true,
 9900                    },
 9901                    BracketPair {
 9902                        start: "(".to_string(),
 9903                        end: ")".to_string(),
 9904                        close: true,
 9905                        surround: true,
 9906                        newline: true,
 9907                    },
 9908                    BracketPair {
 9909                        start: "/*".to_string(),
 9910                        end: " */".to_string(),
 9911                        close: true,
 9912                        surround: true,
 9913                        newline: true,
 9914                    },
 9915                    BracketPair {
 9916                        start: "[".to_string(),
 9917                        end: "]".to_string(),
 9918                        close: false,
 9919                        surround: false,
 9920                        newline: true,
 9921                    },
 9922                    BracketPair {
 9923                        start: "\"".to_string(),
 9924                        end: "\"".to_string(),
 9925                        close: true,
 9926                        surround: true,
 9927                        newline: false,
 9928                    },
 9929                    BracketPair {
 9930                        start: "<".to_string(),
 9931                        end: ">".to_string(),
 9932                        close: false,
 9933                        surround: true,
 9934                        newline: true,
 9935                    },
 9936                ],
 9937                ..Default::default()
 9938            },
 9939            autoclose_before: "})]".to_string(),
 9940            ..Default::default()
 9941        },
 9942        Some(tree_sitter_rust::LANGUAGE.into()),
 9943    );
 9944    let language = Arc::new(language);
 9945
 9946    cx.language_registry().add(language.clone());
 9947    cx.update_buffer(|buffer, cx| {
 9948        buffer.set_language(Some(language), cx);
 9949    });
 9950
 9951    cx.set_state(
 9952        &r#"
 9953            fn main() {
 9954                sampleˇ
 9955            }
 9956        "#
 9957        .unindent(),
 9958    );
 9959
 9960    cx.update_editor(|editor, window, cx| {
 9961        editor.handle_input("(", window, cx);
 9962    });
 9963    cx.assert_editor_state(
 9964        &"
 9965            fn main() {
 9966                sample(ˇ)
 9967            }
 9968        "
 9969        .unindent(),
 9970    );
 9971
 9972    let mocked_response = lsp::SignatureHelp {
 9973        signatures: vec![lsp::SignatureInformation {
 9974            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9975            documentation: None,
 9976            parameters: Some(vec![
 9977                lsp::ParameterInformation {
 9978                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9979                    documentation: None,
 9980                },
 9981                lsp::ParameterInformation {
 9982                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9983                    documentation: None,
 9984                },
 9985            ]),
 9986            active_parameter: None,
 9987        }],
 9988        active_signature: Some(0),
 9989        active_parameter: Some(0),
 9990    };
 9991    handle_signature_help_request(&mut cx, mocked_response).await;
 9992
 9993    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9994        .await;
 9995
 9996    cx.editor(|editor, _, _| {
 9997        let signature_help_state = editor.signature_help_state.popover().cloned();
 9998        assert_eq!(
 9999            signature_help_state.unwrap().label,
10000            "param1: u8, param2: u8"
10001        );
10002    });
10003}
10004
10005#[gpui::test]
10006async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10007    init_test(cx, |_| {});
10008
10009    cx.update(|cx| {
10010        cx.update_global::<SettingsStore, _>(|settings, cx| {
10011            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10012                settings.auto_signature_help = Some(false);
10013                settings.show_signature_help_after_edits = Some(false);
10014            });
10015        });
10016    });
10017
10018    let mut cx = EditorLspTestContext::new_rust(
10019        lsp::ServerCapabilities {
10020            signature_help_provider: Some(lsp::SignatureHelpOptions {
10021                ..Default::default()
10022            }),
10023            ..Default::default()
10024        },
10025        cx,
10026    )
10027    .await;
10028
10029    let language = Language::new(
10030        LanguageConfig {
10031            name: "Rust".into(),
10032            brackets: BracketPairConfig {
10033                pairs: vec![
10034                    BracketPair {
10035                        start: "{".to_string(),
10036                        end: "}".to_string(),
10037                        close: true,
10038                        surround: true,
10039                        newline: true,
10040                    },
10041                    BracketPair {
10042                        start: "(".to_string(),
10043                        end: ")".to_string(),
10044                        close: true,
10045                        surround: true,
10046                        newline: true,
10047                    },
10048                    BracketPair {
10049                        start: "/*".to_string(),
10050                        end: " */".to_string(),
10051                        close: true,
10052                        surround: true,
10053                        newline: true,
10054                    },
10055                    BracketPair {
10056                        start: "[".to_string(),
10057                        end: "]".to_string(),
10058                        close: false,
10059                        surround: false,
10060                        newline: true,
10061                    },
10062                    BracketPair {
10063                        start: "\"".to_string(),
10064                        end: "\"".to_string(),
10065                        close: true,
10066                        surround: true,
10067                        newline: false,
10068                    },
10069                    BracketPair {
10070                        start: "<".to_string(),
10071                        end: ">".to_string(),
10072                        close: false,
10073                        surround: true,
10074                        newline: true,
10075                    },
10076                ],
10077                ..Default::default()
10078            },
10079            autoclose_before: "})]".to_string(),
10080            ..Default::default()
10081        },
10082        Some(tree_sitter_rust::LANGUAGE.into()),
10083    );
10084    let language = Arc::new(language);
10085
10086    cx.language_registry().add(language.clone());
10087    cx.update_buffer(|buffer, cx| {
10088        buffer.set_language(Some(language), cx);
10089    });
10090
10091    // Ensure that signature_help is not called when no signature help is enabled.
10092    cx.set_state(
10093        &r#"
10094            fn main() {
10095                sampleˇ
10096            }
10097        "#
10098        .unindent(),
10099    );
10100    cx.update_editor(|editor, window, cx| {
10101        editor.handle_input("(", window, cx);
10102    });
10103    cx.assert_editor_state(
10104        &"
10105            fn main() {
10106                sample(ˇ)
10107            }
10108        "
10109        .unindent(),
10110    );
10111    cx.editor(|editor, _, _| {
10112        assert!(editor.signature_help_state.task().is_none());
10113    });
10114
10115    let mocked_response = lsp::SignatureHelp {
10116        signatures: vec![lsp::SignatureInformation {
10117            label: "fn sample(param1: u8, param2: u8)".to_string(),
10118            documentation: None,
10119            parameters: Some(vec![
10120                lsp::ParameterInformation {
10121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10122                    documentation: None,
10123                },
10124                lsp::ParameterInformation {
10125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10126                    documentation: None,
10127                },
10128            ]),
10129            active_parameter: None,
10130        }],
10131        active_signature: Some(0),
10132        active_parameter: Some(0),
10133    };
10134
10135    // Ensure that signature_help is called when enabled afte edits
10136    cx.update(|_, cx| {
10137        cx.update_global::<SettingsStore, _>(|settings, cx| {
10138            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10139                settings.auto_signature_help = Some(false);
10140                settings.show_signature_help_after_edits = Some(true);
10141            });
10142        });
10143    });
10144    cx.set_state(
10145        &r#"
10146            fn main() {
10147                sampleˇ
10148            }
10149        "#
10150        .unindent(),
10151    );
10152    cx.update_editor(|editor, window, cx| {
10153        editor.handle_input("(", window, cx);
10154    });
10155    cx.assert_editor_state(
10156        &"
10157            fn main() {
10158                sample(ˇ)
10159            }
10160        "
10161        .unindent(),
10162    );
10163    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10164    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10165        .await;
10166    cx.update_editor(|editor, _, _| {
10167        let signature_help_state = editor.signature_help_state.popover().cloned();
10168        assert!(signature_help_state.is_some());
10169        assert_eq!(
10170            signature_help_state.unwrap().label,
10171            "param1: u8, param2: u8"
10172        );
10173        editor.signature_help_state = SignatureHelpState::default();
10174    });
10175
10176    // Ensure that signature_help is called when auto signature help override is enabled
10177    cx.update(|_, cx| {
10178        cx.update_global::<SettingsStore, _>(|settings, cx| {
10179            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10180                settings.auto_signature_help = Some(true);
10181                settings.show_signature_help_after_edits = Some(false);
10182            });
10183        });
10184    });
10185    cx.set_state(
10186        &r#"
10187            fn main() {
10188                sampleˇ
10189            }
10190        "#
10191        .unindent(),
10192    );
10193    cx.update_editor(|editor, window, cx| {
10194        editor.handle_input("(", window, cx);
10195    });
10196    cx.assert_editor_state(
10197        &"
10198            fn main() {
10199                sample(ˇ)
10200            }
10201        "
10202        .unindent(),
10203    );
10204    handle_signature_help_request(&mut cx, mocked_response).await;
10205    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10206        .await;
10207    cx.editor(|editor, _, _| {
10208        let signature_help_state = editor.signature_help_state.popover().cloned();
10209        assert!(signature_help_state.is_some());
10210        assert_eq!(
10211            signature_help_state.unwrap().label,
10212            "param1: u8, param2: u8"
10213        );
10214    });
10215}
10216
10217#[gpui::test]
10218async fn test_signature_help(cx: &mut TestAppContext) {
10219    init_test(cx, |_| {});
10220    cx.update(|cx| {
10221        cx.update_global::<SettingsStore, _>(|settings, cx| {
10222            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10223                settings.auto_signature_help = Some(true);
10224            });
10225        });
10226    });
10227
10228    let mut cx = EditorLspTestContext::new_rust(
10229        lsp::ServerCapabilities {
10230            signature_help_provider: Some(lsp::SignatureHelpOptions {
10231                ..Default::default()
10232            }),
10233            ..Default::default()
10234        },
10235        cx,
10236    )
10237    .await;
10238
10239    // A test that directly calls `show_signature_help`
10240    cx.update_editor(|editor, window, cx| {
10241        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10242    });
10243
10244    let mocked_response = lsp::SignatureHelp {
10245        signatures: vec![lsp::SignatureInformation {
10246            label: "fn sample(param1: u8, param2: u8)".to_string(),
10247            documentation: None,
10248            parameters: Some(vec![
10249                lsp::ParameterInformation {
10250                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10251                    documentation: None,
10252                },
10253                lsp::ParameterInformation {
10254                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10255                    documentation: None,
10256                },
10257            ]),
10258            active_parameter: None,
10259        }],
10260        active_signature: Some(0),
10261        active_parameter: Some(0),
10262    };
10263    handle_signature_help_request(&mut cx, mocked_response).await;
10264
10265    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10266        .await;
10267
10268    cx.editor(|editor, _, _| {
10269        let signature_help_state = editor.signature_help_state.popover().cloned();
10270        assert!(signature_help_state.is_some());
10271        assert_eq!(
10272            signature_help_state.unwrap().label,
10273            "param1: u8, param2: u8"
10274        );
10275    });
10276
10277    // When exiting outside from inside the brackets, `signature_help` is closed.
10278    cx.set_state(indoc! {"
10279        fn main() {
10280            sample(ˇ);
10281        }
10282
10283        fn sample(param1: u8, param2: u8) {}
10284    "});
10285
10286    cx.update_editor(|editor, window, cx| {
10287        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10288    });
10289
10290    let mocked_response = lsp::SignatureHelp {
10291        signatures: Vec::new(),
10292        active_signature: None,
10293        active_parameter: None,
10294    };
10295    handle_signature_help_request(&mut cx, mocked_response).await;
10296
10297    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10298        .await;
10299
10300    cx.editor(|editor, _, _| {
10301        assert!(!editor.signature_help_state.is_shown());
10302    });
10303
10304    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10305    cx.set_state(indoc! {"
10306        fn main() {
10307            sample(ˇ);
10308        }
10309
10310        fn sample(param1: u8, param2: u8) {}
10311    "});
10312
10313    let mocked_response = lsp::SignatureHelp {
10314        signatures: vec![lsp::SignatureInformation {
10315            label: "fn sample(param1: u8, param2: u8)".to_string(),
10316            documentation: None,
10317            parameters: Some(vec![
10318                lsp::ParameterInformation {
10319                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10320                    documentation: None,
10321                },
10322                lsp::ParameterInformation {
10323                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10324                    documentation: None,
10325                },
10326            ]),
10327            active_parameter: None,
10328        }],
10329        active_signature: Some(0),
10330        active_parameter: Some(0),
10331    };
10332    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10333    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10334        .await;
10335    cx.editor(|editor, _, _| {
10336        assert!(editor.signature_help_state.is_shown());
10337    });
10338
10339    // Restore the popover with more parameter input
10340    cx.set_state(indoc! {"
10341        fn main() {
10342            sample(param1, param2ˇ);
10343        }
10344
10345        fn sample(param1: u8, param2: u8) {}
10346    "});
10347
10348    let mocked_response = lsp::SignatureHelp {
10349        signatures: vec![lsp::SignatureInformation {
10350            label: "fn sample(param1: u8, param2: u8)".to_string(),
10351            documentation: None,
10352            parameters: Some(vec![
10353                lsp::ParameterInformation {
10354                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10355                    documentation: None,
10356                },
10357                lsp::ParameterInformation {
10358                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10359                    documentation: None,
10360                },
10361            ]),
10362            active_parameter: None,
10363        }],
10364        active_signature: Some(0),
10365        active_parameter: Some(1),
10366    };
10367    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10368    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10369        .await;
10370
10371    // When selecting a range, the popover is gone.
10372    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10373    cx.update_editor(|editor, window, cx| {
10374        editor.change_selections(None, window, cx, |s| {
10375            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10376        })
10377    });
10378    cx.assert_editor_state(indoc! {"
10379        fn main() {
10380            sample(param1, «ˇparam2»);
10381        }
10382
10383        fn sample(param1: u8, param2: u8) {}
10384    "});
10385    cx.editor(|editor, _, _| {
10386        assert!(!editor.signature_help_state.is_shown());
10387    });
10388
10389    // When unselecting again, the popover is back if within the brackets.
10390    cx.update_editor(|editor, window, cx| {
10391        editor.change_selections(None, window, cx, |s| {
10392            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10393        })
10394    });
10395    cx.assert_editor_state(indoc! {"
10396        fn main() {
10397            sample(param1, ˇparam2);
10398        }
10399
10400        fn sample(param1: u8, param2: u8) {}
10401    "});
10402    handle_signature_help_request(&mut cx, mocked_response).await;
10403    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10404        .await;
10405    cx.editor(|editor, _, _| {
10406        assert!(editor.signature_help_state.is_shown());
10407    });
10408
10409    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10410    cx.update_editor(|editor, window, cx| {
10411        editor.change_selections(None, window, cx, |s| {
10412            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10413            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10414        })
10415    });
10416    cx.assert_editor_state(indoc! {"
10417        fn main() {
10418            sample(param1, ˇparam2);
10419        }
10420
10421        fn sample(param1: u8, param2: u8) {}
10422    "});
10423
10424    let mocked_response = lsp::SignatureHelp {
10425        signatures: vec![lsp::SignatureInformation {
10426            label: "fn sample(param1: u8, param2: u8)".to_string(),
10427            documentation: None,
10428            parameters: Some(vec![
10429                lsp::ParameterInformation {
10430                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10431                    documentation: None,
10432                },
10433                lsp::ParameterInformation {
10434                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10435                    documentation: None,
10436                },
10437            ]),
10438            active_parameter: None,
10439        }],
10440        active_signature: Some(0),
10441        active_parameter: Some(1),
10442    };
10443    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10444    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10445        .await;
10446    cx.update_editor(|editor, _, cx| {
10447        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10448    });
10449    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10450        .await;
10451    cx.update_editor(|editor, window, cx| {
10452        editor.change_selections(None, window, cx, |s| {
10453            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10454        })
10455    });
10456    cx.assert_editor_state(indoc! {"
10457        fn main() {
10458            sample(param1, «ˇparam2»);
10459        }
10460
10461        fn sample(param1: u8, param2: u8) {}
10462    "});
10463    cx.update_editor(|editor, window, cx| {
10464        editor.change_selections(None, window, cx, |s| {
10465            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10466        })
10467    });
10468    cx.assert_editor_state(indoc! {"
10469        fn main() {
10470            sample(param1, ˇparam2);
10471        }
10472
10473        fn sample(param1: u8, param2: u8) {}
10474    "});
10475    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10476        .await;
10477}
10478
10479#[gpui::test]
10480async fn test_completion_mode(cx: &mut TestAppContext) {
10481    init_test(cx, |_| {});
10482    let mut cx = EditorLspTestContext::new_rust(
10483        lsp::ServerCapabilities {
10484            completion_provider: Some(lsp::CompletionOptions {
10485                resolve_provider: Some(true),
10486                ..Default::default()
10487            }),
10488            ..Default::default()
10489        },
10490        cx,
10491    )
10492    .await;
10493
10494    struct Run {
10495        run_description: &'static str,
10496        initial_state: String,
10497        buffer_marked_text: String,
10498        completion_label: &'static str,
10499        completion_text: &'static str,
10500        expected_with_insert_mode: String,
10501        expected_with_replace_mode: String,
10502        expected_with_replace_subsequence_mode: String,
10503        expected_with_replace_suffix_mode: String,
10504    }
10505
10506    let runs = [
10507        Run {
10508            run_description: "Start of word matches completion text",
10509            initial_state: "before ediˇ after".into(),
10510            buffer_marked_text: "before <edi|> after".into(),
10511            completion_label: "editor",
10512            completion_text: "editor",
10513            expected_with_insert_mode: "before editorˇ after".into(),
10514            expected_with_replace_mode: "before editorˇ after".into(),
10515            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10516            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10517        },
10518        Run {
10519            run_description: "Accept same text at the middle of the word",
10520            initial_state: "before ediˇtor after".into(),
10521            buffer_marked_text: "before <edi|tor> after".into(),
10522            completion_label: "editor",
10523            completion_text: "editor",
10524            expected_with_insert_mode: "before editorˇtor after".into(),
10525            expected_with_replace_mode: "before editorˇ after".into(),
10526            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10527            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10528        },
10529        Run {
10530            run_description: "End of word matches completion text -- cursor at end",
10531            initial_state: "before torˇ after".into(),
10532            buffer_marked_text: "before <tor|> after".into(),
10533            completion_label: "editor",
10534            completion_text: "editor",
10535            expected_with_insert_mode: "before editorˇ after".into(),
10536            expected_with_replace_mode: "before editorˇ after".into(),
10537            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10538            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10539        },
10540        Run {
10541            run_description: "End of word matches completion text -- cursor at start",
10542            initial_state: "before ˇtor after".into(),
10543            buffer_marked_text: "before <|tor> after".into(),
10544            completion_label: "editor",
10545            completion_text: "editor",
10546            expected_with_insert_mode: "before editorˇtor after".into(),
10547            expected_with_replace_mode: "before editorˇ after".into(),
10548            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10549            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10550        },
10551        Run {
10552            run_description: "Prepend text containing whitespace",
10553            initial_state: "pˇfield: bool".into(),
10554            buffer_marked_text: "<p|field>: bool".into(),
10555            completion_label: "pub ",
10556            completion_text: "pub ",
10557            expected_with_insert_mode: "pub ˇfield: bool".into(),
10558            expected_with_replace_mode: "pub ˇ: bool".into(),
10559            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10560            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10561        },
10562        Run {
10563            run_description: "Add element to start of list",
10564            initial_state: "[element_ˇelement_2]".into(),
10565            buffer_marked_text: "[<element_|element_2>]".into(),
10566            completion_label: "element_1",
10567            completion_text: "element_1",
10568            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10569            expected_with_replace_mode: "[element_1ˇ]".into(),
10570            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10571            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10572        },
10573        Run {
10574            run_description: "Add element to start of list -- first and second elements are equal",
10575            initial_state: "[elˇelement]".into(),
10576            buffer_marked_text: "[<el|element>]".into(),
10577            completion_label: "element",
10578            completion_text: "element",
10579            expected_with_insert_mode: "[elementˇelement]".into(),
10580            expected_with_replace_mode: "[elementˇ]".into(),
10581            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10582            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10583        },
10584        Run {
10585            run_description: "Ends with matching suffix",
10586            initial_state: "SubˇError".into(),
10587            buffer_marked_text: "<Sub|Error>".into(),
10588            completion_label: "SubscriptionError",
10589            completion_text: "SubscriptionError",
10590            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10591            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10592            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10593            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10594        },
10595        Run {
10596            run_description: "Suffix is a subsequence -- contiguous",
10597            initial_state: "SubˇErr".into(),
10598            buffer_marked_text: "<Sub|Err>".into(),
10599            completion_label: "SubscriptionError",
10600            completion_text: "SubscriptionError",
10601            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10602            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10603            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10604            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10605        },
10606        Run {
10607            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10608            initial_state: "Suˇscrirr".into(),
10609            buffer_marked_text: "<Su|scrirr>".into(),
10610            completion_label: "SubscriptionError",
10611            completion_text: "SubscriptionError",
10612            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10613            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10614            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10615            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10616        },
10617        Run {
10618            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10619            initial_state: "foo(indˇix)".into(),
10620            buffer_marked_text: "foo(<ind|ix>)".into(),
10621            completion_label: "node_index",
10622            completion_text: "node_index",
10623            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10624            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10625            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10626            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10627        },
10628        Run {
10629            run_description: "Replace range ends before cursor - should extend to cursor",
10630            initial_state: "before editˇo after".into(),
10631            buffer_marked_text: "before <{ed}>it|o after".into(),
10632            completion_label: "editor",
10633            completion_text: "editor",
10634            expected_with_insert_mode: "before editorˇo after".into(),
10635            expected_with_replace_mode: "before editorˇo after".into(),
10636            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
10637            expected_with_replace_suffix_mode: "before editorˇo after".into(),
10638        },
10639        Run {
10640            run_description: "Uses label for suffix matching",
10641            initial_state: "before ediˇtor after".into(),
10642            buffer_marked_text: "before <edi|tor> after".into(),
10643            completion_label: "editor",
10644            completion_text: "editor()",
10645            expected_with_insert_mode: "before editor()ˇtor after".into(),
10646            expected_with_replace_mode: "before editor()ˇ after".into(),
10647            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
10648            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
10649        },
10650        Run {
10651            run_description: "Case insensitive subsequence and suffix matching",
10652            initial_state: "before EDiˇtoR after".into(),
10653            buffer_marked_text: "before <EDi|toR> after".into(),
10654            completion_label: "editor",
10655            completion_text: "editor",
10656            expected_with_insert_mode: "before editorˇtoR after".into(),
10657            expected_with_replace_mode: "before editorˇ after".into(),
10658            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10659            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10660        },
10661    ];
10662
10663    for run in runs {
10664        let run_variations = [
10665            (LspInsertMode::Insert, run.expected_with_insert_mode),
10666            (LspInsertMode::Replace, run.expected_with_replace_mode),
10667            (
10668                LspInsertMode::ReplaceSubsequence,
10669                run.expected_with_replace_subsequence_mode,
10670            ),
10671            (
10672                LspInsertMode::ReplaceSuffix,
10673                run.expected_with_replace_suffix_mode,
10674            ),
10675        ];
10676
10677        for (lsp_insert_mode, expected_text) in run_variations {
10678            eprintln!(
10679                "run = {:?}, mode = {lsp_insert_mode:.?}",
10680                run.run_description,
10681            );
10682
10683            update_test_language_settings(&mut cx, |settings| {
10684                settings.defaults.completions = Some(CompletionSettings {
10685                    lsp_insert_mode,
10686                    words: WordsCompletionMode::Disabled,
10687                    lsp: true,
10688                    lsp_fetch_timeout_ms: 0,
10689                });
10690            });
10691
10692            cx.set_state(&run.initial_state);
10693            cx.update_editor(|editor, window, cx| {
10694                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10695            });
10696
10697            let counter = Arc::new(AtomicUsize::new(0));
10698            handle_completion_request_with_insert_and_replace(
10699                &mut cx,
10700                &run.buffer_marked_text,
10701                vec![(run.completion_label, run.completion_text)],
10702                counter.clone(),
10703            )
10704            .await;
10705            cx.condition(|editor, _| editor.context_menu_visible())
10706                .await;
10707            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10708
10709            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10710                editor
10711                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10712                    .unwrap()
10713            });
10714            cx.assert_editor_state(&expected_text);
10715            handle_resolve_completion_request(&mut cx, None).await;
10716            apply_additional_edits.await.unwrap();
10717        }
10718    }
10719}
10720
10721#[gpui::test]
10722async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10723    init_test(cx, |_| {});
10724    let mut cx = EditorLspTestContext::new_rust(
10725        lsp::ServerCapabilities {
10726            completion_provider: Some(lsp::CompletionOptions {
10727                resolve_provider: Some(true),
10728                ..Default::default()
10729            }),
10730            ..Default::default()
10731        },
10732        cx,
10733    )
10734    .await;
10735
10736    let initial_state = "SubˇError";
10737    let buffer_marked_text = "<Sub|Error>";
10738    let completion_text = "SubscriptionError";
10739    let expected_with_insert_mode = "SubscriptionErrorˇError";
10740    let expected_with_replace_mode = "SubscriptionErrorˇ";
10741
10742    update_test_language_settings(&mut cx, |settings| {
10743        settings.defaults.completions = Some(CompletionSettings {
10744            words: WordsCompletionMode::Disabled,
10745            // set the opposite here to ensure that the action is overriding the default behavior
10746            lsp_insert_mode: LspInsertMode::Insert,
10747            lsp: true,
10748            lsp_fetch_timeout_ms: 0,
10749        });
10750    });
10751
10752    cx.set_state(initial_state);
10753    cx.update_editor(|editor, window, cx| {
10754        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10755    });
10756
10757    let counter = Arc::new(AtomicUsize::new(0));
10758    handle_completion_request_with_insert_and_replace(
10759        &mut cx,
10760        &buffer_marked_text,
10761        vec![(completion_text, completion_text)],
10762        counter.clone(),
10763    )
10764    .await;
10765    cx.condition(|editor, _| editor.context_menu_visible())
10766        .await;
10767    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10768
10769    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10770        editor
10771            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10772            .unwrap()
10773    });
10774    cx.assert_editor_state(&expected_with_replace_mode);
10775    handle_resolve_completion_request(&mut cx, None).await;
10776    apply_additional_edits.await.unwrap();
10777
10778    update_test_language_settings(&mut cx, |settings| {
10779        settings.defaults.completions = Some(CompletionSettings {
10780            words: WordsCompletionMode::Disabled,
10781            // set the opposite here to ensure that the action is overriding the default behavior
10782            lsp_insert_mode: LspInsertMode::Replace,
10783            lsp: true,
10784            lsp_fetch_timeout_ms: 0,
10785        });
10786    });
10787
10788    cx.set_state(initial_state);
10789    cx.update_editor(|editor, window, cx| {
10790        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10791    });
10792    handle_completion_request_with_insert_and_replace(
10793        &mut cx,
10794        &buffer_marked_text,
10795        vec![(completion_text, completion_text)],
10796        counter.clone(),
10797    )
10798    .await;
10799    cx.condition(|editor, _| editor.context_menu_visible())
10800        .await;
10801    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10802
10803    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10804        editor
10805            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10806            .unwrap()
10807    });
10808    cx.assert_editor_state(&expected_with_insert_mode);
10809    handle_resolve_completion_request(&mut cx, None).await;
10810    apply_additional_edits.await.unwrap();
10811}
10812
10813#[gpui::test]
10814async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10815    init_test(cx, |_| {});
10816    let mut cx = EditorLspTestContext::new_rust(
10817        lsp::ServerCapabilities {
10818            completion_provider: Some(lsp::CompletionOptions {
10819                resolve_provider: Some(true),
10820                ..Default::default()
10821            }),
10822            ..Default::default()
10823        },
10824        cx,
10825    )
10826    .await;
10827
10828    // scenario: surrounding text matches completion text
10829    let completion_text = "to_offset";
10830    let initial_state = indoc! {"
10831        1. buf.to_offˇsuffix
10832        2. buf.to_offˇsuf
10833        3. buf.to_offˇfix
10834        4. buf.to_offˇ
10835        5. into_offˇensive
10836        6. ˇsuffix
10837        7. let ˇ //
10838        8. aaˇzz
10839        9. buf.to_off«zzzzzˇ»suffix
10840        10. buf.«ˇzzzzz»suffix
10841        11. to_off«ˇzzzzz»
10842
10843        buf.to_offˇsuffix  // newest cursor
10844    "};
10845    let completion_marked_buffer = indoc! {"
10846        1. buf.to_offsuffix
10847        2. buf.to_offsuf
10848        3. buf.to_offfix
10849        4. buf.to_off
10850        5. into_offensive
10851        6. suffix
10852        7. let  //
10853        8. aazz
10854        9. buf.to_offzzzzzsuffix
10855        10. buf.zzzzzsuffix
10856        11. to_offzzzzz
10857
10858        buf.<to_off|suffix>  // newest cursor
10859    "};
10860    let expected = indoc! {"
10861        1. buf.to_offsetˇ
10862        2. buf.to_offsetˇsuf
10863        3. buf.to_offsetˇfix
10864        4. buf.to_offsetˇ
10865        5. into_offsetˇensive
10866        6. to_offsetˇsuffix
10867        7. let to_offsetˇ //
10868        8. aato_offsetˇzz
10869        9. buf.to_offsetˇ
10870        10. buf.to_offsetˇsuffix
10871        11. to_offsetˇ
10872
10873        buf.to_offsetˇ  // newest cursor
10874    "};
10875    cx.set_state(initial_state);
10876    cx.update_editor(|editor, window, cx| {
10877        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10878    });
10879    handle_completion_request_with_insert_and_replace(
10880        &mut cx,
10881        completion_marked_buffer,
10882        vec![(completion_text, completion_text)],
10883        Arc::new(AtomicUsize::new(0)),
10884    )
10885    .await;
10886    cx.condition(|editor, _| editor.context_menu_visible())
10887        .await;
10888    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10889        editor
10890            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10891            .unwrap()
10892    });
10893    cx.assert_editor_state(expected);
10894    handle_resolve_completion_request(&mut cx, None).await;
10895    apply_additional_edits.await.unwrap();
10896
10897    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10898    let completion_text = "foo_and_bar";
10899    let initial_state = indoc! {"
10900        1. ooanbˇ
10901        2. zooanbˇ
10902        3. ooanbˇz
10903        4. zooanbˇz
10904        5. ooanˇ
10905        6. oanbˇ
10906
10907        ooanbˇ
10908    "};
10909    let completion_marked_buffer = indoc! {"
10910        1. ooanb
10911        2. zooanb
10912        3. ooanbz
10913        4. zooanbz
10914        5. ooan
10915        6. oanb
10916
10917        <ooanb|>
10918    "};
10919    let expected = indoc! {"
10920        1. foo_and_barˇ
10921        2. zfoo_and_barˇ
10922        3. foo_and_barˇz
10923        4. zfoo_and_barˇz
10924        5. ooanfoo_and_barˇ
10925        6. oanbfoo_and_barˇ
10926
10927        foo_and_barˇ
10928    "};
10929    cx.set_state(initial_state);
10930    cx.update_editor(|editor, window, cx| {
10931        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10932    });
10933    handle_completion_request_with_insert_and_replace(
10934        &mut cx,
10935        completion_marked_buffer,
10936        vec![(completion_text, completion_text)],
10937        Arc::new(AtomicUsize::new(0)),
10938    )
10939    .await;
10940    cx.condition(|editor, _| editor.context_menu_visible())
10941        .await;
10942    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10943        editor
10944            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10945            .unwrap()
10946    });
10947    cx.assert_editor_state(expected);
10948    handle_resolve_completion_request(&mut cx, None).await;
10949    apply_additional_edits.await.unwrap();
10950
10951    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10952    // (expects the same as if it was inserted at the end)
10953    let completion_text = "foo_and_bar";
10954    let initial_state = indoc! {"
10955        1. ooˇanb
10956        2. zooˇanb
10957        3. ooˇanbz
10958        4. zooˇanbz
10959
10960        ooˇanb
10961    "};
10962    let completion_marked_buffer = indoc! {"
10963        1. ooanb
10964        2. zooanb
10965        3. ooanbz
10966        4. zooanbz
10967
10968        <oo|anb>
10969    "};
10970    let expected = indoc! {"
10971        1. foo_and_barˇ
10972        2. zfoo_and_barˇ
10973        3. foo_and_barˇz
10974        4. zfoo_and_barˇz
10975
10976        foo_and_barˇ
10977    "};
10978    cx.set_state(initial_state);
10979    cx.update_editor(|editor, window, cx| {
10980        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10981    });
10982    handle_completion_request_with_insert_and_replace(
10983        &mut cx,
10984        completion_marked_buffer,
10985        vec![(completion_text, completion_text)],
10986        Arc::new(AtomicUsize::new(0)),
10987    )
10988    .await;
10989    cx.condition(|editor, _| editor.context_menu_visible())
10990        .await;
10991    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10992        editor
10993            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10994            .unwrap()
10995    });
10996    cx.assert_editor_state(expected);
10997    handle_resolve_completion_request(&mut cx, None).await;
10998    apply_additional_edits.await.unwrap();
10999}
11000
11001// This used to crash
11002#[gpui::test]
11003async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11004    init_test(cx, |_| {});
11005
11006    let buffer_text = indoc! {"
11007        fn main() {
11008            10.satu;
11009
11010            //
11011            // separate cursors so they open in different excerpts (manually reproducible)
11012            //
11013
11014            10.satu20;
11015        }
11016    "};
11017    let multibuffer_text_with_selections = indoc! {"
11018        fn main() {
11019            10.satuˇ;
11020
11021            //
11022
11023            //
11024
11025            10.satuˇ20;
11026        }
11027    "};
11028    let expected_multibuffer = indoc! {"
11029        fn main() {
11030            10.saturating_sub()ˇ;
11031
11032            //
11033
11034            //
11035
11036            10.saturating_sub()ˇ;
11037        }
11038    "};
11039
11040    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11041    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11042
11043    let fs = FakeFs::new(cx.executor());
11044    fs.insert_tree(
11045        path!("/a"),
11046        json!({
11047            "main.rs": buffer_text,
11048        }),
11049    )
11050    .await;
11051
11052    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11053    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11054    language_registry.add(rust_lang());
11055    let mut fake_servers = language_registry.register_fake_lsp(
11056        "Rust",
11057        FakeLspAdapter {
11058            capabilities: lsp::ServerCapabilities {
11059                completion_provider: Some(lsp::CompletionOptions {
11060                    resolve_provider: None,
11061                    ..lsp::CompletionOptions::default()
11062                }),
11063                ..lsp::ServerCapabilities::default()
11064            },
11065            ..FakeLspAdapter::default()
11066        },
11067    );
11068    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11069    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11070    let buffer = project
11071        .update(cx, |project, cx| {
11072            project.open_local_buffer(path!("/a/main.rs"), cx)
11073        })
11074        .await
11075        .unwrap();
11076
11077    let multi_buffer = cx.new(|cx| {
11078        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11079        multi_buffer.push_excerpts(
11080            buffer.clone(),
11081            [ExcerptRange::new(0..first_excerpt_end)],
11082            cx,
11083        );
11084        multi_buffer.push_excerpts(
11085            buffer.clone(),
11086            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11087            cx,
11088        );
11089        multi_buffer
11090    });
11091
11092    let editor = workspace
11093        .update(cx, |_, window, cx| {
11094            cx.new(|cx| {
11095                Editor::new(
11096                    EditorMode::Full {
11097                        scale_ui_elements_with_buffer_font_size: false,
11098                        show_active_line_background: false,
11099                        sized_by_content: false,
11100                    },
11101                    multi_buffer.clone(),
11102                    Some(project.clone()),
11103                    window,
11104                    cx,
11105                )
11106            })
11107        })
11108        .unwrap();
11109
11110    let pane = workspace
11111        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11112        .unwrap();
11113    pane.update_in(cx, |pane, window, cx| {
11114        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11115    });
11116
11117    let fake_server = fake_servers.next().await.unwrap();
11118
11119    editor.update_in(cx, |editor, window, cx| {
11120        editor.change_selections(None, window, cx, |s| {
11121            s.select_ranges([
11122                Point::new(1, 11)..Point::new(1, 11),
11123                Point::new(7, 11)..Point::new(7, 11),
11124            ])
11125        });
11126
11127        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11128    });
11129
11130    editor.update_in(cx, |editor, window, cx| {
11131        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11132    });
11133
11134    fake_server
11135        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11136            let completion_item = lsp::CompletionItem {
11137                label: "saturating_sub()".into(),
11138                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11139                    lsp::InsertReplaceEdit {
11140                        new_text: "saturating_sub()".to_owned(),
11141                        insert: lsp::Range::new(
11142                            lsp::Position::new(7, 7),
11143                            lsp::Position::new(7, 11),
11144                        ),
11145                        replace: lsp::Range::new(
11146                            lsp::Position::new(7, 7),
11147                            lsp::Position::new(7, 13),
11148                        ),
11149                    },
11150                )),
11151                ..lsp::CompletionItem::default()
11152            };
11153
11154            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11155        })
11156        .next()
11157        .await
11158        .unwrap();
11159
11160    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11161        .await;
11162
11163    editor
11164        .update_in(cx, |editor, window, cx| {
11165            editor
11166                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11167                .unwrap()
11168        })
11169        .await
11170        .unwrap();
11171
11172    editor.update(cx, |editor, cx| {
11173        assert_text_with_selections(editor, expected_multibuffer, cx);
11174    })
11175}
11176
11177#[gpui::test]
11178async fn test_completion(cx: &mut TestAppContext) {
11179    init_test(cx, |_| {});
11180
11181    let mut cx = EditorLspTestContext::new_rust(
11182        lsp::ServerCapabilities {
11183            completion_provider: Some(lsp::CompletionOptions {
11184                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11185                resolve_provider: Some(true),
11186                ..Default::default()
11187            }),
11188            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11189            ..Default::default()
11190        },
11191        cx,
11192    )
11193    .await;
11194    let counter = Arc::new(AtomicUsize::new(0));
11195
11196    cx.set_state(indoc! {"
11197        oneˇ
11198        two
11199        three
11200    "});
11201    cx.simulate_keystroke(".");
11202    handle_completion_request(
11203        indoc! {"
11204            one.|<>
11205            two
11206            three
11207        "},
11208        vec!["first_completion", "second_completion"],
11209        true,
11210        counter.clone(),
11211        &mut cx,
11212    )
11213    .await;
11214    cx.condition(|editor, _| editor.context_menu_visible())
11215        .await;
11216    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11217
11218    let _handler = handle_signature_help_request(
11219        &mut cx,
11220        lsp::SignatureHelp {
11221            signatures: vec![lsp::SignatureInformation {
11222                label: "test signature".to_string(),
11223                documentation: None,
11224                parameters: Some(vec![lsp::ParameterInformation {
11225                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11226                    documentation: None,
11227                }]),
11228                active_parameter: None,
11229            }],
11230            active_signature: None,
11231            active_parameter: None,
11232        },
11233    );
11234    cx.update_editor(|editor, window, cx| {
11235        assert!(
11236            !editor.signature_help_state.is_shown(),
11237            "No signature help was called for"
11238        );
11239        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11240    });
11241    cx.run_until_parked();
11242    cx.update_editor(|editor, _, _| {
11243        assert!(
11244            !editor.signature_help_state.is_shown(),
11245            "No signature help should be shown when completions menu is open"
11246        );
11247    });
11248
11249    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11250        editor.context_menu_next(&Default::default(), window, cx);
11251        editor
11252            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11253            .unwrap()
11254    });
11255    cx.assert_editor_state(indoc! {"
11256        one.second_completionˇ
11257        two
11258        three
11259    "});
11260
11261    handle_resolve_completion_request(
11262        &mut cx,
11263        Some(vec![
11264            (
11265                //This overlaps with the primary completion edit which is
11266                //misbehavior from the LSP spec, test that we filter it out
11267                indoc! {"
11268                    one.second_ˇcompletion
11269                    two
11270                    threeˇ
11271                "},
11272                "overlapping additional edit",
11273            ),
11274            (
11275                indoc! {"
11276                    one.second_completion
11277                    two
11278                    threeˇ
11279                "},
11280                "\nadditional edit",
11281            ),
11282        ]),
11283    )
11284    .await;
11285    apply_additional_edits.await.unwrap();
11286    cx.assert_editor_state(indoc! {"
11287        one.second_completionˇ
11288        two
11289        three
11290        additional edit
11291    "});
11292
11293    cx.set_state(indoc! {"
11294        one.second_completion
11295        twoˇ
11296        threeˇ
11297        additional edit
11298    "});
11299    cx.simulate_keystroke(" ");
11300    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11301    cx.simulate_keystroke("s");
11302    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11303
11304    cx.assert_editor_state(indoc! {"
11305        one.second_completion
11306        two sˇ
11307        three sˇ
11308        additional edit
11309    "});
11310    handle_completion_request(
11311        indoc! {"
11312            one.second_completion
11313            two s
11314            three <s|>
11315            additional edit
11316        "},
11317        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11318        true,
11319        counter.clone(),
11320        &mut cx,
11321    )
11322    .await;
11323    cx.condition(|editor, _| editor.context_menu_visible())
11324        .await;
11325    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11326
11327    cx.simulate_keystroke("i");
11328
11329    handle_completion_request(
11330        indoc! {"
11331            one.second_completion
11332            two si
11333            three <si|>
11334            additional edit
11335        "},
11336        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11337        true,
11338        counter.clone(),
11339        &mut cx,
11340    )
11341    .await;
11342    cx.condition(|editor, _| editor.context_menu_visible())
11343        .await;
11344    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11345
11346    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11347        editor
11348            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11349            .unwrap()
11350    });
11351    cx.assert_editor_state(indoc! {"
11352        one.second_completion
11353        two sixth_completionˇ
11354        three sixth_completionˇ
11355        additional edit
11356    "});
11357
11358    apply_additional_edits.await.unwrap();
11359
11360    update_test_language_settings(&mut cx, |settings| {
11361        settings.defaults.show_completions_on_input = Some(false);
11362    });
11363    cx.set_state("editorˇ");
11364    cx.simulate_keystroke(".");
11365    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11366    cx.simulate_keystrokes("c l o");
11367    cx.assert_editor_state("editor.cloˇ");
11368    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11369    cx.update_editor(|editor, window, cx| {
11370        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11371    });
11372    handle_completion_request(
11373        "editor.<clo|>",
11374        vec!["close", "clobber"],
11375        true,
11376        counter.clone(),
11377        &mut cx,
11378    )
11379    .await;
11380    cx.condition(|editor, _| editor.context_menu_visible())
11381        .await;
11382    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11383
11384    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11385        editor
11386            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11387            .unwrap()
11388    });
11389    cx.assert_editor_state("editor.closeˇ");
11390    handle_resolve_completion_request(&mut cx, None).await;
11391    apply_additional_edits.await.unwrap();
11392}
11393
11394#[gpui::test]
11395async fn test_completion_reuse(cx: &mut TestAppContext) {
11396    init_test(cx, |_| {});
11397
11398    let mut cx = EditorLspTestContext::new_rust(
11399        lsp::ServerCapabilities {
11400            completion_provider: Some(lsp::CompletionOptions {
11401                trigger_characters: Some(vec![".".to_string()]),
11402                ..Default::default()
11403            }),
11404            ..Default::default()
11405        },
11406        cx,
11407    )
11408    .await;
11409
11410    let counter = Arc::new(AtomicUsize::new(0));
11411    cx.set_state("objˇ");
11412    cx.simulate_keystroke(".");
11413
11414    // Initial completion request returns complete results
11415    let is_incomplete = false;
11416    handle_completion_request(
11417        "obj.|<>",
11418        vec!["a", "ab", "abc"],
11419        is_incomplete,
11420        counter.clone(),
11421        &mut cx,
11422    )
11423    .await;
11424    cx.run_until_parked();
11425    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11426    cx.assert_editor_state("obj.ˇ");
11427    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11428
11429    // Type "a" - filters existing completions
11430    cx.simulate_keystroke("a");
11431    cx.run_until_parked();
11432    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11433    cx.assert_editor_state("obj.aˇ");
11434    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11435
11436    // Type "b" - filters existing completions
11437    cx.simulate_keystroke("b");
11438    cx.run_until_parked();
11439    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11440    cx.assert_editor_state("obj.abˇ");
11441    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11442
11443    // Type "c" - filters existing completions
11444    cx.simulate_keystroke("c");
11445    cx.run_until_parked();
11446    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11447    cx.assert_editor_state("obj.abcˇ");
11448    check_displayed_completions(vec!["abc"], &mut cx);
11449
11450    // Backspace to delete "c" - filters existing completions
11451    cx.update_editor(|editor, window, cx| {
11452        editor.backspace(&Backspace, window, cx);
11453    });
11454    cx.run_until_parked();
11455    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11456    cx.assert_editor_state("obj.abˇ");
11457    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11458
11459    // Moving cursor to the left dismisses menu.
11460    cx.update_editor(|editor, window, cx| {
11461        editor.move_left(&MoveLeft, window, cx);
11462    });
11463    cx.run_until_parked();
11464    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11465    cx.assert_editor_state("obj.aˇb");
11466    cx.update_editor(|editor, _, _| {
11467        assert_eq!(editor.context_menu_visible(), false);
11468    });
11469
11470    // Type "b" - new request
11471    cx.simulate_keystroke("b");
11472    let is_incomplete = false;
11473    handle_completion_request(
11474        "obj.<ab|>a",
11475        vec!["ab", "abc"],
11476        is_incomplete,
11477        counter.clone(),
11478        &mut cx,
11479    )
11480    .await;
11481    cx.run_until_parked();
11482    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11483    cx.assert_editor_state("obj.abˇb");
11484    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11485
11486    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
11487    cx.update_editor(|editor, window, cx| {
11488        editor.backspace(&Backspace, window, cx);
11489    });
11490    let is_incomplete = false;
11491    handle_completion_request(
11492        "obj.<a|>b",
11493        vec!["a", "ab", "abc"],
11494        is_incomplete,
11495        counter.clone(),
11496        &mut cx,
11497    )
11498    .await;
11499    cx.run_until_parked();
11500    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11501    cx.assert_editor_state("obj.aˇb");
11502    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11503
11504    // Backspace to delete "a" - dismisses menu.
11505    cx.update_editor(|editor, window, cx| {
11506        editor.backspace(&Backspace, window, cx);
11507    });
11508    cx.run_until_parked();
11509    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11510    cx.assert_editor_state("obj.ˇb");
11511    cx.update_editor(|editor, _, _| {
11512        assert_eq!(editor.context_menu_visible(), false);
11513    });
11514}
11515
11516#[gpui::test]
11517async fn test_word_completion(cx: &mut TestAppContext) {
11518    let lsp_fetch_timeout_ms = 10;
11519    init_test(cx, |language_settings| {
11520        language_settings.defaults.completions = Some(CompletionSettings {
11521            words: WordsCompletionMode::Fallback,
11522            lsp: true,
11523            lsp_fetch_timeout_ms: 10,
11524            lsp_insert_mode: LspInsertMode::Insert,
11525        });
11526    });
11527
11528    let mut cx = EditorLspTestContext::new_rust(
11529        lsp::ServerCapabilities {
11530            completion_provider: Some(lsp::CompletionOptions {
11531                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11532                ..lsp::CompletionOptions::default()
11533            }),
11534            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11535            ..lsp::ServerCapabilities::default()
11536        },
11537        cx,
11538    )
11539    .await;
11540
11541    let throttle_completions = Arc::new(AtomicBool::new(false));
11542
11543    let lsp_throttle_completions = throttle_completions.clone();
11544    let _completion_requests_handler =
11545        cx.lsp
11546            .server
11547            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11548                let lsp_throttle_completions = lsp_throttle_completions.clone();
11549                let cx = cx.clone();
11550                async move {
11551                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11552                        cx.background_executor()
11553                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11554                            .await;
11555                    }
11556                    Ok(Some(lsp::CompletionResponse::Array(vec![
11557                        lsp::CompletionItem {
11558                            label: "first".into(),
11559                            ..lsp::CompletionItem::default()
11560                        },
11561                        lsp::CompletionItem {
11562                            label: "last".into(),
11563                            ..lsp::CompletionItem::default()
11564                        },
11565                    ])))
11566                }
11567            });
11568
11569    cx.set_state(indoc! {"
11570        oneˇ
11571        two
11572        three
11573    "});
11574    cx.simulate_keystroke(".");
11575    cx.executor().run_until_parked();
11576    cx.condition(|editor, _| editor.context_menu_visible())
11577        .await;
11578    cx.update_editor(|editor, window, cx| {
11579        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11580        {
11581            assert_eq!(
11582                completion_menu_entries(&menu),
11583                &["first", "last"],
11584                "When LSP server is fast to reply, no fallback word completions are used"
11585            );
11586        } else {
11587            panic!("expected completion menu to be open");
11588        }
11589        editor.cancel(&Cancel, window, cx);
11590    });
11591    cx.executor().run_until_parked();
11592    cx.condition(|editor, _| !editor.context_menu_visible())
11593        .await;
11594
11595    throttle_completions.store(true, atomic::Ordering::Release);
11596    cx.simulate_keystroke(".");
11597    cx.executor()
11598        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11599    cx.executor().run_until_parked();
11600    cx.condition(|editor, _| editor.context_menu_visible())
11601        .await;
11602    cx.update_editor(|editor, _, _| {
11603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11604        {
11605            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11606                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11607        } else {
11608            panic!("expected completion menu to be open");
11609        }
11610    });
11611}
11612
11613#[gpui::test]
11614async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11615    init_test(cx, |language_settings| {
11616        language_settings.defaults.completions = Some(CompletionSettings {
11617            words: WordsCompletionMode::Enabled,
11618            lsp: true,
11619            lsp_fetch_timeout_ms: 0,
11620            lsp_insert_mode: LspInsertMode::Insert,
11621        });
11622    });
11623
11624    let mut cx = EditorLspTestContext::new_rust(
11625        lsp::ServerCapabilities {
11626            completion_provider: Some(lsp::CompletionOptions {
11627                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11628                ..lsp::CompletionOptions::default()
11629            }),
11630            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11631            ..lsp::ServerCapabilities::default()
11632        },
11633        cx,
11634    )
11635    .await;
11636
11637    let _completion_requests_handler =
11638        cx.lsp
11639            .server
11640            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11641                Ok(Some(lsp::CompletionResponse::Array(vec![
11642                    lsp::CompletionItem {
11643                        label: "first".into(),
11644                        ..lsp::CompletionItem::default()
11645                    },
11646                    lsp::CompletionItem {
11647                        label: "last".into(),
11648                        ..lsp::CompletionItem::default()
11649                    },
11650                ])))
11651            });
11652
11653    cx.set_state(indoc! {"ˇ
11654        first
11655        last
11656        second
11657    "});
11658    cx.simulate_keystroke(".");
11659    cx.executor().run_until_parked();
11660    cx.condition(|editor, _| editor.context_menu_visible())
11661        .await;
11662    cx.update_editor(|editor, _, _| {
11663        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11664        {
11665            assert_eq!(
11666                completion_menu_entries(&menu),
11667                &["first", "last", "second"],
11668                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11669            );
11670        } else {
11671            panic!("expected completion menu to be open");
11672        }
11673    });
11674}
11675
11676#[gpui::test]
11677async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11678    init_test(cx, |language_settings| {
11679        language_settings.defaults.completions = Some(CompletionSettings {
11680            words: WordsCompletionMode::Disabled,
11681            lsp: true,
11682            lsp_fetch_timeout_ms: 0,
11683            lsp_insert_mode: LspInsertMode::Insert,
11684        });
11685    });
11686
11687    let mut cx = EditorLspTestContext::new_rust(
11688        lsp::ServerCapabilities {
11689            completion_provider: Some(lsp::CompletionOptions {
11690                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11691                ..lsp::CompletionOptions::default()
11692            }),
11693            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11694            ..lsp::ServerCapabilities::default()
11695        },
11696        cx,
11697    )
11698    .await;
11699
11700    let _completion_requests_handler =
11701        cx.lsp
11702            .server
11703            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11704                panic!("LSP completions should not be queried when dealing with word completions")
11705            });
11706
11707    cx.set_state(indoc! {"ˇ
11708        first
11709        last
11710        second
11711    "});
11712    cx.update_editor(|editor, window, cx| {
11713        editor.show_word_completions(&ShowWordCompletions, window, cx);
11714    });
11715    cx.executor().run_until_parked();
11716    cx.condition(|editor, _| editor.context_menu_visible())
11717        .await;
11718    cx.update_editor(|editor, _, _| {
11719        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11720        {
11721            assert_eq!(
11722                completion_menu_entries(&menu),
11723                &["first", "last", "second"],
11724                "`ShowWordCompletions` action should show word completions"
11725            );
11726        } else {
11727            panic!("expected completion menu to be open");
11728        }
11729    });
11730
11731    cx.simulate_keystroke("l");
11732    cx.executor().run_until_parked();
11733    cx.condition(|editor, _| editor.context_menu_visible())
11734        .await;
11735    cx.update_editor(|editor, _, _| {
11736        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11737        {
11738            assert_eq!(
11739                completion_menu_entries(&menu),
11740                &["last"],
11741                "After showing word completions, further editing should filter them and not query the LSP"
11742            );
11743        } else {
11744            panic!("expected completion menu to be open");
11745        }
11746    });
11747}
11748
11749#[gpui::test]
11750async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11751    init_test(cx, |language_settings| {
11752        language_settings.defaults.completions = Some(CompletionSettings {
11753            words: WordsCompletionMode::Fallback,
11754            lsp: false,
11755            lsp_fetch_timeout_ms: 0,
11756            lsp_insert_mode: LspInsertMode::Insert,
11757        });
11758    });
11759
11760    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11761
11762    cx.set_state(indoc! {"ˇ
11763        0_usize
11764        let
11765        33
11766        4.5f32
11767    "});
11768    cx.update_editor(|editor, window, cx| {
11769        editor.show_completions(&ShowCompletions::default(), window, cx);
11770    });
11771    cx.executor().run_until_parked();
11772    cx.condition(|editor, _| editor.context_menu_visible())
11773        .await;
11774    cx.update_editor(|editor, window, cx| {
11775        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11776        {
11777            assert_eq!(
11778                completion_menu_entries(&menu),
11779                &["let"],
11780                "With no digits in the completion query, no digits should be in the word completions"
11781            );
11782        } else {
11783            panic!("expected completion menu to be open");
11784        }
11785        editor.cancel(&Cancel, window, cx);
11786    });
11787
11788    cx.set_state(indoc! {"11789        0_usize
11790        let
11791        3
11792        33.35f32
11793    "});
11794    cx.update_editor(|editor, window, cx| {
11795        editor.show_completions(&ShowCompletions::default(), window, cx);
11796    });
11797    cx.executor().run_until_parked();
11798    cx.condition(|editor, _| editor.context_menu_visible())
11799        .await;
11800    cx.update_editor(|editor, _, _| {
11801        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11802        {
11803            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11804                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11805        } else {
11806            panic!("expected completion menu to be open");
11807        }
11808    });
11809}
11810
11811fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11812    let position = || lsp::Position {
11813        line: params.text_document_position.position.line,
11814        character: params.text_document_position.position.character,
11815    };
11816    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11817        range: lsp::Range {
11818            start: position(),
11819            end: position(),
11820        },
11821        new_text: text.to_string(),
11822    }))
11823}
11824
11825#[gpui::test]
11826async fn test_multiline_completion(cx: &mut TestAppContext) {
11827    init_test(cx, |_| {});
11828
11829    let fs = FakeFs::new(cx.executor());
11830    fs.insert_tree(
11831        path!("/a"),
11832        json!({
11833            "main.ts": "a",
11834        }),
11835    )
11836    .await;
11837
11838    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11839    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11840    let typescript_language = Arc::new(Language::new(
11841        LanguageConfig {
11842            name: "TypeScript".into(),
11843            matcher: LanguageMatcher {
11844                path_suffixes: vec!["ts".to_string()],
11845                ..LanguageMatcher::default()
11846            },
11847            line_comments: vec!["// ".into()],
11848            ..LanguageConfig::default()
11849        },
11850        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11851    ));
11852    language_registry.add(typescript_language.clone());
11853    let mut fake_servers = language_registry.register_fake_lsp(
11854        "TypeScript",
11855        FakeLspAdapter {
11856            capabilities: lsp::ServerCapabilities {
11857                completion_provider: Some(lsp::CompletionOptions {
11858                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11859                    ..lsp::CompletionOptions::default()
11860                }),
11861                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11862                ..lsp::ServerCapabilities::default()
11863            },
11864            // Emulate vtsls label generation
11865            label_for_completion: Some(Box::new(|item, _| {
11866                let text = if let Some(description) = item
11867                    .label_details
11868                    .as_ref()
11869                    .and_then(|label_details| label_details.description.as_ref())
11870                {
11871                    format!("{} {}", item.label, description)
11872                } else if let Some(detail) = &item.detail {
11873                    format!("{} {}", item.label, detail)
11874                } else {
11875                    item.label.clone()
11876                };
11877                let len = text.len();
11878                Some(language::CodeLabel {
11879                    text,
11880                    runs: Vec::new(),
11881                    filter_range: 0..len,
11882                })
11883            })),
11884            ..FakeLspAdapter::default()
11885        },
11886    );
11887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11888    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11889    let worktree_id = workspace
11890        .update(cx, |workspace, _window, cx| {
11891            workspace.project().update(cx, |project, cx| {
11892                project.worktrees(cx).next().unwrap().read(cx).id()
11893            })
11894        })
11895        .unwrap();
11896    let _buffer = project
11897        .update(cx, |project, cx| {
11898            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11899        })
11900        .await
11901        .unwrap();
11902    let editor = workspace
11903        .update(cx, |workspace, window, cx| {
11904            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11905        })
11906        .unwrap()
11907        .await
11908        .unwrap()
11909        .downcast::<Editor>()
11910        .unwrap();
11911    let fake_server = fake_servers.next().await.unwrap();
11912
11913    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11914    let multiline_label_2 = "a\nb\nc\n";
11915    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11916    let multiline_description = "d\ne\nf\n";
11917    let multiline_detail_2 = "g\nh\ni\n";
11918
11919    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11920        move |params, _| async move {
11921            Ok(Some(lsp::CompletionResponse::Array(vec![
11922                lsp::CompletionItem {
11923                    label: multiline_label.to_string(),
11924                    text_edit: gen_text_edit(&params, "new_text_1"),
11925                    ..lsp::CompletionItem::default()
11926                },
11927                lsp::CompletionItem {
11928                    label: "single line label 1".to_string(),
11929                    detail: Some(multiline_detail.to_string()),
11930                    text_edit: gen_text_edit(&params, "new_text_2"),
11931                    ..lsp::CompletionItem::default()
11932                },
11933                lsp::CompletionItem {
11934                    label: "single line label 2".to_string(),
11935                    label_details: Some(lsp::CompletionItemLabelDetails {
11936                        description: Some(multiline_description.to_string()),
11937                        detail: None,
11938                    }),
11939                    text_edit: gen_text_edit(&params, "new_text_2"),
11940                    ..lsp::CompletionItem::default()
11941                },
11942                lsp::CompletionItem {
11943                    label: multiline_label_2.to_string(),
11944                    detail: Some(multiline_detail_2.to_string()),
11945                    text_edit: gen_text_edit(&params, "new_text_3"),
11946                    ..lsp::CompletionItem::default()
11947                },
11948                lsp::CompletionItem {
11949                    label: "Label with many     spaces and \t but without newlines".to_string(),
11950                    detail: Some(
11951                        "Details with many     spaces and \t but without newlines".to_string(),
11952                    ),
11953                    text_edit: gen_text_edit(&params, "new_text_4"),
11954                    ..lsp::CompletionItem::default()
11955                },
11956            ])))
11957        },
11958    );
11959
11960    editor.update_in(cx, |editor, window, cx| {
11961        cx.focus_self(window);
11962        editor.move_to_end(&MoveToEnd, window, cx);
11963        editor.handle_input(".", window, cx);
11964    });
11965    cx.run_until_parked();
11966    completion_handle.next().await.unwrap();
11967
11968    editor.update(cx, |editor, _| {
11969        assert!(editor.context_menu_visible());
11970        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11971        {
11972            let completion_labels = menu
11973                .completions
11974                .borrow()
11975                .iter()
11976                .map(|c| c.label.text.clone())
11977                .collect::<Vec<_>>();
11978            assert_eq!(
11979                completion_labels,
11980                &[
11981                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11982                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11983                    "single line label 2 d e f ",
11984                    "a b c g h i ",
11985                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11986                ],
11987                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11988            );
11989
11990            for completion in menu
11991                .completions
11992                .borrow()
11993                .iter() {
11994                    assert_eq!(
11995                        completion.label.filter_range,
11996                        0..completion.label.text.len(),
11997                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11998                    );
11999                }
12000        } else {
12001            panic!("expected completion menu to be open");
12002        }
12003    });
12004}
12005
12006#[gpui::test]
12007async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12008    init_test(cx, |_| {});
12009    let mut cx = EditorLspTestContext::new_rust(
12010        lsp::ServerCapabilities {
12011            completion_provider: Some(lsp::CompletionOptions {
12012                trigger_characters: Some(vec![".".to_string()]),
12013                ..Default::default()
12014            }),
12015            ..Default::default()
12016        },
12017        cx,
12018    )
12019    .await;
12020    cx.lsp
12021        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12022            Ok(Some(lsp::CompletionResponse::Array(vec![
12023                lsp::CompletionItem {
12024                    label: "first".into(),
12025                    ..Default::default()
12026                },
12027                lsp::CompletionItem {
12028                    label: "last".into(),
12029                    ..Default::default()
12030                },
12031            ])))
12032        });
12033    cx.set_state("variableˇ");
12034    cx.simulate_keystroke(".");
12035    cx.executor().run_until_parked();
12036
12037    cx.update_editor(|editor, _, _| {
12038        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12039        {
12040            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12041        } else {
12042            panic!("expected completion menu to be open");
12043        }
12044    });
12045
12046    cx.update_editor(|editor, window, cx| {
12047        editor.move_page_down(&MovePageDown::default(), window, cx);
12048        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12049        {
12050            assert!(
12051                menu.selected_item == 1,
12052                "expected PageDown to select the last item from the context menu"
12053            );
12054        } else {
12055            panic!("expected completion menu to stay open after PageDown");
12056        }
12057    });
12058
12059    cx.update_editor(|editor, window, cx| {
12060        editor.move_page_up(&MovePageUp::default(), window, cx);
12061        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12062        {
12063            assert!(
12064                menu.selected_item == 0,
12065                "expected PageUp to select the first item from the context menu"
12066            );
12067        } else {
12068            panic!("expected completion menu to stay open after PageUp");
12069        }
12070    });
12071}
12072
12073#[gpui::test]
12074async fn test_as_is_completions(cx: &mut TestAppContext) {
12075    init_test(cx, |_| {});
12076    let mut cx = EditorLspTestContext::new_rust(
12077        lsp::ServerCapabilities {
12078            completion_provider: Some(lsp::CompletionOptions {
12079                ..Default::default()
12080            }),
12081            ..Default::default()
12082        },
12083        cx,
12084    )
12085    .await;
12086    cx.lsp
12087        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12088            Ok(Some(lsp::CompletionResponse::Array(vec![
12089                lsp::CompletionItem {
12090                    label: "unsafe".into(),
12091                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12092                        range: lsp::Range {
12093                            start: lsp::Position {
12094                                line: 1,
12095                                character: 2,
12096                            },
12097                            end: lsp::Position {
12098                                line: 1,
12099                                character: 3,
12100                            },
12101                        },
12102                        new_text: "unsafe".to_string(),
12103                    })),
12104                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12105                    ..Default::default()
12106                },
12107            ])))
12108        });
12109    cx.set_state("fn a() {}\n");
12110    cx.executor().run_until_parked();
12111    cx.update_editor(|editor, window, cx| {
12112        editor.show_completions(
12113            &ShowCompletions {
12114                trigger: Some("\n".into()),
12115            },
12116            window,
12117            cx,
12118        );
12119    });
12120    cx.executor().run_until_parked();
12121
12122    cx.update_editor(|editor, window, cx| {
12123        editor.confirm_completion(&Default::default(), window, cx)
12124    });
12125    cx.executor().run_until_parked();
12126    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12127}
12128
12129#[gpui::test]
12130async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12131    init_test(cx, |_| {});
12132
12133    let mut cx = EditorLspTestContext::new_rust(
12134        lsp::ServerCapabilities {
12135            completion_provider: Some(lsp::CompletionOptions {
12136                trigger_characters: Some(vec![".".to_string()]),
12137                resolve_provider: Some(true),
12138                ..Default::default()
12139            }),
12140            ..Default::default()
12141        },
12142        cx,
12143    )
12144    .await;
12145
12146    cx.set_state("fn main() { let a = 2ˇ; }");
12147    cx.simulate_keystroke(".");
12148    let completion_item = lsp::CompletionItem {
12149        label: "Some".into(),
12150        kind: Some(lsp::CompletionItemKind::SNIPPET),
12151        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12152        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12153            kind: lsp::MarkupKind::Markdown,
12154            value: "```rust\nSome(2)\n```".to_string(),
12155        })),
12156        deprecated: Some(false),
12157        sort_text: Some("Some".to_string()),
12158        filter_text: Some("Some".to_string()),
12159        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12160        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12161            range: lsp::Range {
12162                start: lsp::Position {
12163                    line: 0,
12164                    character: 22,
12165                },
12166                end: lsp::Position {
12167                    line: 0,
12168                    character: 22,
12169                },
12170            },
12171            new_text: "Some(2)".to_string(),
12172        })),
12173        additional_text_edits: Some(vec![lsp::TextEdit {
12174            range: lsp::Range {
12175                start: lsp::Position {
12176                    line: 0,
12177                    character: 20,
12178                },
12179                end: lsp::Position {
12180                    line: 0,
12181                    character: 22,
12182                },
12183            },
12184            new_text: "".to_string(),
12185        }]),
12186        ..Default::default()
12187    };
12188
12189    let closure_completion_item = completion_item.clone();
12190    let counter = Arc::new(AtomicUsize::new(0));
12191    let counter_clone = counter.clone();
12192    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12193        let task_completion_item = closure_completion_item.clone();
12194        counter_clone.fetch_add(1, atomic::Ordering::Release);
12195        async move {
12196            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12197                is_incomplete: true,
12198                item_defaults: None,
12199                items: vec![task_completion_item],
12200            })))
12201        }
12202    });
12203
12204    cx.condition(|editor, _| editor.context_menu_visible())
12205        .await;
12206    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12207    assert!(request.next().await.is_some());
12208    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12209
12210    cx.simulate_keystrokes("S o m");
12211    cx.condition(|editor, _| editor.context_menu_visible())
12212        .await;
12213    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12214    assert!(request.next().await.is_some());
12215    assert!(request.next().await.is_some());
12216    assert!(request.next().await.is_some());
12217    request.close();
12218    assert!(request.next().await.is_none());
12219    assert_eq!(
12220        counter.load(atomic::Ordering::Acquire),
12221        4,
12222        "With the completions menu open, only one LSP request should happen per input"
12223    );
12224}
12225
12226#[gpui::test]
12227async fn test_toggle_comment(cx: &mut TestAppContext) {
12228    init_test(cx, |_| {});
12229    let mut cx = EditorTestContext::new(cx).await;
12230    let language = Arc::new(Language::new(
12231        LanguageConfig {
12232            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12233            ..Default::default()
12234        },
12235        Some(tree_sitter_rust::LANGUAGE.into()),
12236    ));
12237    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12238
12239    // If multiple selections intersect a line, the line is only toggled once.
12240    cx.set_state(indoc! {"
12241        fn a() {
12242            «//b();
12243            ˇ»// «c();
12244            //ˇ»  d();
12245        }
12246    "});
12247
12248    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12249
12250    cx.assert_editor_state(indoc! {"
12251        fn a() {
12252            «b();
12253            c();
12254            ˇ» d();
12255        }
12256    "});
12257
12258    // The comment prefix is inserted at the same column for every line in a
12259    // selection.
12260    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12261
12262    cx.assert_editor_state(indoc! {"
12263        fn a() {
12264            // «b();
12265            // c();
12266            ˇ»//  d();
12267        }
12268    "});
12269
12270    // If a selection ends at the beginning of a line, that line is not toggled.
12271    cx.set_selections_state(indoc! {"
12272        fn a() {
12273            // b();
12274            «// c();
12275        ˇ»    //  d();
12276        }
12277    "});
12278
12279    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12280
12281    cx.assert_editor_state(indoc! {"
12282        fn a() {
12283            // b();
12284            «c();
12285        ˇ»    //  d();
12286        }
12287    "});
12288
12289    // If a selection span a single line and is empty, the line is toggled.
12290    cx.set_state(indoc! {"
12291        fn a() {
12292            a();
12293            b();
12294        ˇ
12295        }
12296    "});
12297
12298    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12299
12300    cx.assert_editor_state(indoc! {"
12301        fn a() {
12302            a();
12303            b();
12304        //•ˇ
12305        }
12306    "});
12307
12308    // If a selection span multiple lines, empty lines are not toggled.
12309    cx.set_state(indoc! {"
12310        fn a() {
12311            «a();
12312
12313            c();ˇ»
12314        }
12315    "});
12316
12317    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12318
12319    cx.assert_editor_state(indoc! {"
12320        fn a() {
12321            // «a();
12322
12323            // c();ˇ»
12324        }
12325    "});
12326
12327    // If a selection includes multiple comment prefixes, all lines are uncommented.
12328    cx.set_state(indoc! {"
12329        fn a() {
12330            «// a();
12331            /// b();
12332            //! c();ˇ»
12333        }
12334    "});
12335
12336    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12337
12338    cx.assert_editor_state(indoc! {"
12339        fn a() {
12340            «a();
12341            b();
12342            c();ˇ»
12343        }
12344    "});
12345}
12346
12347#[gpui::test]
12348async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12349    init_test(cx, |_| {});
12350    let mut cx = EditorTestContext::new(cx).await;
12351    let language = Arc::new(Language::new(
12352        LanguageConfig {
12353            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12354            ..Default::default()
12355        },
12356        Some(tree_sitter_rust::LANGUAGE.into()),
12357    ));
12358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12359
12360    let toggle_comments = &ToggleComments {
12361        advance_downwards: false,
12362        ignore_indent: true,
12363    };
12364
12365    // If multiple selections intersect a line, the line is only toggled once.
12366    cx.set_state(indoc! {"
12367        fn a() {
12368        //    «b();
12369        //    c();
12370        //    ˇ» d();
12371        }
12372    "});
12373
12374    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12375
12376    cx.assert_editor_state(indoc! {"
12377        fn a() {
12378            «b();
12379            c();
12380            ˇ» d();
12381        }
12382    "});
12383
12384    // The comment prefix is inserted at the beginning of each line
12385    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12386
12387    cx.assert_editor_state(indoc! {"
12388        fn a() {
12389        //    «b();
12390        //    c();
12391        //    ˇ» d();
12392        }
12393    "});
12394
12395    // If a selection ends at the beginning of a line, that line is not toggled.
12396    cx.set_selections_state(indoc! {"
12397        fn a() {
12398        //    b();
12399        //    «c();
12400        ˇ»//     d();
12401        }
12402    "});
12403
12404    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12405
12406    cx.assert_editor_state(indoc! {"
12407        fn a() {
12408        //    b();
12409            «c();
12410        ˇ»//     d();
12411        }
12412    "});
12413
12414    // If a selection span a single line and is empty, the line is toggled.
12415    cx.set_state(indoc! {"
12416        fn a() {
12417            a();
12418            b();
12419        ˇ
12420        }
12421    "});
12422
12423    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12424
12425    cx.assert_editor_state(indoc! {"
12426        fn a() {
12427            a();
12428            b();
12429        //ˇ
12430        }
12431    "});
12432
12433    // If a selection span multiple lines, empty lines are not toggled.
12434    cx.set_state(indoc! {"
12435        fn a() {
12436            «a();
12437
12438            c();ˇ»
12439        }
12440    "});
12441
12442    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12443
12444    cx.assert_editor_state(indoc! {"
12445        fn a() {
12446        //    «a();
12447
12448        //    c();ˇ»
12449        }
12450    "});
12451
12452    // If a selection includes multiple comment prefixes, all lines are uncommented.
12453    cx.set_state(indoc! {"
12454        fn a() {
12455        //    «a();
12456        ///    b();
12457        //!    c();ˇ»
12458        }
12459    "});
12460
12461    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12462
12463    cx.assert_editor_state(indoc! {"
12464        fn a() {
12465            «a();
12466            b();
12467            c();ˇ»
12468        }
12469    "});
12470}
12471
12472#[gpui::test]
12473async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12474    init_test(cx, |_| {});
12475
12476    let language = Arc::new(Language::new(
12477        LanguageConfig {
12478            line_comments: vec!["// ".into()],
12479            ..Default::default()
12480        },
12481        Some(tree_sitter_rust::LANGUAGE.into()),
12482    ));
12483
12484    let mut cx = EditorTestContext::new(cx).await;
12485
12486    cx.language_registry().add(language.clone());
12487    cx.update_buffer(|buffer, cx| {
12488        buffer.set_language(Some(language), cx);
12489    });
12490
12491    let toggle_comments = &ToggleComments {
12492        advance_downwards: true,
12493        ignore_indent: false,
12494    };
12495
12496    // Single cursor on one line -> advance
12497    // Cursor moves horizontally 3 characters as well on non-blank line
12498    cx.set_state(indoc!(
12499        "fn a() {
12500             ˇdog();
12501             cat();
12502        }"
12503    ));
12504    cx.update_editor(|editor, window, cx| {
12505        editor.toggle_comments(toggle_comments, window, cx);
12506    });
12507    cx.assert_editor_state(indoc!(
12508        "fn a() {
12509             // dog();
12510             catˇ();
12511        }"
12512    ));
12513
12514    // Single selection on one line -> don't advance
12515    cx.set_state(indoc!(
12516        "fn a() {
12517             «dog()ˇ»;
12518             cat();
12519        }"
12520    ));
12521    cx.update_editor(|editor, window, cx| {
12522        editor.toggle_comments(toggle_comments, window, cx);
12523    });
12524    cx.assert_editor_state(indoc!(
12525        "fn a() {
12526             // «dog()ˇ»;
12527             cat();
12528        }"
12529    ));
12530
12531    // Multiple cursors on one line -> advance
12532    cx.set_state(indoc!(
12533        "fn a() {
12534             ˇdˇog();
12535             cat();
12536        }"
12537    ));
12538    cx.update_editor(|editor, window, cx| {
12539        editor.toggle_comments(toggle_comments, window, cx);
12540    });
12541    cx.assert_editor_state(indoc!(
12542        "fn a() {
12543             // dog();
12544             catˇ(ˇ);
12545        }"
12546    ));
12547
12548    // Multiple cursors on one line, with selection -> don't advance
12549    cx.set_state(indoc!(
12550        "fn a() {
12551             ˇdˇog«()ˇ»;
12552             cat();
12553        }"
12554    ));
12555    cx.update_editor(|editor, window, cx| {
12556        editor.toggle_comments(toggle_comments, window, cx);
12557    });
12558    cx.assert_editor_state(indoc!(
12559        "fn a() {
12560             // ˇdˇog«()ˇ»;
12561             cat();
12562        }"
12563    ));
12564
12565    // Single cursor on one line -> advance
12566    // Cursor moves to column 0 on blank line
12567    cx.set_state(indoc!(
12568        "fn a() {
12569             ˇdog();
12570
12571             cat();
12572        }"
12573    ));
12574    cx.update_editor(|editor, window, cx| {
12575        editor.toggle_comments(toggle_comments, window, cx);
12576    });
12577    cx.assert_editor_state(indoc!(
12578        "fn a() {
12579             // dog();
12580        ˇ
12581             cat();
12582        }"
12583    ));
12584
12585    // Single cursor on one line -> advance
12586    // Cursor starts and ends at column 0
12587    cx.set_state(indoc!(
12588        "fn a() {
12589         ˇ    dog();
12590             cat();
12591        }"
12592    ));
12593    cx.update_editor(|editor, window, cx| {
12594        editor.toggle_comments(toggle_comments, window, cx);
12595    });
12596    cx.assert_editor_state(indoc!(
12597        "fn a() {
12598             // dog();
12599         ˇ    cat();
12600        }"
12601    ));
12602}
12603
12604#[gpui::test]
12605async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12606    init_test(cx, |_| {});
12607
12608    let mut cx = EditorTestContext::new(cx).await;
12609
12610    let html_language = Arc::new(
12611        Language::new(
12612            LanguageConfig {
12613                name: "HTML".into(),
12614                block_comment: Some(("<!-- ".into(), " -->".into())),
12615                ..Default::default()
12616            },
12617            Some(tree_sitter_html::LANGUAGE.into()),
12618        )
12619        .with_injection_query(
12620            r#"
12621            (script_element
12622                (raw_text) @injection.content
12623                (#set! injection.language "javascript"))
12624            "#,
12625        )
12626        .unwrap(),
12627    );
12628
12629    let javascript_language = Arc::new(Language::new(
12630        LanguageConfig {
12631            name: "JavaScript".into(),
12632            line_comments: vec!["// ".into()],
12633            ..Default::default()
12634        },
12635        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12636    ));
12637
12638    cx.language_registry().add(html_language.clone());
12639    cx.language_registry().add(javascript_language.clone());
12640    cx.update_buffer(|buffer, cx| {
12641        buffer.set_language(Some(html_language), cx);
12642    });
12643
12644    // Toggle comments for empty selections
12645    cx.set_state(
12646        &r#"
12647            <p>A</p>ˇ
12648            <p>B</p>ˇ
12649            <p>C</p>ˇ
12650        "#
12651        .unindent(),
12652    );
12653    cx.update_editor(|editor, window, cx| {
12654        editor.toggle_comments(&ToggleComments::default(), window, cx)
12655    });
12656    cx.assert_editor_state(
12657        &r#"
12658            <!-- <p>A</p>ˇ -->
12659            <!-- <p>B</p>ˇ -->
12660            <!-- <p>C</p>ˇ -->
12661        "#
12662        .unindent(),
12663    );
12664    cx.update_editor(|editor, window, cx| {
12665        editor.toggle_comments(&ToggleComments::default(), window, cx)
12666    });
12667    cx.assert_editor_state(
12668        &r#"
12669            <p>A</p>ˇ
12670            <p>B</p>ˇ
12671            <p>C</p>ˇ
12672        "#
12673        .unindent(),
12674    );
12675
12676    // Toggle comments for mixture of empty and non-empty selections, where
12677    // multiple selections occupy a given line.
12678    cx.set_state(
12679        &r#"
12680            <p>A«</p>
12681            <p>ˇ»B</p>ˇ
12682            <p>C«</p>
12683            <p>ˇ»D</p>ˇ
12684        "#
12685        .unindent(),
12686    );
12687
12688    cx.update_editor(|editor, window, cx| {
12689        editor.toggle_comments(&ToggleComments::default(), window, cx)
12690    });
12691    cx.assert_editor_state(
12692        &r#"
12693            <!-- <p>A«</p>
12694            <p>ˇ»B</p>ˇ -->
12695            <!-- <p>C«</p>
12696            <p>ˇ»D</p>ˇ -->
12697        "#
12698        .unindent(),
12699    );
12700    cx.update_editor(|editor, window, cx| {
12701        editor.toggle_comments(&ToggleComments::default(), window, cx)
12702    });
12703    cx.assert_editor_state(
12704        &r#"
12705            <p>A«</p>
12706            <p>ˇ»B</p>ˇ
12707            <p>C«</p>
12708            <p>ˇ»D</p>ˇ
12709        "#
12710        .unindent(),
12711    );
12712
12713    // Toggle comments when different languages are active for different
12714    // selections.
12715    cx.set_state(
12716        &r#"
12717            ˇ<script>
12718                ˇvar x = new Y();
12719            ˇ</script>
12720        "#
12721        .unindent(),
12722    );
12723    cx.executor().run_until_parked();
12724    cx.update_editor(|editor, window, cx| {
12725        editor.toggle_comments(&ToggleComments::default(), window, cx)
12726    });
12727    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12728    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12729    cx.assert_editor_state(
12730        &r#"
12731            <!-- ˇ<script> -->
12732                // ˇvar x = new Y();
12733            <!-- ˇ</script> -->
12734        "#
12735        .unindent(),
12736    );
12737}
12738
12739#[gpui::test]
12740fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12741    init_test(cx, |_| {});
12742
12743    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12744    let multibuffer = cx.new(|cx| {
12745        let mut multibuffer = MultiBuffer::new(ReadWrite);
12746        multibuffer.push_excerpts(
12747            buffer.clone(),
12748            [
12749                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12750                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12751            ],
12752            cx,
12753        );
12754        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12755        multibuffer
12756    });
12757
12758    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12759    editor.update_in(cx, |editor, window, cx| {
12760        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12761        editor.change_selections(None, window, cx, |s| {
12762            s.select_ranges([
12763                Point::new(0, 0)..Point::new(0, 0),
12764                Point::new(1, 0)..Point::new(1, 0),
12765            ])
12766        });
12767
12768        editor.handle_input("X", window, cx);
12769        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12770        assert_eq!(
12771            editor.selections.ranges(cx),
12772            [
12773                Point::new(0, 1)..Point::new(0, 1),
12774                Point::new(1, 1)..Point::new(1, 1),
12775            ]
12776        );
12777
12778        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12779        editor.change_selections(None, window, cx, |s| {
12780            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12781        });
12782        editor.backspace(&Default::default(), window, cx);
12783        assert_eq!(editor.text(cx), "Xa\nbbb");
12784        assert_eq!(
12785            editor.selections.ranges(cx),
12786            [Point::new(1, 0)..Point::new(1, 0)]
12787        );
12788
12789        editor.change_selections(None, window, cx, |s| {
12790            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12791        });
12792        editor.backspace(&Default::default(), window, cx);
12793        assert_eq!(editor.text(cx), "X\nbb");
12794        assert_eq!(
12795            editor.selections.ranges(cx),
12796            [Point::new(0, 1)..Point::new(0, 1)]
12797        );
12798    });
12799}
12800
12801#[gpui::test]
12802fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12803    init_test(cx, |_| {});
12804
12805    let markers = vec![('[', ']').into(), ('(', ')').into()];
12806    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12807        indoc! {"
12808            [aaaa
12809            (bbbb]
12810            cccc)",
12811        },
12812        markers.clone(),
12813    );
12814    let excerpt_ranges = markers.into_iter().map(|marker| {
12815        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12816        ExcerptRange::new(context.clone())
12817    });
12818    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12819    let multibuffer = cx.new(|cx| {
12820        let mut multibuffer = MultiBuffer::new(ReadWrite);
12821        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12822        multibuffer
12823    });
12824
12825    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12826    editor.update_in(cx, |editor, window, cx| {
12827        let (expected_text, selection_ranges) = marked_text_ranges(
12828            indoc! {"
12829                aaaa
12830                bˇbbb
12831                bˇbbˇb
12832                cccc"
12833            },
12834            true,
12835        );
12836        assert_eq!(editor.text(cx), expected_text);
12837        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12838
12839        editor.handle_input("X", window, cx);
12840
12841        let (expected_text, expected_selections) = marked_text_ranges(
12842            indoc! {"
12843                aaaa
12844                bXˇbbXb
12845                bXˇbbXˇb
12846                cccc"
12847            },
12848            false,
12849        );
12850        assert_eq!(editor.text(cx), expected_text);
12851        assert_eq!(editor.selections.ranges(cx), expected_selections);
12852
12853        editor.newline(&Newline, window, cx);
12854        let (expected_text, expected_selections) = marked_text_ranges(
12855            indoc! {"
12856                aaaa
12857                bX
12858                ˇbbX
12859                b
12860                bX
12861                ˇbbX
12862                ˇb
12863                cccc"
12864            },
12865            false,
12866        );
12867        assert_eq!(editor.text(cx), expected_text);
12868        assert_eq!(editor.selections.ranges(cx), expected_selections);
12869    });
12870}
12871
12872#[gpui::test]
12873fn test_refresh_selections(cx: &mut TestAppContext) {
12874    init_test(cx, |_| {});
12875
12876    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12877    let mut excerpt1_id = None;
12878    let multibuffer = cx.new(|cx| {
12879        let mut multibuffer = MultiBuffer::new(ReadWrite);
12880        excerpt1_id = multibuffer
12881            .push_excerpts(
12882                buffer.clone(),
12883                [
12884                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12885                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12886                ],
12887                cx,
12888            )
12889            .into_iter()
12890            .next();
12891        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12892        multibuffer
12893    });
12894
12895    let editor = cx.add_window(|window, cx| {
12896        let mut editor = build_editor(multibuffer.clone(), window, cx);
12897        let snapshot = editor.snapshot(window, cx);
12898        editor.change_selections(None, window, cx, |s| {
12899            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12900        });
12901        editor.begin_selection(
12902            Point::new(2, 1).to_display_point(&snapshot),
12903            true,
12904            1,
12905            window,
12906            cx,
12907        );
12908        assert_eq!(
12909            editor.selections.ranges(cx),
12910            [
12911                Point::new(1, 3)..Point::new(1, 3),
12912                Point::new(2, 1)..Point::new(2, 1),
12913            ]
12914        );
12915        editor
12916    });
12917
12918    // Refreshing selections is a no-op when excerpts haven't changed.
12919    _ = editor.update(cx, |editor, window, cx| {
12920        editor.change_selections(None, window, cx, |s| s.refresh());
12921        assert_eq!(
12922            editor.selections.ranges(cx),
12923            [
12924                Point::new(1, 3)..Point::new(1, 3),
12925                Point::new(2, 1)..Point::new(2, 1),
12926            ]
12927        );
12928    });
12929
12930    multibuffer.update(cx, |multibuffer, cx| {
12931        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12932    });
12933    _ = editor.update(cx, |editor, window, cx| {
12934        // Removing an excerpt causes the first selection to become degenerate.
12935        assert_eq!(
12936            editor.selections.ranges(cx),
12937            [
12938                Point::new(0, 0)..Point::new(0, 0),
12939                Point::new(0, 1)..Point::new(0, 1)
12940            ]
12941        );
12942
12943        // Refreshing selections will relocate the first selection to the original buffer
12944        // location.
12945        editor.change_selections(None, window, cx, |s| s.refresh());
12946        assert_eq!(
12947            editor.selections.ranges(cx),
12948            [
12949                Point::new(0, 1)..Point::new(0, 1),
12950                Point::new(0, 3)..Point::new(0, 3)
12951            ]
12952        );
12953        assert!(editor.selections.pending_anchor().is_some());
12954    });
12955}
12956
12957#[gpui::test]
12958fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12959    init_test(cx, |_| {});
12960
12961    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12962    let mut excerpt1_id = None;
12963    let multibuffer = cx.new(|cx| {
12964        let mut multibuffer = MultiBuffer::new(ReadWrite);
12965        excerpt1_id = multibuffer
12966            .push_excerpts(
12967                buffer.clone(),
12968                [
12969                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12970                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12971                ],
12972                cx,
12973            )
12974            .into_iter()
12975            .next();
12976        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12977        multibuffer
12978    });
12979
12980    let editor = cx.add_window(|window, cx| {
12981        let mut editor = build_editor(multibuffer.clone(), window, cx);
12982        let snapshot = editor.snapshot(window, cx);
12983        editor.begin_selection(
12984            Point::new(1, 3).to_display_point(&snapshot),
12985            false,
12986            1,
12987            window,
12988            cx,
12989        );
12990        assert_eq!(
12991            editor.selections.ranges(cx),
12992            [Point::new(1, 3)..Point::new(1, 3)]
12993        );
12994        editor
12995    });
12996
12997    multibuffer.update(cx, |multibuffer, cx| {
12998        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12999    });
13000    _ = editor.update(cx, |editor, window, cx| {
13001        assert_eq!(
13002            editor.selections.ranges(cx),
13003            [Point::new(0, 0)..Point::new(0, 0)]
13004        );
13005
13006        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13007        editor.change_selections(None, window, cx, |s| s.refresh());
13008        assert_eq!(
13009            editor.selections.ranges(cx),
13010            [Point::new(0, 3)..Point::new(0, 3)]
13011        );
13012        assert!(editor.selections.pending_anchor().is_some());
13013    });
13014}
13015
13016#[gpui::test]
13017async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13018    init_test(cx, |_| {});
13019
13020    let language = Arc::new(
13021        Language::new(
13022            LanguageConfig {
13023                brackets: BracketPairConfig {
13024                    pairs: vec![
13025                        BracketPair {
13026                            start: "{".to_string(),
13027                            end: "}".to_string(),
13028                            close: true,
13029                            surround: true,
13030                            newline: true,
13031                        },
13032                        BracketPair {
13033                            start: "/* ".to_string(),
13034                            end: " */".to_string(),
13035                            close: true,
13036                            surround: true,
13037                            newline: true,
13038                        },
13039                    ],
13040                    ..Default::default()
13041                },
13042                ..Default::default()
13043            },
13044            Some(tree_sitter_rust::LANGUAGE.into()),
13045        )
13046        .with_indents_query("")
13047        .unwrap(),
13048    );
13049
13050    let text = concat!(
13051        "{   }\n",     //
13052        "  x\n",       //
13053        "  /*   */\n", //
13054        "x\n",         //
13055        "{{} }\n",     //
13056    );
13057
13058    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13059    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13061    editor
13062        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13063        .await;
13064
13065    editor.update_in(cx, |editor, window, cx| {
13066        editor.change_selections(None, window, cx, |s| {
13067            s.select_display_ranges([
13068                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13069                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13070                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13071            ])
13072        });
13073        editor.newline(&Newline, window, cx);
13074
13075        assert_eq!(
13076            editor.buffer().read(cx).read(cx).text(),
13077            concat!(
13078                "{ \n",    // Suppress rustfmt
13079                "\n",      //
13080                "}\n",     //
13081                "  x\n",   //
13082                "  /* \n", //
13083                "  \n",    //
13084                "  */\n",  //
13085                "x\n",     //
13086                "{{} \n",  //
13087                "}\n",     //
13088            )
13089        );
13090    });
13091}
13092
13093#[gpui::test]
13094fn test_highlighted_ranges(cx: &mut TestAppContext) {
13095    init_test(cx, |_| {});
13096
13097    let editor = cx.add_window(|window, cx| {
13098        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13099        build_editor(buffer.clone(), window, cx)
13100    });
13101
13102    _ = editor.update(cx, |editor, window, cx| {
13103        struct Type1;
13104        struct Type2;
13105
13106        let buffer = editor.buffer.read(cx).snapshot(cx);
13107
13108        let anchor_range =
13109            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13110
13111        editor.highlight_background::<Type1>(
13112            &[
13113                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13114                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13115                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13116                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13117            ],
13118            |_| Hsla::red(),
13119            cx,
13120        );
13121        editor.highlight_background::<Type2>(
13122            &[
13123                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13124                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13125                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13126                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13127            ],
13128            |_| Hsla::green(),
13129            cx,
13130        );
13131
13132        let snapshot = editor.snapshot(window, cx);
13133        let mut highlighted_ranges = editor.background_highlights_in_range(
13134            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13135            &snapshot,
13136            cx.theme().colors(),
13137        );
13138        // Enforce a consistent ordering based on color without relying on the ordering of the
13139        // highlight's `TypeId` which is non-executor.
13140        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13141        assert_eq!(
13142            highlighted_ranges,
13143            &[
13144                (
13145                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13146                    Hsla::red(),
13147                ),
13148                (
13149                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13150                    Hsla::red(),
13151                ),
13152                (
13153                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13154                    Hsla::green(),
13155                ),
13156                (
13157                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13158                    Hsla::green(),
13159                ),
13160            ]
13161        );
13162        assert_eq!(
13163            editor.background_highlights_in_range(
13164                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13165                &snapshot,
13166                cx.theme().colors(),
13167            ),
13168            &[(
13169                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13170                Hsla::red(),
13171            )]
13172        );
13173    });
13174}
13175
13176#[gpui::test]
13177async fn test_following(cx: &mut TestAppContext) {
13178    init_test(cx, |_| {});
13179
13180    let fs = FakeFs::new(cx.executor());
13181    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13182
13183    let buffer = project.update(cx, |project, cx| {
13184        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13185        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13186    });
13187    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13188    let follower = cx.update(|cx| {
13189        cx.open_window(
13190            WindowOptions {
13191                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13192                    gpui::Point::new(px(0.), px(0.)),
13193                    gpui::Point::new(px(10.), px(80.)),
13194                ))),
13195                ..Default::default()
13196            },
13197            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13198        )
13199        .unwrap()
13200    });
13201
13202    let is_still_following = Rc::new(RefCell::new(true));
13203    let follower_edit_event_count = Rc::new(RefCell::new(0));
13204    let pending_update = Rc::new(RefCell::new(None));
13205    let leader_entity = leader.root(cx).unwrap();
13206    let follower_entity = follower.root(cx).unwrap();
13207    _ = follower.update(cx, {
13208        let update = pending_update.clone();
13209        let is_still_following = is_still_following.clone();
13210        let follower_edit_event_count = follower_edit_event_count.clone();
13211        |_, window, cx| {
13212            cx.subscribe_in(
13213                &leader_entity,
13214                window,
13215                move |_, leader, event, window, cx| {
13216                    leader.read(cx).add_event_to_update_proto(
13217                        event,
13218                        &mut update.borrow_mut(),
13219                        window,
13220                        cx,
13221                    );
13222                },
13223            )
13224            .detach();
13225
13226            cx.subscribe_in(
13227                &follower_entity,
13228                window,
13229                move |_, _, event: &EditorEvent, _window, _cx| {
13230                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13231                        *is_still_following.borrow_mut() = false;
13232                    }
13233
13234                    if let EditorEvent::BufferEdited = event {
13235                        *follower_edit_event_count.borrow_mut() += 1;
13236                    }
13237                },
13238            )
13239            .detach();
13240        }
13241    });
13242
13243    // Update the selections only
13244    _ = leader.update(cx, |leader, window, cx| {
13245        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13246    });
13247    follower
13248        .update(cx, |follower, window, cx| {
13249            follower.apply_update_proto(
13250                &project,
13251                pending_update.borrow_mut().take().unwrap(),
13252                window,
13253                cx,
13254            )
13255        })
13256        .unwrap()
13257        .await
13258        .unwrap();
13259    _ = follower.update(cx, |follower, _, cx| {
13260        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13261    });
13262    assert!(*is_still_following.borrow());
13263    assert_eq!(*follower_edit_event_count.borrow(), 0);
13264
13265    // Update the scroll position only
13266    _ = leader.update(cx, |leader, window, cx| {
13267        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13268    });
13269    follower
13270        .update(cx, |follower, window, cx| {
13271            follower.apply_update_proto(
13272                &project,
13273                pending_update.borrow_mut().take().unwrap(),
13274                window,
13275                cx,
13276            )
13277        })
13278        .unwrap()
13279        .await
13280        .unwrap();
13281    assert_eq!(
13282        follower
13283            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13284            .unwrap(),
13285        gpui::Point::new(1.5, 3.5)
13286    );
13287    assert!(*is_still_following.borrow());
13288    assert_eq!(*follower_edit_event_count.borrow(), 0);
13289
13290    // Update the selections and scroll position. The follower's scroll position is updated
13291    // via autoscroll, not via the leader's exact scroll position.
13292    _ = leader.update(cx, |leader, window, cx| {
13293        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13294        leader.request_autoscroll(Autoscroll::newest(), cx);
13295        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13296    });
13297    follower
13298        .update(cx, |follower, window, cx| {
13299            follower.apply_update_proto(
13300                &project,
13301                pending_update.borrow_mut().take().unwrap(),
13302                window,
13303                cx,
13304            )
13305        })
13306        .unwrap()
13307        .await
13308        .unwrap();
13309    _ = follower.update(cx, |follower, _, cx| {
13310        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13311        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13312    });
13313    assert!(*is_still_following.borrow());
13314
13315    // Creating a pending selection that precedes another selection
13316    _ = leader.update(cx, |leader, window, cx| {
13317        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13318        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13319    });
13320    follower
13321        .update(cx, |follower, window, cx| {
13322            follower.apply_update_proto(
13323                &project,
13324                pending_update.borrow_mut().take().unwrap(),
13325                window,
13326                cx,
13327            )
13328        })
13329        .unwrap()
13330        .await
13331        .unwrap();
13332    _ = follower.update(cx, |follower, _, cx| {
13333        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13334    });
13335    assert!(*is_still_following.borrow());
13336
13337    // Extend the pending selection so that it surrounds another selection
13338    _ = leader.update(cx, |leader, window, cx| {
13339        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13340    });
13341    follower
13342        .update(cx, |follower, window, cx| {
13343            follower.apply_update_proto(
13344                &project,
13345                pending_update.borrow_mut().take().unwrap(),
13346                window,
13347                cx,
13348            )
13349        })
13350        .unwrap()
13351        .await
13352        .unwrap();
13353    _ = follower.update(cx, |follower, _, cx| {
13354        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13355    });
13356
13357    // Scrolling locally breaks the follow
13358    _ = follower.update(cx, |follower, window, cx| {
13359        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13360        follower.set_scroll_anchor(
13361            ScrollAnchor {
13362                anchor: top_anchor,
13363                offset: gpui::Point::new(0.0, 0.5),
13364            },
13365            window,
13366            cx,
13367        );
13368    });
13369    assert!(!(*is_still_following.borrow()));
13370}
13371
13372#[gpui::test]
13373async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13374    init_test(cx, |_| {});
13375
13376    let fs = FakeFs::new(cx.executor());
13377    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13378    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13379    let pane = workspace
13380        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13381        .unwrap();
13382
13383    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13384
13385    let leader = pane.update_in(cx, |_, window, cx| {
13386        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13387        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13388    });
13389
13390    // Start following the editor when it has no excerpts.
13391    let mut state_message =
13392        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13393    let workspace_entity = workspace.root(cx).unwrap();
13394    let follower_1 = cx
13395        .update_window(*workspace.deref(), |_, window, cx| {
13396            Editor::from_state_proto(
13397                workspace_entity,
13398                ViewId {
13399                    creator: CollaboratorId::PeerId(PeerId::default()),
13400                    id: 0,
13401                },
13402                &mut state_message,
13403                window,
13404                cx,
13405            )
13406        })
13407        .unwrap()
13408        .unwrap()
13409        .await
13410        .unwrap();
13411
13412    let update_message = Rc::new(RefCell::new(None));
13413    follower_1.update_in(cx, {
13414        let update = update_message.clone();
13415        |_, window, cx| {
13416            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13417                leader.read(cx).add_event_to_update_proto(
13418                    event,
13419                    &mut update.borrow_mut(),
13420                    window,
13421                    cx,
13422                );
13423            })
13424            .detach();
13425        }
13426    });
13427
13428    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13429        (
13430            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13431            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13432        )
13433    });
13434
13435    // Insert some excerpts.
13436    leader.update(cx, |leader, cx| {
13437        leader.buffer.update(cx, |multibuffer, cx| {
13438            multibuffer.set_excerpts_for_path(
13439                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13440                buffer_1.clone(),
13441                vec![
13442                    Point::row_range(0..3),
13443                    Point::row_range(1..6),
13444                    Point::row_range(12..15),
13445                ],
13446                0,
13447                cx,
13448            );
13449            multibuffer.set_excerpts_for_path(
13450                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13451                buffer_2.clone(),
13452                vec![Point::row_range(0..6), Point::row_range(8..12)],
13453                0,
13454                cx,
13455            );
13456        });
13457    });
13458
13459    // Apply the update of adding the excerpts.
13460    follower_1
13461        .update_in(cx, |follower, window, cx| {
13462            follower.apply_update_proto(
13463                &project,
13464                update_message.borrow().clone().unwrap(),
13465                window,
13466                cx,
13467            )
13468        })
13469        .await
13470        .unwrap();
13471    assert_eq!(
13472        follower_1.update(cx, |editor, cx| editor.text(cx)),
13473        leader.update(cx, |editor, cx| editor.text(cx))
13474    );
13475    update_message.borrow_mut().take();
13476
13477    // Start following separately after it already has excerpts.
13478    let mut state_message =
13479        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13480    let workspace_entity = workspace.root(cx).unwrap();
13481    let follower_2 = cx
13482        .update_window(*workspace.deref(), |_, window, cx| {
13483            Editor::from_state_proto(
13484                workspace_entity,
13485                ViewId {
13486                    creator: CollaboratorId::PeerId(PeerId::default()),
13487                    id: 0,
13488                },
13489                &mut state_message,
13490                window,
13491                cx,
13492            )
13493        })
13494        .unwrap()
13495        .unwrap()
13496        .await
13497        .unwrap();
13498    assert_eq!(
13499        follower_2.update(cx, |editor, cx| editor.text(cx)),
13500        leader.update(cx, |editor, cx| editor.text(cx))
13501    );
13502
13503    // Remove some excerpts.
13504    leader.update(cx, |leader, cx| {
13505        leader.buffer.update(cx, |multibuffer, cx| {
13506            let excerpt_ids = multibuffer.excerpt_ids();
13507            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13508            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13509        });
13510    });
13511
13512    // Apply the update of removing the excerpts.
13513    follower_1
13514        .update_in(cx, |follower, window, cx| {
13515            follower.apply_update_proto(
13516                &project,
13517                update_message.borrow().clone().unwrap(),
13518                window,
13519                cx,
13520            )
13521        })
13522        .await
13523        .unwrap();
13524    follower_2
13525        .update_in(cx, |follower, window, cx| {
13526            follower.apply_update_proto(
13527                &project,
13528                update_message.borrow().clone().unwrap(),
13529                window,
13530                cx,
13531            )
13532        })
13533        .await
13534        .unwrap();
13535    update_message.borrow_mut().take();
13536    assert_eq!(
13537        follower_1.update(cx, |editor, cx| editor.text(cx)),
13538        leader.update(cx, |editor, cx| editor.text(cx))
13539    );
13540}
13541
13542#[gpui::test]
13543async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13544    init_test(cx, |_| {});
13545
13546    let mut cx = EditorTestContext::new(cx).await;
13547    let lsp_store =
13548        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13549
13550    cx.set_state(indoc! {"
13551        ˇfn func(abc def: i32) -> u32 {
13552        }
13553    "});
13554
13555    cx.update(|_, cx| {
13556        lsp_store.update(cx, |lsp_store, cx| {
13557            lsp_store
13558                .update_diagnostics(
13559                    LanguageServerId(0),
13560                    lsp::PublishDiagnosticsParams {
13561                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13562                        version: None,
13563                        diagnostics: vec![
13564                            lsp::Diagnostic {
13565                                range: lsp::Range::new(
13566                                    lsp::Position::new(0, 11),
13567                                    lsp::Position::new(0, 12),
13568                                ),
13569                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13570                                ..Default::default()
13571                            },
13572                            lsp::Diagnostic {
13573                                range: lsp::Range::new(
13574                                    lsp::Position::new(0, 12),
13575                                    lsp::Position::new(0, 15),
13576                                ),
13577                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13578                                ..Default::default()
13579                            },
13580                            lsp::Diagnostic {
13581                                range: lsp::Range::new(
13582                                    lsp::Position::new(0, 25),
13583                                    lsp::Position::new(0, 28),
13584                                ),
13585                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13586                                ..Default::default()
13587                            },
13588                        ],
13589                    },
13590                    &[],
13591                    cx,
13592                )
13593                .unwrap()
13594        });
13595    });
13596
13597    executor.run_until_parked();
13598
13599    cx.update_editor(|editor, window, cx| {
13600        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13601    });
13602
13603    cx.assert_editor_state(indoc! {"
13604        fn func(abc def: i32) -> ˇu32 {
13605        }
13606    "});
13607
13608    cx.update_editor(|editor, window, cx| {
13609        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13610    });
13611
13612    cx.assert_editor_state(indoc! {"
13613        fn func(abc ˇdef: i32) -> u32 {
13614        }
13615    "});
13616
13617    cx.update_editor(|editor, window, cx| {
13618        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13619    });
13620
13621    cx.assert_editor_state(indoc! {"
13622        fn func(abcˇ def: i32) -> u32 {
13623        }
13624    "});
13625
13626    cx.update_editor(|editor, window, cx| {
13627        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13628    });
13629
13630    cx.assert_editor_state(indoc! {"
13631        fn func(abc def: i32) -> ˇu32 {
13632        }
13633    "});
13634}
13635
13636#[gpui::test]
13637async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13638    init_test(cx, |_| {});
13639
13640    let mut cx = EditorTestContext::new(cx).await;
13641
13642    let diff_base = r#"
13643        use some::mod;
13644
13645        const A: u32 = 42;
13646
13647        fn main() {
13648            println!("hello");
13649
13650            println!("world");
13651        }
13652        "#
13653    .unindent();
13654
13655    // Edits are modified, removed, modified, added
13656    cx.set_state(
13657        &r#"
13658        use some::modified;
13659
13660        ˇ
13661        fn main() {
13662            println!("hello there");
13663
13664            println!("around the");
13665            println!("world");
13666        }
13667        "#
13668        .unindent(),
13669    );
13670
13671    cx.set_head_text(&diff_base);
13672    executor.run_until_parked();
13673
13674    cx.update_editor(|editor, window, cx| {
13675        //Wrap around the bottom of the buffer
13676        for _ in 0..3 {
13677            editor.go_to_next_hunk(&GoToHunk, window, cx);
13678        }
13679    });
13680
13681    cx.assert_editor_state(
13682        &r#"
13683        ˇuse some::modified;
13684
13685
13686        fn main() {
13687            println!("hello there");
13688
13689            println!("around the");
13690            println!("world");
13691        }
13692        "#
13693        .unindent(),
13694    );
13695
13696    cx.update_editor(|editor, window, cx| {
13697        //Wrap around the top of the buffer
13698        for _ in 0..2 {
13699            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13700        }
13701    });
13702
13703    cx.assert_editor_state(
13704        &r#"
13705        use some::modified;
13706
13707
13708        fn main() {
13709        ˇ    println!("hello there");
13710
13711            println!("around the");
13712            println!("world");
13713        }
13714        "#
13715        .unindent(),
13716    );
13717
13718    cx.update_editor(|editor, window, cx| {
13719        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13720    });
13721
13722    cx.assert_editor_state(
13723        &r#"
13724        use some::modified;
13725
13726        ˇ
13727        fn main() {
13728            println!("hello there");
13729
13730            println!("around the");
13731            println!("world");
13732        }
13733        "#
13734        .unindent(),
13735    );
13736
13737    cx.update_editor(|editor, window, cx| {
13738        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13739    });
13740
13741    cx.assert_editor_state(
13742        &r#"
13743        ˇuse some::modified;
13744
13745
13746        fn main() {
13747            println!("hello there");
13748
13749            println!("around the");
13750            println!("world");
13751        }
13752        "#
13753        .unindent(),
13754    );
13755
13756    cx.update_editor(|editor, window, cx| {
13757        for _ in 0..2 {
13758            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13759        }
13760    });
13761
13762    cx.assert_editor_state(
13763        &r#"
13764        use some::modified;
13765
13766
13767        fn main() {
13768        ˇ    println!("hello there");
13769
13770            println!("around the");
13771            println!("world");
13772        }
13773        "#
13774        .unindent(),
13775    );
13776
13777    cx.update_editor(|editor, window, cx| {
13778        editor.fold(&Fold, window, cx);
13779    });
13780
13781    cx.update_editor(|editor, window, cx| {
13782        editor.go_to_next_hunk(&GoToHunk, window, cx);
13783    });
13784
13785    cx.assert_editor_state(
13786        &r#"
13787        ˇuse some::modified;
13788
13789
13790        fn main() {
13791            println!("hello there");
13792
13793            println!("around the");
13794            println!("world");
13795        }
13796        "#
13797        .unindent(),
13798    );
13799}
13800
13801#[test]
13802fn test_split_words() {
13803    fn split(text: &str) -> Vec<&str> {
13804        split_words(text).collect()
13805    }
13806
13807    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13808    assert_eq!(split("hello_world"), &["hello_", "world"]);
13809    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13810    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13811    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13812    assert_eq!(split("helloworld"), &["helloworld"]);
13813
13814    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13815}
13816
13817#[gpui::test]
13818async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13819    init_test(cx, |_| {});
13820
13821    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13822    let mut assert = |before, after| {
13823        let _state_context = cx.set_state(before);
13824        cx.run_until_parked();
13825        cx.update_editor(|editor, window, cx| {
13826            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13827        });
13828        cx.run_until_parked();
13829        cx.assert_editor_state(after);
13830    };
13831
13832    // Outside bracket jumps to outside of matching bracket
13833    assert("console.logˇ(var);", "console.log(var)ˇ;");
13834    assert("console.log(var)ˇ;", "console.logˇ(var);");
13835
13836    // Inside bracket jumps to inside of matching bracket
13837    assert("console.log(ˇvar);", "console.log(varˇ);");
13838    assert("console.log(varˇ);", "console.log(ˇvar);");
13839
13840    // When outside a bracket and inside, favor jumping to the inside bracket
13841    assert(
13842        "console.log('foo', [1, 2, 3]ˇ);",
13843        "console.log(ˇ'foo', [1, 2, 3]);",
13844    );
13845    assert(
13846        "console.log(ˇ'foo', [1, 2, 3]);",
13847        "console.log('foo', [1, 2, 3]ˇ);",
13848    );
13849
13850    // Bias forward if two options are equally likely
13851    assert(
13852        "let result = curried_fun()ˇ();",
13853        "let result = curried_fun()()ˇ;",
13854    );
13855
13856    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13857    assert(
13858        indoc! {"
13859            function test() {
13860                console.log('test')ˇ
13861            }"},
13862        indoc! {"
13863            function test() {
13864                console.logˇ('test')
13865            }"},
13866    );
13867}
13868
13869#[gpui::test]
13870async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13871    init_test(cx, |_| {});
13872
13873    let fs = FakeFs::new(cx.executor());
13874    fs.insert_tree(
13875        path!("/a"),
13876        json!({
13877            "main.rs": "fn main() { let a = 5; }",
13878            "other.rs": "// Test file",
13879        }),
13880    )
13881    .await;
13882    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13883
13884    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13885    language_registry.add(Arc::new(Language::new(
13886        LanguageConfig {
13887            name: "Rust".into(),
13888            matcher: LanguageMatcher {
13889                path_suffixes: vec!["rs".to_string()],
13890                ..Default::default()
13891            },
13892            brackets: BracketPairConfig {
13893                pairs: vec![BracketPair {
13894                    start: "{".to_string(),
13895                    end: "}".to_string(),
13896                    close: true,
13897                    surround: true,
13898                    newline: true,
13899                }],
13900                disabled_scopes_by_bracket_ix: Vec::new(),
13901            },
13902            ..Default::default()
13903        },
13904        Some(tree_sitter_rust::LANGUAGE.into()),
13905    )));
13906    let mut fake_servers = language_registry.register_fake_lsp(
13907        "Rust",
13908        FakeLspAdapter {
13909            capabilities: lsp::ServerCapabilities {
13910                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13911                    first_trigger_character: "{".to_string(),
13912                    more_trigger_character: None,
13913                }),
13914                ..Default::default()
13915            },
13916            ..Default::default()
13917        },
13918    );
13919
13920    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13921
13922    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13923
13924    let worktree_id = workspace
13925        .update(cx, |workspace, _, cx| {
13926            workspace.project().update(cx, |project, cx| {
13927                project.worktrees(cx).next().unwrap().read(cx).id()
13928            })
13929        })
13930        .unwrap();
13931
13932    let buffer = project
13933        .update(cx, |project, cx| {
13934            project.open_local_buffer(path!("/a/main.rs"), cx)
13935        })
13936        .await
13937        .unwrap();
13938    let editor_handle = workspace
13939        .update(cx, |workspace, window, cx| {
13940            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13941        })
13942        .unwrap()
13943        .await
13944        .unwrap()
13945        .downcast::<Editor>()
13946        .unwrap();
13947
13948    cx.executor().start_waiting();
13949    let fake_server = fake_servers.next().await.unwrap();
13950
13951    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13952        |params, _| async move {
13953            assert_eq!(
13954                params.text_document_position.text_document.uri,
13955                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13956            );
13957            assert_eq!(
13958                params.text_document_position.position,
13959                lsp::Position::new(0, 21),
13960            );
13961
13962            Ok(Some(vec![lsp::TextEdit {
13963                new_text: "]".to_string(),
13964                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13965            }]))
13966        },
13967    );
13968
13969    editor_handle.update_in(cx, |editor, window, cx| {
13970        window.focus(&editor.focus_handle(cx));
13971        editor.change_selections(None, window, cx, |s| {
13972            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13973        });
13974        editor.handle_input("{", window, cx);
13975    });
13976
13977    cx.executor().run_until_parked();
13978
13979    buffer.update(cx, |buffer, _| {
13980        assert_eq!(
13981            buffer.text(),
13982            "fn main() { let a = {5}; }",
13983            "No extra braces from on type formatting should appear in the buffer"
13984        )
13985    });
13986}
13987
13988#[gpui::test]
13989async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13990    init_test(cx, |_| {});
13991
13992    let fs = FakeFs::new(cx.executor());
13993    fs.insert_tree(
13994        path!("/a"),
13995        json!({
13996            "main.rs": "fn main() { let a = 5; }",
13997            "other.rs": "// Test file",
13998        }),
13999    )
14000    .await;
14001
14002    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14003
14004    let server_restarts = Arc::new(AtomicUsize::new(0));
14005    let closure_restarts = Arc::clone(&server_restarts);
14006    let language_server_name = "test language server";
14007    let language_name: LanguageName = "Rust".into();
14008
14009    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14010    language_registry.add(Arc::new(Language::new(
14011        LanguageConfig {
14012            name: language_name.clone(),
14013            matcher: LanguageMatcher {
14014                path_suffixes: vec!["rs".to_string()],
14015                ..Default::default()
14016            },
14017            ..Default::default()
14018        },
14019        Some(tree_sitter_rust::LANGUAGE.into()),
14020    )));
14021    let mut fake_servers = language_registry.register_fake_lsp(
14022        "Rust",
14023        FakeLspAdapter {
14024            name: language_server_name,
14025            initialization_options: Some(json!({
14026                "testOptionValue": true
14027            })),
14028            initializer: Some(Box::new(move |fake_server| {
14029                let task_restarts = Arc::clone(&closure_restarts);
14030                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14031                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14032                    futures::future::ready(Ok(()))
14033                });
14034            })),
14035            ..Default::default()
14036        },
14037    );
14038
14039    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14040    let _buffer = project
14041        .update(cx, |project, cx| {
14042            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14043        })
14044        .await
14045        .unwrap();
14046    let _fake_server = fake_servers.next().await.unwrap();
14047    update_test_language_settings(cx, |language_settings| {
14048        language_settings.languages.insert(
14049            language_name.clone(),
14050            LanguageSettingsContent {
14051                tab_size: NonZeroU32::new(8),
14052                ..Default::default()
14053            },
14054        );
14055    });
14056    cx.executor().run_until_parked();
14057    assert_eq!(
14058        server_restarts.load(atomic::Ordering::Acquire),
14059        0,
14060        "Should not restart LSP server on an unrelated change"
14061    );
14062
14063    update_test_project_settings(cx, |project_settings| {
14064        project_settings.lsp.insert(
14065            "Some other server name".into(),
14066            LspSettings {
14067                binary: None,
14068                settings: None,
14069                initialization_options: Some(json!({
14070                    "some other init value": false
14071                })),
14072                enable_lsp_tasks: false,
14073            },
14074        );
14075    });
14076    cx.executor().run_until_parked();
14077    assert_eq!(
14078        server_restarts.load(atomic::Ordering::Acquire),
14079        0,
14080        "Should not restart LSP server on an unrelated LSP settings change"
14081    );
14082
14083    update_test_project_settings(cx, |project_settings| {
14084        project_settings.lsp.insert(
14085            language_server_name.into(),
14086            LspSettings {
14087                binary: None,
14088                settings: None,
14089                initialization_options: Some(json!({
14090                    "anotherInitValue": false
14091                })),
14092                enable_lsp_tasks: false,
14093            },
14094        );
14095    });
14096    cx.executor().run_until_parked();
14097    assert_eq!(
14098        server_restarts.load(atomic::Ordering::Acquire),
14099        1,
14100        "Should restart LSP server on a related LSP settings change"
14101    );
14102
14103    update_test_project_settings(cx, |project_settings| {
14104        project_settings.lsp.insert(
14105            language_server_name.into(),
14106            LspSettings {
14107                binary: None,
14108                settings: None,
14109                initialization_options: Some(json!({
14110                    "anotherInitValue": false
14111                })),
14112                enable_lsp_tasks: false,
14113            },
14114        );
14115    });
14116    cx.executor().run_until_parked();
14117    assert_eq!(
14118        server_restarts.load(atomic::Ordering::Acquire),
14119        1,
14120        "Should not restart LSP server on a related LSP settings change that is the same"
14121    );
14122
14123    update_test_project_settings(cx, |project_settings| {
14124        project_settings.lsp.insert(
14125            language_server_name.into(),
14126            LspSettings {
14127                binary: None,
14128                settings: None,
14129                initialization_options: None,
14130                enable_lsp_tasks: false,
14131            },
14132        );
14133    });
14134    cx.executor().run_until_parked();
14135    assert_eq!(
14136        server_restarts.load(atomic::Ordering::Acquire),
14137        2,
14138        "Should restart LSP server on another related LSP settings change"
14139    );
14140}
14141
14142#[gpui::test]
14143async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14144    init_test(cx, |_| {});
14145
14146    let mut cx = EditorLspTestContext::new_rust(
14147        lsp::ServerCapabilities {
14148            completion_provider: Some(lsp::CompletionOptions {
14149                trigger_characters: Some(vec![".".to_string()]),
14150                resolve_provider: Some(true),
14151                ..Default::default()
14152            }),
14153            ..Default::default()
14154        },
14155        cx,
14156    )
14157    .await;
14158
14159    cx.set_state("fn main() { let a = 2ˇ; }");
14160    cx.simulate_keystroke(".");
14161    let completion_item = lsp::CompletionItem {
14162        label: "some".into(),
14163        kind: Some(lsp::CompletionItemKind::SNIPPET),
14164        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14165        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14166            kind: lsp::MarkupKind::Markdown,
14167            value: "```rust\nSome(2)\n```".to_string(),
14168        })),
14169        deprecated: Some(false),
14170        sort_text: Some("fffffff2".to_string()),
14171        filter_text: Some("some".to_string()),
14172        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14173        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14174            range: lsp::Range {
14175                start: lsp::Position {
14176                    line: 0,
14177                    character: 22,
14178                },
14179                end: lsp::Position {
14180                    line: 0,
14181                    character: 22,
14182                },
14183            },
14184            new_text: "Some(2)".to_string(),
14185        })),
14186        additional_text_edits: Some(vec![lsp::TextEdit {
14187            range: lsp::Range {
14188                start: lsp::Position {
14189                    line: 0,
14190                    character: 20,
14191                },
14192                end: lsp::Position {
14193                    line: 0,
14194                    character: 22,
14195                },
14196            },
14197            new_text: "".to_string(),
14198        }]),
14199        ..Default::default()
14200    };
14201
14202    let closure_completion_item = completion_item.clone();
14203    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14204        let task_completion_item = closure_completion_item.clone();
14205        async move {
14206            Ok(Some(lsp::CompletionResponse::Array(vec![
14207                task_completion_item,
14208            ])))
14209        }
14210    });
14211
14212    request.next().await;
14213
14214    cx.condition(|editor, _| editor.context_menu_visible())
14215        .await;
14216    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14217        editor
14218            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14219            .unwrap()
14220    });
14221    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14222
14223    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14224        let task_completion_item = completion_item.clone();
14225        async move { Ok(task_completion_item) }
14226    })
14227    .next()
14228    .await
14229    .unwrap();
14230    apply_additional_edits.await.unwrap();
14231    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14232}
14233
14234#[gpui::test]
14235async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14236    init_test(cx, |_| {});
14237
14238    let mut cx = EditorLspTestContext::new_rust(
14239        lsp::ServerCapabilities {
14240            completion_provider: Some(lsp::CompletionOptions {
14241                trigger_characters: Some(vec![".".to_string()]),
14242                resolve_provider: Some(true),
14243                ..Default::default()
14244            }),
14245            ..Default::default()
14246        },
14247        cx,
14248    )
14249    .await;
14250
14251    cx.set_state("fn main() { let a = 2ˇ; }");
14252    cx.simulate_keystroke(".");
14253
14254    let item1 = lsp::CompletionItem {
14255        label: "method id()".to_string(),
14256        filter_text: Some("id".to_string()),
14257        detail: None,
14258        documentation: None,
14259        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14260            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14261            new_text: ".id".to_string(),
14262        })),
14263        ..lsp::CompletionItem::default()
14264    };
14265
14266    let item2 = lsp::CompletionItem {
14267        label: "other".to_string(),
14268        filter_text: Some("other".to_string()),
14269        detail: None,
14270        documentation: None,
14271        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14272            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14273            new_text: ".other".to_string(),
14274        })),
14275        ..lsp::CompletionItem::default()
14276    };
14277
14278    let item1 = item1.clone();
14279    cx.set_request_handler::<lsp::request::Completion, _, _>({
14280        let item1 = item1.clone();
14281        move |_, _, _| {
14282            let item1 = item1.clone();
14283            let item2 = item2.clone();
14284            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14285        }
14286    })
14287    .next()
14288    .await;
14289
14290    cx.condition(|editor, _| editor.context_menu_visible())
14291        .await;
14292    cx.update_editor(|editor, _, _| {
14293        let context_menu = editor.context_menu.borrow_mut();
14294        let context_menu = context_menu
14295            .as_ref()
14296            .expect("Should have the context menu deployed");
14297        match context_menu {
14298            CodeContextMenu::Completions(completions_menu) => {
14299                let completions = completions_menu.completions.borrow_mut();
14300                assert_eq!(
14301                    completions
14302                        .iter()
14303                        .map(|completion| &completion.label.text)
14304                        .collect::<Vec<_>>(),
14305                    vec!["method id()", "other"]
14306                )
14307            }
14308            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14309        }
14310    });
14311
14312    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14313        let item1 = item1.clone();
14314        move |_, item_to_resolve, _| {
14315            let item1 = item1.clone();
14316            async move {
14317                if item1 == item_to_resolve {
14318                    Ok(lsp::CompletionItem {
14319                        label: "method id()".to_string(),
14320                        filter_text: Some("id".to_string()),
14321                        detail: Some("Now resolved!".to_string()),
14322                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14323                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14324                            range: lsp::Range::new(
14325                                lsp::Position::new(0, 22),
14326                                lsp::Position::new(0, 22),
14327                            ),
14328                            new_text: ".id".to_string(),
14329                        })),
14330                        ..lsp::CompletionItem::default()
14331                    })
14332                } else {
14333                    Ok(item_to_resolve)
14334                }
14335            }
14336        }
14337    })
14338    .next()
14339    .await
14340    .unwrap();
14341    cx.run_until_parked();
14342
14343    cx.update_editor(|editor, window, cx| {
14344        editor.context_menu_next(&Default::default(), window, cx);
14345    });
14346
14347    cx.update_editor(|editor, _, _| {
14348        let context_menu = editor.context_menu.borrow_mut();
14349        let context_menu = context_menu
14350            .as_ref()
14351            .expect("Should have the context menu deployed");
14352        match context_menu {
14353            CodeContextMenu::Completions(completions_menu) => {
14354                let completions = completions_menu.completions.borrow_mut();
14355                assert_eq!(
14356                    completions
14357                        .iter()
14358                        .map(|completion| &completion.label.text)
14359                        .collect::<Vec<_>>(),
14360                    vec!["method id() Now resolved!", "other"],
14361                    "Should update first completion label, but not second as the filter text did not match."
14362                );
14363            }
14364            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14365        }
14366    });
14367}
14368
14369#[gpui::test]
14370async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14371    init_test(cx, |_| {});
14372    let mut cx = EditorLspTestContext::new_rust(
14373        lsp::ServerCapabilities {
14374            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14375            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14376            completion_provider: Some(lsp::CompletionOptions {
14377                resolve_provider: Some(true),
14378                ..Default::default()
14379            }),
14380            ..Default::default()
14381        },
14382        cx,
14383    )
14384    .await;
14385    cx.set_state(indoc! {"
14386        struct TestStruct {
14387            field: i32
14388        }
14389
14390        fn mainˇ() {
14391            let unused_var = 42;
14392            let test_struct = TestStruct { field: 42 };
14393        }
14394    "});
14395    let symbol_range = cx.lsp_range(indoc! {"
14396        struct TestStruct {
14397            field: i32
14398        }
14399
14400        «fn main»() {
14401            let unused_var = 42;
14402            let test_struct = TestStruct { field: 42 };
14403        }
14404    "});
14405    let mut hover_requests =
14406        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14407            Ok(Some(lsp::Hover {
14408                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14409                    kind: lsp::MarkupKind::Markdown,
14410                    value: "Function documentation".to_string(),
14411                }),
14412                range: Some(symbol_range),
14413            }))
14414        });
14415
14416    // Case 1: Test that code action menu hide hover popover
14417    cx.dispatch_action(Hover);
14418    hover_requests.next().await;
14419    cx.condition(|editor, _| editor.hover_state.visible()).await;
14420    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14421        move |_, _, _| async move {
14422            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14423                lsp::CodeAction {
14424                    title: "Remove unused variable".to_string(),
14425                    kind: Some(CodeActionKind::QUICKFIX),
14426                    edit: Some(lsp::WorkspaceEdit {
14427                        changes: Some(
14428                            [(
14429                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14430                                vec![lsp::TextEdit {
14431                                    range: lsp::Range::new(
14432                                        lsp::Position::new(5, 4),
14433                                        lsp::Position::new(5, 27),
14434                                    ),
14435                                    new_text: "".to_string(),
14436                                }],
14437                            )]
14438                            .into_iter()
14439                            .collect(),
14440                        ),
14441                        ..Default::default()
14442                    }),
14443                    ..Default::default()
14444                },
14445            )]))
14446        },
14447    );
14448    cx.update_editor(|editor, window, cx| {
14449        editor.toggle_code_actions(
14450            &ToggleCodeActions {
14451                deployed_from: None,
14452                quick_launch: false,
14453            },
14454            window,
14455            cx,
14456        );
14457    });
14458    code_action_requests.next().await;
14459    cx.run_until_parked();
14460    cx.condition(|editor, _| editor.context_menu_visible())
14461        .await;
14462    cx.update_editor(|editor, _, _| {
14463        assert!(
14464            !editor.hover_state.visible(),
14465            "Hover popover should be hidden when code action menu is shown"
14466        );
14467        // Hide code actions
14468        editor.context_menu.take();
14469    });
14470
14471    // Case 2: Test that code completions hide hover popover
14472    cx.dispatch_action(Hover);
14473    hover_requests.next().await;
14474    cx.condition(|editor, _| editor.hover_state.visible()).await;
14475    let counter = Arc::new(AtomicUsize::new(0));
14476    let mut completion_requests =
14477        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14478            let counter = counter.clone();
14479            async move {
14480                counter.fetch_add(1, atomic::Ordering::Release);
14481                Ok(Some(lsp::CompletionResponse::Array(vec![
14482                    lsp::CompletionItem {
14483                        label: "main".into(),
14484                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14485                        detail: Some("() -> ()".to_string()),
14486                        ..Default::default()
14487                    },
14488                    lsp::CompletionItem {
14489                        label: "TestStruct".into(),
14490                        kind: Some(lsp::CompletionItemKind::STRUCT),
14491                        detail: Some("struct TestStruct".to_string()),
14492                        ..Default::default()
14493                    },
14494                ])))
14495            }
14496        });
14497    cx.update_editor(|editor, window, cx| {
14498        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14499    });
14500    completion_requests.next().await;
14501    cx.condition(|editor, _| editor.context_menu_visible())
14502        .await;
14503    cx.update_editor(|editor, _, _| {
14504        assert!(
14505            !editor.hover_state.visible(),
14506            "Hover popover should be hidden when completion menu is shown"
14507        );
14508    });
14509}
14510
14511#[gpui::test]
14512async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14513    init_test(cx, |_| {});
14514
14515    let mut cx = EditorLspTestContext::new_rust(
14516        lsp::ServerCapabilities {
14517            completion_provider: Some(lsp::CompletionOptions {
14518                trigger_characters: Some(vec![".".to_string()]),
14519                resolve_provider: Some(true),
14520                ..Default::default()
14521            }),
14522            ..Default::default()
14523        },
14524        cx,
14525    )
14526    .await;
14527
14528    cx.set_state("fn main() { let a = 2ˇ; }");
14529    cx.simulate_keystroke(".");
14530
14531    let unresolved_item_1 = lsp::CompletionItem {
14532        label: "id".to_string(),
14533        filter_text: Some("id".to_string()),
14534        detail: None,
14535        documentation: None,
14536        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14537            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14538            new_text: ".id".to_string(),
14539        })),
14540        ..lsp::CompletionItem::default()
14541    };
14542    let resolved_item_1 = lsp::CompletionItem {
14543        additional_text_edits: Some(vec![lsp::TextEdit {
14544            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14545            new_text: "!!".to_string(),
14546        }]),
14547        ..unresolved_item_1.clone()
14548    };
14549    let unresolved_item_2 = lsp::CompletionItem {
14550        label: "other".to_string(),
14551        filter_text: Some("other".to_string()),
14552        detail: None,
14553        documentation: None,
14554        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14555            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14556            new_text: ".other".to_string(),
14557        })),
14558        ..lsp::CompletionItem::default()
14559    };
14560    let resolved_item_2 = lsp::CompletionItem {
14561        additional_text_edits: Some(vec![lsp::TextEdit {
14562            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14563            new_text: "??".to_string(),
14564        }]),
14565        ..unresolved_item_2.clone()
14566    };
14567
14568    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14569    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14570    cx.lsp
14571        .server
14572        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14573            let unresolved_item_1 = unresolved_item_1.clone();
14574            let resolved_item_1 = resolved_item_1.clone();
14575            let unresolved_item_2 = unresolved_item_2.clone();
14576            let resolved_item_2 = resolved_item_2.clone();
14577            let resolve_requests_1 = resolve_requests_1.clone();
14578            let resolve_requests_2 = resolve_requests_2.clone();
14579            move |unresolved_request, _| {
14580                let unresolved_item_1 = unresolved_item_1.clone();
14581                let resolved_item_1 = resolved_item_1.clone();
14582                let unresolved_item_2 = unresolved_item_2.clone();
14583                let resolved_item_2 = resolved_item_2.clone();
14584                let resolve_requests_1 = resolve_requests_1.clone();
14585                let resolve_requests_2 = resolve_requests_2.clone();
14586                async move {
14587                    if unresolved_request == unresolved_item_1 {
14588                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14589                        Ok(resolved_item_1.clone())
14590                    } else if unresolved_request == unresolved_item_2 {
14591                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14592                        Ok(resolved_item_2.clone())
14593                    } else {
14594                        panic!("Unexpected completion item {unresolved_request:?}")
14595                    }
14596                }
14597            }
14598        })
14599        .detach();
14600
14601    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14602        let unresolved_item_1 = unresolved_item_1.clone();
14603        let unresolved_item_2 = unresolved_item_2.clone();
14604        async move {
14605            Ok(Some(lsp::CompletionResponse::Array(vec![
14606                unresolved_item_1,
14607                unresolved_item_2,
14608            ])))
14609        }
14610    })
14611    .next()
14612    .await;
14613
14614    cx.condition(|editor, _| editor.context_menu_visible())
14615        .await;
14616    cx.update_editor(|editor, _, _| {
14617        let context_menu = editor.context_menu.borrow_mut();
14618        let context_menu = context_menu
14619            .as_ref()
14620            .expect("Should have the context menu deployed");
14621        match context_menu {
14622            CodeContextMenu::Completions(completions_menu) => {
14623                let completions = completions_menu.completions.borrow_mut();
14624                assert_eq!(
14625                    completions
14626                        .iter()
14627                        .map(|completion| &completion.label.text)
14628                        .collect::<Vec<_>>(),
14629                    vec!["id", "other"]
14630                )
14631            }
14632            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14633        }
14634    });
14635    cx.run_until_parked();
14636
14637    cx.update_editor(|editor, window, cx| {
14638        editor.context_menu_next(&ContextMenuNext, window, cx);
14639    });
14640    cx.run_until_parked();
14641    cx.update_editor(|editor, window, cx| {
14642        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14643    });
14644    cx.run_until_parked();
14645    cx.update_editor(|editor, window, cx| {
14646        editor.context_menu_next(&ContextMenuNext, window, cx);
14647    });
14648    cx.run_until_parked();
14649    cx.update_editor(|editor, window, cx| {
14650        editor
14651            .compose_completion(&ComposeCompletion::default(), window, cx)
14652            .expect("No task returned")
14653    })
14654    .await
14655    .expect("Completion failed");
14656    cx.run_until_parked();
14657
14658    cx.update_editor(|editor, _, cx| {
14659        assert_eq!(
14660            resolve_requests_1.load(atomic::Ordering::Acquire),
14661            1,
14662            "Should always resolve once despite multiple selections"
14663        );
14664        assert_eq!(
14665            resolve_requests_2.load(atomic::Ordering::Acquire),
14666            1,
14667            "Should always resolve once after multiple selections and applying the completion"
14668        );
14669        assert_eq!(
14670            editor.text(cx),
14671            "fn main() { let a = ??.other; }",
14672            "Should use resolved data when applying the completion"
14673        );
14674    });
14675}
14676
14677#[gpui::test]
14678async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14679    init_test(cx, |_| {});
14680
14681    let item_0 = lsp::CompletionItem {
14682        label: "abs".into(),
14683        insert_text: Some("abs".into()),
14684        data: Some(json!({ "very": "special"})),
14685        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14686        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14687            lsp::InsertReplaceEdit {
14688                new_text: "abs".to_string(),
14689                insert: lsp::Range::default(),
14690                replace: lsp::Range::default(),
14691            },
14692        )),
14693        ..lsp::CompletionItem::default()
14694    };
14695    let items = iter::once(item_0.clone())
14696        .chain((11..51).map(|i| lsp::CompletionItem {
14697            label: format!("item_{}", i),
14698            insert_text: Some(format!("item_{}", i)),
14699            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14700            ..lsp::CompletionItem::default()
14701        }))
14702        .collect::<Vec<_>>();
14703
14704    let default_commit_characters = vec!["?".to_string()];
14705    let default_data = json!({ "default": "data"});
14706    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14707    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14708    let default_edit_range = lsp::Range {
14709        start: lsp::Position {
14710            line: 0,
14711            character: 5,
14712        },
14713        end: lsp::Position {
14714            line: 0,
14715            character: 5,
14716        },
14717    };
14718
14719    let mut cx = EditorLspTestContext::new_rust(
14720        lsp::ServerCapabilities {
14721            completion_provider: Some(lsp::CompletionOptions {
14722                trigger_characters: Some(vec![".".to_string()]),
14723                resolve_provider: Some(true),
14724                ..Default::default()
14725            }),
14726            ..Default::default()
14727        },
14728        cx,
14729    )
14730    .await;
14731
14732    cx.set_state("fn main() { let a = 2ˇ; }");
14733    cx.simulate_keystroke(".");
14734
14735    let completion_data = default_data.clone();
14736    let completion_characters = default_commit_characters.clone();
14737    let completion_items = items.clone();
14738    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14739        let default_data = completion_data.clone();
14740        let default_commit_characters = completion_characters.clone();
14741        let items = completion_items.clone();
14742        async move {
14743            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14744                items,
14745                item_defaults: Some(lsp::CompletionListItemDefaults {
14746                    data: Some(default_data.clone()),
14747                    commit_characters: Some(default_commit_characters.clone()),
14748                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14749                        default_edit_range,
14750                    )),
14751                    insert_text_format: Some(default_insert_text_format),
14752                    insert_text_mode: Some(default_insert_text_mode),
14753                }),
14754                ..lsp::CompletionList::default()
14755            })))
14756        }
14757    })
14758    .next()
14759    .await;
14760
14761    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14762    cx.lsp
14763        .server
14764        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14765            let closure_resolved_items = resolved_items.clone();
14766            move |item_to_resolve, _| {
14767                let closure_resolved_items = closure_resolved_items.clone();
14768                async move {
14769                    closure_resolved_items.lock().push(item_to_resolve.clone());
14770                    Ok(item_to_resolve)
14771                }
14772            }
14773        })
14774        .detach();
14775
14776    cx.condition(|editor, _| editor.context_menu_visible())
14777        .await;
14778    cx.run_until_parked();
14779    cx.update_editor(|editor, _, _| {
14780        let menu = editor.context_menu.borrow_mut();
14781        match menu.as_ref().expect("should have the completions menu") {
14782            CodeContextMenu::Completions(completions_menu) => {
14783                assert_eq!(
14784                    completions_menu
14785                        .entries
14786                        .borrow()
14787                        .iter()
14788                        .map(|mat| mat.string.clone())
14789                        .collect::<Vec<String>>(),
14790                    items
14791                        .iter()
14792                        .map(|completion| completion.label.clone())
14793                        .collect::<Vec<String>>()
14794                );
14795            }
14796            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14797        }
14798    });
14799    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14800    // with 4 from the end.
14801    assert_eq!(
14802        *resolved_items.lock(),
14803        [&items[0..16], &items[items.len() - 4..items.len()]]
14804            .concat()
14805            .iter()
14806            .cloned()
14807            .map(|mut item| {
14808                if item.data.is_none() {
14809                    item.data = Some(default_data.clone());
14810                }
14811                item
14812            })
14813            .collect::<Vec<lsp::CompletionItem>>(),
14814        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14815    );
14816    resolved_items.lock().clear();
14817
14818    cx.update_editor(|editor, window, cx| {
14819        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14820    });
14821    cx.run_until_parked();
14822    // Completions that have already been resolved are skipped.
14823    assert_eq!(
14824        *resolved_items.lock(),
14825        items[items.len() - 16..items.len() - 4]
14826            .iter()
14827            .cloned()
14828            .map(|mut item| {
14829                if item.data.is_none() {
14830                    item.data = Some(default_data.clone());
14831                }
14832                item
14833            })
14834            .collect::<Vec<lsp::CompletionItem>>()
14835    );
14836    resolved_items.lock().clear();
14837}
14838
14839#[gpui::test]
14840async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14841    init_test(cx, |_| {});
14842
14843    let mut cx = EditorLspTestContext::new(
14844        Language::new(
14845            LanguageConfig {
14846                matcher: LanguageMatcher {
14847                    path_suffixes: vec!["jsx".into()],
14848                    ..Default::default()
14849                },
14850                overrides: [(
14851                    "element".into(),
14852                    LanguageConfigOverride {
14853                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14854                        ..Default::default()
14855                    },
14856                )]
14857                .into_iter()
14858                .collect(),
14859                ..Default::default()
14860            },
14861            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14862        )
14863        .with_override_query("(jsx_self_closing_element) @element")
14864        .unwrap(),
14865        lsp::ServerCapabilities {
14866            completion_provider: Some(lsp::CompletionOptions {
14867                trigger_characters: Some(vec![":".to_string()]),
14868                ..Default::default()
14869            }),
14870            ..Default::default()
14871        },
14872        cx,
14873    )
14874    .await;
14875
14876    cx.lsp
14877        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14878            Ok(Some(lsp::CompletionResponse::Array(vec![
14879                lsp::CompletionItem {
14880                    label: "bg-blue".into(),
14881                    ..Default::default()
14882                },
14883                lsp::CompletionItem {
14884                    label: "bg-red".into(),
14885                    ..Default::default()
14886                },
14887                lsp::CompletionItem {
14888                    label: "bg-yellow".into(),
14889                    ..Default::default()
14890                },
14891            ])))
14892        });
14893
14894    cx.set_state(r#"<p class="bgˇ" />"#);
14895
14896    // Trigger completion when typing a dash, because the dash is an extra
14897    // word character in the 'element' scope, which contains the cursor.
14898    cx.simulate_keystroke("-");
14899    cx.executor().run_until_parked();
14900    cx.update_editor(|editor, _, _| {
14901        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14902        {
14903            assert_eq!(
14904                completion_menu_entries(&menu),
14905                &["bg-red", "bg-blue", "bg-yellow"]
14906            );
14907        } else {
14908            panic!("expected completion menu to be open");
14909        }
14910    });
14911
14912    cx.simulate_keystroke("l");
14913    cx.executor().run_until_parked();
14914    cx.update_editor(|editor, _, _| {
14915        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14916        {
14917            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14918        } else {
14919            panic!("expected completion menu to be open");
14920        }
14921    });
14922
14923    // When filtering completions, consider the character after the '-' to
14924    // be the start of a subword.
14925    cx.set_state(r#"<p class="yelˇ" />"#);
14926    cx.simulate_keystroke("l");
14927    cx.executor().run_until_parked();
14928    cx.update_editor(|editor, _, _| {
14929        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14930        {
14931            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14932        } else {
14933            panic!("expected completion menu to be open");
14934        }
14935    });
14936}
14937
14938fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14939    let entries = menu.entries.borrow();
14940    entries.iter().map(|mat| mat.string.clone()).collect()
14941}
14942
14943#[gpui::test]
14944async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14945    init_test(cx, |settings| {
14946        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14947            FormatterList(vec![Formatter::Prettier].into()),
14948        ))
14949    });
14950
14951    let fs = FakeFs::new(cx.executor());
14952    fs.insert_file(path!("/file.ts"), Default::default()).await;
14953
14954    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14955    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14956
14957    language_registry.add(Arc::new(Language::new(
14958        LanguageConfig {
14959            name: "TypeScript".into(),
14960            matcher: LanguageMatcher {
14961                path_suffixes: vec!["ts".to_string()],
14962                ..Default::default()
14963            },
14964            ..Default::default()
14965        },
14966        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14967    )));
14968    update_test_language_settings(cx, |settings| {
14969        settings.defaults.prettier = Some(PrettierSettings {
14970            allowed: true,
14971            ..PrettierSettings::default()
14972        });
14973    });
14974
14975    let test_plugin = "test_plugin";
14976    let _ = language_registry.register_fake_lsp(
14977        "TypeScript",
14978        FakeLspAdapter {
14979            prettier_plugins: vec![test_plugin],
14980            ..Default::default()
14981        },
14982    );
14983
14984    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14985    let buffer = project
14986        .update(cx, |project, cx| {
14987            project.open_local_buffer(path!("/file.ts"), cx)
14988        })
14989        .await
14990        .unwrap();
14991
14992    let buffer_text = "one\ntwo\nthree\n";
14993    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14994    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14995    editor.update_in(cx, |editor, window, cx| {
14996        editor.set_text(buffer_text, window, cx)
14997    });
14998
14999    editor
15000        .update_in(cx, |editor, window, cx| {
15001            editor.perform_format(
15002                project.clone(),
15003                FormatTrigger::Manual,
15004                FormatTarget::Buffers,
15005                window,
15006                cx,
15007            )
15008        })
15009        .unwrap()
15010        .await;
15011    assert_eq!(
15012        editor.update(cx, |editor, cx| editor.text(cx)),
15013        buffer_text.to_string() + prettier_format_suffix,
15014        "Test prettier formatting was not applied to the original buffer text",
15015    );
15016
15017    update_test_language_settings(cx, |settings| {
15018        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15019    });
15020    let format = editor.update_in(cx, |editor, window, cx| {
15021        editor.perform_format(
15022            project.clone(),
15023            FormatTrigger::Manual,
15024            FormatTarget::Buffers,
15025            window,
15026            cx,
15027        )
15028    });
15029    format.await.unwrap();
15030    assert_eq!(
15031        editor.update(cx, |editor, cx| editor.text(cx)),
15032        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15033        "Autoformatting (via test prettier) was not applied to the original buffer text",
15034    );
15035}
15036
15037#[gpui::test]
15038async fn test_addition_reverts(cx: &mut TestAppContext) {
15039    init_test(cx, |_| {});
15040    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15041    let base_text = indoc! {r#"
15042        struct Row;
15043        struct Row1;
15044        struct Row2;
15045
15046        struct Row4;
15047        struct Row5;
15048        struct Row6;
15049
15050        struct Row8;
15051        struct Row9;
15052        struct Row10;"#};
15053
15054    // When addition hunks are not adjacent to carets, no hunk revert is performed
15055    assert_hunk_revert(
15056        indoc! {r#"struct Row;
15057                   struct Row1;
15058                   struct Row1.1;
15059                   struct Row1.2;
15060                   struct Row2;ˇ
15061
15062                   struct Row4;
15063                   struct Row5;
15064                   struct Row6;
15065
15066                   struct Row8;
15067                   ˇstruct Row9;
15068                   struct Row9.1;
15069                   struct Row9.2;
15070                   struct Row9.3;
15071                   struct Row10;"#},
15072        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15073        indoc! {r#"struct Row;
15074                   struct Row1;
15075                   struct Row1.1;
15076                   struct Row1.2;
15077                   struct Row2;ˇ
15078
15079                   struct Row4;
15080                   struct Row5;
15081                   struct Row6;
15082
15083                   struct Row8;
15084                   ˇstruct Row9;
15085                   struct Row9.1;
15086                   struct Row9.2;
15087                   struct Row9.3;
15088                   struct Row10;"#},
15089        base_text,
15090        &mut cx,
15091    );
15092    // Same for selections
15093    assert_hunk_revert(
15094        indoc! {r#"struct Row;
15095                   struct Row1;
15096                   struct Row2;
15097                   struct Row2.1;
15098                   struct Row2.2;
15099                   «ˇ
15100                   struct Row4;
15101                   struct» Row5;
15102                   «struct Row6;
15103                   ˇ»
15104                   struct Row9.1;
15105                   struct Row9.2;
15106                   struct Row9.3;
15107                   struct Row8;
15108                   struct Row9;
15109                   struct Row10;"#},
15110        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15111        indoc! {r#"struct Row;
15112                   struct Row1;
15113                   struct Row2;
15114                   struct Row2.1;
15115                   struct Row2.2;
15116                   «ˇ
15117                   struct Row4;
15118                   struct» Row5;
15119                   «struct Row6;
15120                   ˇ»
15121                   struct Row9.1;
15122                   struct Row9.2;
15123                   struct Row9.3;
15124                   struct Row8;
15125                   struct Row9;
15126                   struct Row10;"#},
15127        base_text,
15128        &mut cx,
15129    );
15130
15131    // When carets and selections intersect the addition hunks, those are reverted.
15132    // Adjacent carets got merged.
15133    assert_hunk_revert(
15134        indoc! {r#"struct Row;
15135                   ˇ// something on the top
15136                   struct Row1;
15137                   struct Row2;
15138                   struct Roˇw3.1;
15139                   struct Row2.2;
15140                   struct Row2.3;ˇ
15141
15142                   struct Row4;
15143                   struct ˇRow5.1;
15144                   struct Row5.2;
15145                   struct «Rowˇ»5.3;
15146                   struct Row5;
15147                   struct Row6;
15148                   ˇ
15149                   struct Row9.1;
15150                   struct «Rowˇ»9.2;
15151                   struct «ˇRow»9.3;
15152                   struct Row8;
15153                   struct Row9;
15154                   «ˇ// something on bottom»
15155                   struct Row10;"#},
15156        vec![
15157            DiffHunkStatusKind::Added,
15158            DiffHunkStatusKind::Added,
15159            DiffHunkStatusKind::Added,
15160            DiffHunkStatusKind::Added,
15161            DiffHunkStatusKind::Added,
15162        ],
15163        indoc! {r#"struct Row;
15164                   ˇstruct Row1;
15165                   struct Row2;
15166                   ˇ
15167                   struct Row4;
15168                   ˇstruct Row5;
15169                   struct Row6;
15170                   ˇ
15171                   ˇstruct Row8;
15172                   struct Row9;
15173                   ˇstruct Row10;"#},
15174        base_text,
15175        &mut cx,
15176    );
15177}
15178
15179#[gpui::test]
15180async fn test_modification_reverts(cx: &mut TestAppContext) {
15181    init_test(cx, |_| {});
15182    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15183    let base_text = indoc! {r#"
15184        struct Row;
15185        struct Row1;
15186        struct Row2;
15187
15188        struct Row4;
15189        struct Row5;
15190        struct Row6;
15191
15192        struct Row8;
15193        struct Row9;
15194        struct Row10;"#};
15195
15196    // Modification hunks behave the same as the addition ones.
15197    assert_hunk_revert(
15198        indoc! {r#"struct Row;
15199                   struct Row1;
15200                   struct Row33;
15201                   ˇ
15202                   struct Row4;
15203                   struct Row5;
15204                   struct Row6;
15205                   ˇ
15206                   struct Row99;
15207                   struct Row9;
15208                   struct Row10;"#},
15209        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
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        base_text,
15222        &mut cx,
15223    );
15224    assert_hunk_revert(
15225        indoc! {r#"struct Row;
15226                   struct Row1;
15227                   struct Row33;
15228                   «ˇ
15229                   struct Row4;
15230                   struct» Row5;
15231                   «struct Row6;
15232                   ˇ»
15233                   struct Row99;
15234                   struct Row9;
15235                   struct Row10;"#},
15236        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15237        indoc! {r#"struct Row;
15238                   struct Row1;
15239                   struct Row33;
15240                   «ˇ
15241                   struct Row4;
15242                   struct» Row5;
15243                   «struct Row6;
15244                   ˇ»
15245                   struct Row99;
15246                   struct Row9;
15247                   struct Row10;"#},
15248        base_text,
15249        &mut cx,
15250    );
15251
15252    assert_hunk_revert(
15253        indoc! {r#"ˇstruct Row1.1;
15254                   struct Row1;
15255                   «ˇstr»uct Row22;
15256
15257                   struct ˇRow44;
15258                   struct Row5;
15259                   struct «Rˇ»ow66;ˇ
15260
15261                   «struˇ»ct Row88;
15262                   struct Row9;
15263                   struct Row1011;ˇ"#},
15264        vec![
15265            DiffHunkStatusKind::Modified,
15266            DiffHunkStatusKind::Modified,
15267            DiffHunkStatusKind::Modified,
15268            DiffHunkStatusKind::Modified,
15269            DiffHunkStatusKind::Modified,
15270            DiffHunkStatusKind::Modified,
15271        ],
15272        indoc! {r#"struct Row;
15273                   ˇstruct Row1;
15274                   struct Row2;
15275                   ˇ
15276                   struct Row4;
15277                   ˇstruct Row5;
15278                   struct Row6;
15279                   ˇ
15280                   struct Row8;
15281                   ˇstruct Row9;
15282                   struct Row10;ˇ"#},
15283        base_text,
15284        &mut cx,
15285    );
15286}
15287
15288#[gpui::test]
15289async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15290    init_test(cx, |_| {});
15291    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15292    let base_text = indoc! {r#"
15293        one
15294
15295        two
15296        three
15297        "#};
15298
15299    cx.set_head_text(base_text);
15300    cx.set_state("\nˇ\n");
15301    cx.executor().run_until_parked();
15302    cx.update_editor(|editor, _window, cx| {
15303        editor.expand_selected_diff_hunks(cx);
15304    });
15305    cx.executor().run_until_parked();
15306    cx.update_editor(|editor, window, cx| {
15307        editor.backspace(&Default::default(), window, cx);
15308    });
15309    cx.run_until_parked();
15310    cx.assert_state_with_diff(
15311        indoc! {r#"
15312
15313        - two
15314        - threeˇ
15315        +
15316        "#}
15317        .to_string(),
15318    );
15319}
15320
15321#[gpui::test]
15322async fn test_deletion_reverts(cx: &mut TestAppContext) {
15323    init_test(cx, |_| {});
15324    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15325    let base_text = indoc! {r#"struct Row;
15326struct Row1;
15327struct Row2;
15328
15329struct Row4;
15330struct Row5;
15331struct Row6;
15332
15333struct Row8;
15334struct Row9;
15335struct Row10;"#};
15336
15337    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15338    assert_hunk_revert(
15339        indoc! {r#"struct Row;
15340                   struct Row2;
15341
15342                   ˇstruct Row4;
15343                   struct Row5;
15344                   struct Row6;
15345                   ˇ
15346                   struct Row8;
15347                   struct Row10;"#},
15348        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15349        indoc! {r#"struct Row;
15350                   struct Row2;
15351
15352                   ˇstruct Row4;
15353                   struct Row5;
15354                   struct Row6;
15355                   ˇ
15356                   struct Row8;
15357                   struct Row10;"#},
15358        base_text,
15359        &mut cx,
15360    );
15361    assert_hunk_revert(
15362        indoc! {r#"struct Row;
15363                   struct Row2;
15364
15365                   «ˇstruct Row4;
15366                   struct» Row5;
15367                   «struct Row6;
15368                   ˇ»
15369                   struct Row8;
15370                   struct Row10;"#},
15371        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
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        base_text,
15382        &mut cx,
15383    );
15384
15385    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15386    assert_hunk_revert(
15387        indoc! {r#"struct Row;
15388                   ˇstruct Row2;
15389
15390                   struct Row4;
15391                   struct Row5;
15392                   struct Row6;
15393
15394                   struct Row8;ˇ
15395                   struct Row10;"#},
15396        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15397        indoc! {r#"struct Row;
15398                   struct Row1;
15399                   ˇstruct Row2;
15400
15401                   struct Row4;
15402                   struct Row5;
15403                   struct Row6;
15404
15405                   struct Row8;ˇ
15406                   struct Row9;
15407                   struct Row10;"#},
15408        base_text,
15409        &mut cx,
15410    );
15411    assert_hunk_revert(
15412        indoc! {r#"struct Row;
15413                   struct Row2«ˇ;
15414                   struct Row4;
15415                   struct» Row5;
15416                   «struct Row6;
15417
15418                   struct Row8;ˇ»
15419                   struct Row10;"#},
15420        vec![
15421            DiffHunkStatusKind::Deleted,
15422            DiffHunkStatusKind::Deleted,
15423            DiffHunkStatusKind::Deleted,
15424        ],
15425        indoc! {r#"struct Row;
15426                   struct Row1;
15427                   struct Row2«ˇ;
15428
15429                   struct Row4;
15430                   struct» Row5;
15431                   «struct Row6;
15432
15433                   struct Row8;ˇ»
15434                   struct Row9;
15435                   struct Row10;"#},
15436        base_text,
15437        &mut cx,
15438    );
15439}
15440
15441#[gpui::test]
15442async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15443    init_test(cx, |_| {});
15444
15445    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15446    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15447    let base_text_3 =
15448        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15449
15450    let text_1 = edit_first_char_of_every_line(base_text_1);
15451    let text_2 = edit_first_char_of_every_line(base_text_2);
15452    let text_3 = edit_first_char_of_every_line(base_text_3);
15453
15454    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15455    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15456    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15457
15458    let multibuffer = cx.new(|cx| {
15459        let mut multibuffer = MultiBuffer::new(ReadWrite);
15460        multibuffer.push_excerpts(
15461            buffer_1.clone(),
15462            [
15463                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15464                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15465                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15466            ],
15467            cx,
15468        );
15469        multibuffer.push_excerpts(
15470            buffer_2.clone(),
15471            [
15472                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15473                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15474                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15475            ],
15476            cx,
15477        );
15478        multibuffer.push_excerpts(
15479            buffer_3.clone(),
15480            [
15481                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15482                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15483                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15484            ],
15485            cx,
15486        );
15487        multibuffer
15488    });
15489
15490    let fs = FakeFs::new(cx.executor());
15491    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15492    let (editor, cx) = cx
15493        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15494    editor.update_in(cx, |editor, _window, cx| {
15495        for (buffer, diff_base) in [
15496            (buffer_1.clone(), base_text_1),
15497            (buffer_2.clone(), base_text_2),
15498            (buffer_3.clone(), base_text_3),
15499        ] {
15500            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15501            editor
15502                .buffer
15503                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15504        }
15505    });
15506    cx.executor().run_until_parked();
15507
15508    editor.update_in(cx, |editor, window, cx| {
15509        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}");
15510        editor.select_all(&SelectAll, window, cx);
15511        editor.git_restore(&Default::default(), window, cx);
15512    });
15513    cx.executor().run_until_parked();
15514
15515    // When all ranges are selected, all buffer hunks are reverted.
15516    editor.update(cx, |editor, cx| {
15517        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");
15518    });
15519    buffer_1.update(cx, |buffer, _| {
15520        assert_eq!(buffer.text(), base_text_1);
15521    });
15522    buffer_2.update(cx, |buffer, _| {
15523        assert_eq!(buffer.text(), base_text_2);
15524    });
15525    buffer_3.update(cx, |buffer, _| {
15526        assert_eq!(buffer.text(), base_text_3);
15527    });
15528
15529    editor.update_in(cx, |editor, window, cx| {
15530        editor.undo(&Default::default(), window, cx);
15531    });
15532
15533    editor.update_in(cx, |editor, window, cx| {
15534        editor.change_selections(None, window, cx, |s| {
15535            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15536        });
15537        editor.git_restore(&Default::default(), window, cx);
15538    });
15539
15540    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15541    // but not affect buffer_2 and its related excerpts.
15542    editor.update(cx, |editor, cx| {
15543        assert_eq!(
15544            editor.text(cx),
15545            "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}"
15546        );
15547    });
15548    buffer_1.update(cx, |buffer, _| {
15549        assert_eq!(buffer.text(), base_text_1);
15550    });
15551    buffer_2.update(cx, |buffer, _| {
15552        assert_eq!(
15553            buffer.text(),
15554            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15555        );
15556    });
15557    buffer_3.update(cx, |buffer, _| {
15558        assert_eq!(
15559            buffer.text(),
15560            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15561        );
15562    });
15563
15564    fn edit_first_char_of_every_line(text: &str) -> String {
15565        text.split('\n')
15566            .map(|line| format!("X{}", &line[1..]))
15567            .collect::<Vec<_>>()
15568            .join("\n")
15569    }
15570}
15571
15572#[gpui::test]
15573async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15574    init_test(cx, |_| {});
15575
15576    let cols = 4;
15577    let rows = 10;
15578    let sample_text_1 = sample_text(rows, cols, 'a');
15579    assert_eq!(
15580        sample_text_1,
15581        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15582    );
15583    let sample_text_2 = sample_text(rows, cols, 'l');
15584    assert_eq!(
15585        sample_text_2,
15586        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15587    );
15588    let sample_text_3 = sample_text(rows, cols, 'v');
15589    assert_eq!(
15590        sample_text_3,
15591        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15592    );
15593
15594    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15595    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15596    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15597
15598    let multi_buffer = cx.new(|cx| {
15599        let mut multibuffer = MultiBuffer::new(ReadWrite);
15600        multibuffer.push_excerpts(
15601            buffer_1.clone(),
15602            [
15603                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15604                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15605                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15606            ],
15607            cx,
15608        );
15609        multibuffer.push_excerpts(
15610            buffer_2.clone(),
15611            [
15612                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15613                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15614                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15615            ],
15616            cx,
15617        );
15618        multibuffer.push_excerpts(
15619            buffer_3.clone(),
15620            [
15621                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15622                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15623                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15624            ],
15625            cx,
15626        );
15627        multibuffer
15628    });
15629
15630    let fs = FakeFs::new(cx.executor());
15631    fs.insert_tree(
15632        "/a",
15633        json!({
15634            "main.rs": sample_text_1,
15635            "other.rs": sample_text_2,
15636            "lib.rs": sample_text_3,
15637        }),
15638    )
15639    .await;
15640    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15641    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15642    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15643    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15644        Editor::new(
15645            EditorMode::full(),
15646            multi_buffer,
15647            Some(project.clone()),
15648            window,
15649            cx,
15650        )
15651    });
15652    let multibuffer_item_id = workspace
15653        .update(cx, |workspace, window, cx| {
15654            assert!(
15655                workspace.active_item(cx).is_none(),
15656                "active item should be None before the first item is added"
15657            );
15658            workspace.add_item_to_active_pane(
15659                Box::new(multi_buffer_editor.clone()),
15660                None,
15661                true,
15662                window,
15663                cx,
15664            );
15665            let active_item = workspace
15666                .active_item(cx)
15667                .expect("should have an active item after adding the multi buffer");
15668            assert!(
15669                !active_item.is_singleton(cx),
15670                "A multi buffer was expected to active after adding"
15671            );
15672            active_item.item_id()
15673        })
15674        .unwrap();
15675    cx.executor().run_until_parked();
15676
15677    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15678        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15679            s.select_ranges(Some(1..2))
15680        });
15681        editor.open_excerpts(&OpenExcerpts, window, cx);
15682    });
15683    cx.executor().run_until_parked();
15684    let first_item_id = workspace
15685        .update(cx, |workspace, window, cx| {
15686            let active_item = workspace
15687                .active_item(cx)
15688                .expect("should have an active item after navigating into the 1st buffer");
15689            let first_item_id = active_item.item_id();
15690            assert_ne!(
15691                first_item_id, multibuffer_item_id,
15692                "Should navigate into the 1st buffer and activate it"
15693            );
15694            assert!(
15695                active_item.is_singleton(cx),
15696                "New active item should be a singleton buffer"
15697            );
15698            assert_eq!(
15699                active_item
15700                    .act_as::<Editor>(cx)
15701                    .expect("should have navigated into an editor for the 1st buffer")
15702                    .read(cx)
15703                    .text(cx),
15704                sample_text_1
15705            );
15706
15707            workspace
15708                .go_back(workspace.active_pane().downgrade(), window, cx)
15709                .detach_and_log_err(cx);
15710
15711            first_item_id
15712        })
15713        .unwrap();
15714    cx.executor().run_until_parked();
15715    workspace
15716        .update(cx, |workspace, _, cx| {
15717            let active_item = workspace
15718                .active_item(cx)
15719                .expect("should have an active item after navigating back");
15720            assert_eq!(
15721                active_item.item_id(),
15722                multibuffer_item_id,
15723                "Should navigate back to the multi buffer"
15724            );
15725            assert!(!active_item.is_singleton(cx));
15726        })
15727        .unwrap();
15728
15729    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15730        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15731            s.select_ranges(Some(39..40))
15732        });
15733        editor.open_excerpts(&OpenExcerpts, window, cx);
15734    });
15735    cx.executor().run_until_parked();
15736    let second_item_id = workspace
15737        .update(cx, |workspace, window, cx| {
15738            let active_item = workspace
15739                .active_item(cx)
15740                .expect("should have an active item after navigating into the 2nd buffer");
15741            let second_item_id = active_item.item_id();
15742            assert_ne!(
15743                second_item_id, multibuffer_item_id,
15744                "Should navigate away from the multibuffer"
15745            );
15746            assert_ne!(
15747                second_item_id, first_item_id,
15748                "Should navigate into the 2nd buffer and activate it"
15749            );
15750            assert!(
15751                active_item.is_singleton(cx),
15752                "New active item should be a singleton buffer"
15753            );
15754            assert_eq!(
15755                active_item
15756                    .act_as::<Editor>(cx)
15757                    .expect("should have navigated into an editor")
15758                    .read(cx)
15759                    .text(cx),
15760                sample_text_2
15761            );
15762
15763            workspace
15764                .go_back(workspace.active_pane().downgrade(), window, cx)
15765                .detach_and_log_err(cx);
15766
15767            second_item_id
15768        })
15769        .unwrap();
15770    cx.executor().run_until_parked();
15771    workspace
15772        .update(cx, |workspace, _, cx| {
15773            let active_item = workspace
15774                .active_item(cx)
15775                .expect("should have an active item after navigating back from the 2nd buffer");
15776            assert_eq!(
15777                active_item.item_id(),
15778                multibuffer_item_id,
15779                "Should navigate back from the 2nd buffer to the multi buffer"
15780            );
15781            assert!(!active_item.is_singleton(cx));
15782        })
15783        .unwrap();
15784
15785    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15786        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15787            s.select_ranges(Some(70..70))
15788        });
15789        editor.open_excerpts(&OpenExcerpts, window, cx);
15790    });
15791    cx.executor().run_until_parked();
15792    workspace
15793        .update(cx, |workspace, window, cx| {
15794            let active_item = workspace
15795                .active_item(cx)
15796                .expect("should have an active item after navigating into the 3rd buffer");
15797            let third_item_id = active_item.item_id();
15798            assert_ne!(
15799                third_item_id, multibuffer_item_id,
15800                "Should navigate into the 3rd buffer and activate it"
15801            );
15802            assert_ne!(third_item_id, first_item_id);
15803            assert_ne!(third_item_id, second_item_id);
15804            assert!(
15805                active_item.is_singleton(cx),
15806                "New active item should be a singleton buffer"
15807            );
15808            assert_eq!(
15809                active_item
15810                    .act_as::<Editor>(cx)
15811                    .expect("should have navigated into an editor")
15812                    .read(cx)
15813                    .text(cx),
15814                sample_text_3
15815            );
15816
15817            workspace
15818                .go_back(workspace.active_pane().downgrade(), window, cx)
15819                .detach_and_log_err(cx);
15820        })
15821        .unwrap();
15822    cx.executor().run_until_parked();
15823    workspace
15824        .update(cx, |workspace, _, cx| {
15825            let active_item = workspace
15826                .active_item(cx)
15827                .expect("should have an active item after navigating back from the 3rd buffer");
15828            assert_eq!(
15829                active_item.item_id(),
15830                multibuffer_item_id,
15831                "Should navigate back from the 3rd buffer to the multi buffer"
15832            );
15833            assert!(!active_item.is_singleton(cx));
15834        })
15835        .unwrap();
15836}
15837
15838#[gpui::test]
15839async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15840    init_test(cx, |_| {});
15841
15842    let mut cx = EditorTestContext::new(cx).await;
15843
15844    let diff_base = r#"
15845        use some::mod;
15846
15847        const A: u32 = 42;
15848
15849        fn main() {
15850            println!("hello");
15851
15852            println!("world");
15853        }
15854        "#
15855    .unindent();
15856
15857    cx.set_state(
15858        &r#"
15859        use some::modified;
15860
15861        ˇ
15862        fn main() {
15863            println!("hello there");
15864
15865            println!("around the");
15866            println!("world");
15867        }
15868        "#
15869        .unindent(),
15870    );
15871
15872    cx.set_head_text(&diff_base);
15873    executor.run_until_parked();
15874
15875    cx.update_editor(|editor, window, cx| {
15876        editor.go_to_next_hunk(&GoToHunk, window, cx);
15877        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15878    });
15879    executor.run_until_parked();
15880    cx.assert_state_with_diff(
15881        r#"
15882          use some::modified;
15883
15884
15885          fn main() {
15886        -     println!("hello");
15887        + ˇ    println!("hello there");
15888
15889              println!("around the");
15890              println!("world");
15891          }
15892        "#
15893        .unindent(),
15894    );
15895
15896    cx.update_editor(|editor, window, cx| {
15897        for _ in 0..2 {
15898            editor.go_to_next_hunk(&GoToHunk, window, cx);
15899            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15900        }
15901    });
15902    executor.run_until_parked();
15903    cx.assert_state_with_diff(
15904        r#"
15905        - use some::mod;
15906        + ˇuse some::modified;
15907
15908
15909          fn main() {
15910        -     println!("hello");
15911        +     println!("hello there");
15912
15913        +     println!("around the");
15914              println!("world");
15915          }
15916        "#
15917        .unindent(),
15918    );
15919
15920    cx.update_editor(|editor, window, cx| {
15921        editor.go_to_next_hunk(&GoToHunk, window, cx);
15922        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15923    });
15924    executor.run_until_parked();
15925    cx.assert_state_with_diff(
15926        r#"
15927        - use some::mod;
15928        + use some::modified;
15929
15930        - const A: u32 = 42;
15931          ˇ
15932          fn main() {
15933        -     println!("hello");
15934        +     println!("hello there");
15935
15936        +     println!("around the");
15937              println!("world");
15938          }
15939        "#
15940        .unindent(),
15941    );
15942
15943    cx.update_editor(|editor, window, cx| {
15944        editor.cancel(&Cancel, window, cx);
15945    });
15946
15947    cx.assert_state_with_diff(
15948        r#"
15949          use some::modified;
15950
15951          ˇ
15952          fn main() {
15953              println!("hello there");
15954
15955              println!("around the");
15956              println!("world");
15957          }
15958        "#
15959        .unindent(),
15960    );
15961}
15962
15963#[gpui::test]
15964async fn test_diff_base_change_with_expanded_diff_hunks(
15965    executor: BackgroundExecutor,
15966    cx: &mut TestAppContext,
15967) {
15968    init_test(cx, |_| {});
15969
15970    let mut cx = EditorTestContext::new(cx).await;
15971
15972    let diff_base = r#"
15973        use some::mod1;
15974        use some::mod2;
15975
15976        const A: u32 = 42;
15977        const B: u32 = 42;
15978        const C: u32 = 42;
15979
15980        fn main() {
15981            println!("hello");
15982
15983            println!("world");
15984        }
15985        "#
15986    .unindent();
15987
15988    cx.set_state(
15989        &r#"
15990        use some::mod2;
15991
15992        const A: u32 = 42;
15993        const C: u32 = 42;
15994
15995        fn main(ˇ) {
15996            //println!("hello");
15997
15998            println!("world");
15999            //
16000            //
16001        }
16002        "#
16003        .unindent(),
16004    );
16005
16006    cx.set_head_text(&diff_base);
16007    executor.run_until_parked();
16008
16009    cx.update_editor(|editor, window, cx| {
16010        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16011    });
16012    executor.run_until_parked();
16013    cx.assert_state_with_diff(
16014        r#"
16015        - use some::mod1;
16016          use some::mod2;
16017
16018          const A: u32 = 42;
16019        - const B: u32 = 42;
16020          const C: u32 = 42;
16021
16022          fn main(ˇ) {
16023        -     println!("hello");
16024        +     //println!("hello");
16025
16026              println!("world");
16027        +     //
16028        +     //
16029          }
16030        "#
16031        .unindent(),
16032    );
16033
16034    cx.set_head_text("new diff base!");
16035    executor.run_until_parked();
16036    cx.assert_state_with_diff(
16037        r#"
16038        - new diff base!
16039        + use some::mod2;
16040        +
16041        + const A: u32 = 42;
16042        + const C: u32 = 42;
16043        +
16044        + fn main(ˇ) {
16045        +     //println!("hello");
16046        +
16047        +     println!("world");
16048        +     //
16049        +     //
16050        + }
16051        "#
16052        .unindent(),
16053    );
16054}
16055
16056#[gpui::test]
16057async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16058    init_test(cx, |_| {});
16059
16060    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16061    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16062    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16063    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16064    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16065    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16066
16067    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16068    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16069    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16070
16071    let multi_buffer = cx.new(|cx| {
16072        let mut multibuffer = MultiBuffer::new(ReadWrite);
16073        multibuffer.push_excerpts(
16074            buffer_1.clone(),
16075            [
16076                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16077                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16078                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16079            ],
16080            cx,
16081        );
16082        multibuffer.push_excerpts(
16083            buffer_2.clone(),
16084            [
16085                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16086                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16087                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16088            ],
16089            cx,
16090        );
16091        multibuffer.push_excerpts(
16092            buffer_3.clone(),
16093            [
16094                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16095                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16096                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16097            ],
16098            cx,
16099        );
16100        multibuffer
16101    });
16102
16103    let editor =
16104        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16105    editor
16106        .update(cx, |editor, _window, cx| {
16107            for (buffer, diff_base) in [
16108                (buffer_1.clone(), file_1_old),
16109                (buffer_2.clone(), file_2_old),
16110                (buffer_3.clone(), file_3_old),
16111            ] {
16112                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16113                editor
16114                    .buffer
16115                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16116            }
16117        })
16118        .unwrap();
16119
16120    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16121    cx.run_until_parked();
16122
16123    cx.assert_editor_state(
16124        &"
16125            ˇaaa
16126            ccc
16127            ddd
16128
16129            ggg
16130            hhh
16131
16132
16133            lll
16134            mmm
16135            NNN
16136
16137            qqq
16138            rrr
16139
16140            uuu
16141            111
16142            222
16143            333
16144
16145            666
16146            777
16147
16148            000
16149            !!!"
16150        .unindent(),
16151    );
16152
16153    cx.update_editor(|editor, window, cx| {
16154        editor.select_all(&SelectAll, window, cx);
16155        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16156    });
16157    cx.executor().run_until_parked();
16158
16159    cx.assert_state_with_diff(
16160        "
16161            «aaa
16162          - bbb
16163            ccc
16164            ddd
16165
16166            ggg
16167            hhh
16168
16169
16170            lll
16171            mmm
16172          - nnn
16173          + NNN
16174
16175            qqq
16176            rrr
16177
16178            uuu
16179            111
16180            222
16181            333
16182
16183          + 666
16184            777
16185
16186            000
16187            !!!ˇ»"
16188            .unindent(),
16189    );
16190}
16191
16192#[gpui::test]
16193async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16194    init_test(cx, |_| {});
16195
16196    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16197    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16198
16199    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16200    let multi_buffer = cx.new(|cx| {
16201        let mut multibuffer = MultiBuffer::new(ReadWrite);
16202        multibuffer.push_excerpts(
16203            buffer.clone(),
16204            [
16205                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16206                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16207                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16208            ],
16209            cx,
16210        );
16211        multibuffer
16212    });
16213
16214    let editor =
16215        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16216    editor
16217        .update(cx, |editor, _window, cx| {
16218            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16219            editor
16220                .buffer
16221                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16222        })
16223        .unwrap();
16224
16225    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16226    cx.run_until_parked();
16227
16228    cx.update_editor(|editor, window, cx| {
16229        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16230    });
16231    cx.executor().run_until_parked();
16232
16233    // When the start of a hunk coincides with the start of its excerpt,
16234    // the hunk is expanded. When the start of a a hunk is earlier than
16235    // the start of its excerpt, the hunk is not expanded.
16236    cx.assert_state_with_diff(
16237        "
16238            ˇaaa
16239          - bbb
16240          + BBB
16241
16242          - ddd
16243          - eee
16244          + DDD
16245          + EEE
16246            fff
16247
16248            iii
16249        "
16250        .unindent(),
16251    );
16252}
16253
16254#[gpui::test]
16255async fn test_edits_around_expanded_insertion_hunks(
16256    executor: BackgroundExecutor,
16257    cx: &mut TestAppContext,
16258) {
16259    init_test(cx, |_| {});
16260
16261    let mut cx = EditorTestContext::new(cx).await;
16262
16263    let diff_base = r#"
16264        use some::mod1;
16265        use some::mod2;
16266
16267        const A: u32 = 42;
16268
16269        fn main() {
16270            println!("hello");
16271
16272            println!("world");
16273        }
16274        "#
16275    .unindent();
16276    executor.run_until_parked();
16277    cx.set_state(
16278        &r#"
16279        use some::mod1;
16280        use some::mod2;
16281
16282        const A: u32 = 42;
16283        const B: u32 = 42;
16284        const C: u32 = 42;
16285        ˇ
16286
16287        fn main() {
16288            println!("hello");
16289
16290            println!("world");
16291        }
16292        "#
16293        .unindent(),
16294    );
16295
16296    cx.set_head_text(&diff_base);
16297    executor.run_until_parked();
16298
16299    cx.update_editor(|editor, window, cx| {
16300        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16301    });
16302    executor.run_until_parked();
16303
16304    cx.assert_state_with_diff(
16305        r#"
16306        use some::mod1;
16307        use some::mod2;
16308
16309        const A: u32 = 42;
16310      + const B: u32 = 42;
16311      + const C: u32 = 42;
16312      + ˇ
16313
16314        fn main() {
16315            println!("hello");
16316
16317            println!("world");
16318        }
16319      "#
16320        .unindent(),
16321    );
16322
16323    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16324    executor.run_until_parked();
16325
16326    cx.assert_state_with_diff(
16327        r#"
16328        use some::mod1;
16329        use some::mod2;
16330
16331        const A: u32 = 42;
16332      + const B: u32 = 42;
16333      + const C: u32 = 42;
16334      + const D: u32 = 42;
16335      + ˇ
16336
16337        fn main() {
16338            println!("hello");
16339
16340            println!("world");
16341        }
16342      "#
16343        .unindent(),
16344    );
16345
16346    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16347    executor.run_until_parked();
16348
16349    cx.assert_state_with_diff(
16350        r#"
16351        use some::mod1;
16352        use some::mod2;
16353
16354        const A: u32 = 42;
16355      + const B: u32 = 42;
16356      + const C: u32 = 42;
16357      + const D: u32 = 42;
16358      + const E: u32 = 42;
16359      + ˇ
16360
16361        fn main() {
16362            println!("hello");
16363
16364            println!("world");
16365        }
16366      "#
16367        .unindent(),
16368    );
16369
16370    cx.update_editor(|editor, window, cx| {
16371        editor.delete_line(&DeleteLine, window, cx);
16372    });
16373    executor.run_until_parked();
16374
16375    cx.assert_state_with_diff(
16376        r#"
16377        use some::mod1;
16378        use some::mod2;
16379
16380        const A: u32 = 42;
16381      + const B: u32 = 42;
16382      + const C: u32 = 42;
16383      + const D: u32 = 42;
16384      + const E: u32 = 42;
16385        ˇ
16386        fn main() {
16387            println!("hello");
16388
16389            println!("world");
16390        }
16391      "#
16392        .unindent(),
16393    );
16394
16395    cx.update_editor(|editor, window, cx| {
16396        editor.move_up(&MoveUp, window, cx);
16397        editor.delete_line(&DeleteLine, window, cx);
16398        editor.move_up(&MoveUp, window, cx);
16399        editor.delete_line(&DeleteLine, window, cx);
16400        editor.move_up(&MoveUp, window, cx);
16401        editor.delete_line(&DeleteLine, window, cx);
16402    });
16403    executor.run_until_parked();
16404    cx.assert_state_with_diff(
16405        r#"
16406        use some::mod1;
16407        use some::mod2;
16408
16409        const A: u32 = 42;
16410      + const B: u32 = 42;
16411        ˇ
16412        fn main() {
16413            println!("hello");
16414
16415            println!("world");
16416        }
16417      "#
16418        .unindent(),
16419    );
16420
16421    cx.update_editor(|editor, window, cx| {
16422        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16423        editor.delete_line(&DeleteLine, window, cx);
16424    });
16425    executor.run_until_parked();
16426    cx.assert_state_with_diff(
16427        r#"
16428        ˇ
16429        fn main() {
16430            println!("hello");
16431
16432            println!("world");
16433        }
16434      "#
16435        .unindent(),
16436    );
16437}
16438
16439#[gpui::test]
16440async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16441    init_test(cx, |_| {});
16442
16443    let mut cx = EditorTestContext::new(cx).await;
16444    cx.set_head_text(indoc! { "
16445        one
16446        two
16447        three
16448        four
16449        five
16450        "
16451    });
16452    cx.set_state(indoc! { "
16453        one
16454        ˇthree
16455        five
16456    "});
16457    cx.run_until_parked();
16458    cx.update_editor(|editor, window, cx| {
16459        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16460    });
16461    cx.assert_state_with_diff(
16462        indoc! { "
16463        one
16464      - two
16465        ˇthree
16466      - four
16467        five
16468    "}
16469        .to_string(),
16470    );
16471    cx.update_editor(|editor, window, cx| {
16472        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16473    });
16474
16475    cx.assert_state_with_diff(
16476        indoc! { "
16477        one
16478        ˇthree
16479        five
16480    "}
16481        .to_string(),
16482    );
16483
16484    cx.set_state(indoc! { "
16485        one
16486        ˇTWO
16487        three
16488        four
16489        five
16490    "});
16491    cx.run_until_parked();
16492    cx.update_editor(|editor, window, cx| {
16493        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16494    });
16495
16496    cx.assert_state_with_diff(
16497        indoc! { "
16498            one
16499          - two
16500          + ˇTWO
16501            three
16502            four
16503            five
16504        "}
16505        .to_string(),
16506    );
16507    cx.update_editor(|editor, window, cx| {
16508        editor.move_up(&Default::default(), window, cx);
16509        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16510    });
16511    cx.assert_state_with_diff(
16512        indoc! { "
16513            one
16514            ˇTWO
16515            three
16516            four
16517            five
16518        "}
16519        .to_string(),
16520    );
16521}
16522
16523#[gpui::test]
16524async fn test_edits_around_expanded_deletion_hunks(
16525    executor: BackgroundExecutor,
16526    cx: &mut TestAppContext,
16527) {
16528    init_test(cx, |_| {});
16529
16530    let mut cx = EditorTestContext::new(cx).await;
16531
16532    let diff_base = r#"
16533        use some::mod1;
16534        use some::mod2;
16535
16536        const A: u32 = 42;
16537        const B: u32 = 42;
16538        const C: u32 = 42;
16539
16540
16541        fn main() {
16542            println!("hello");
16543
16544            println!("world");
16545        }
16546    "#
16547    .unindent();
16548    executor.run_until_parked();
16549    cx.set_state(
16550        &r#"
16551        use some::mod1;
16552        use some::mod2;
16553
16554        ˇconst B: u32 = 42;
16555        const C: u32 = 42;
16556
16557
16558        fn main() {
16559            println!("hello");
16560
16561            println!("world");
16562        }
16563        "#
16564        .unindent(),
16565    );
16566
16567    cx.set_head_text(&diff_base);
16568    executor.run_until_parked();
16569
16570    cx.update_editor(|editor, window, cx| {
16571        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16572    });
16573    executor.run_until_parked();
16574
16575    cx.assert_state_with_diff(
16576        r#"
16577        use some::mod1;
16578        use some::mod2;
16579
16580      - const A: u32 = 42;
16581        ˇconst B: u32 = 42;
16582        const C: u32 = 42;
16583
16584
16585        fn main() {
16586            println!("hello");
16587
16588            println!("world");
16589        }
16590      "#
16591        .unindent(),
16592    );
16593
16594    cx.update_editor(|editor, window, cx| {
16595        editor.delete_line(&DeleteLine, window, cx);
16596    });
16597    executor.run_until_parked();
16598    cx.assert_state_with_diff(
16599        r#"
16600        use some::mod1;
16601        use some::mod2;
16602
16603      - const A: u32 = 42;
16604      - const B: u32 = 42;
16605        ˇconst C: u32 = 42;
16606
16607
16608        fn main() {
16609            println!("hello");
16610
16611            println!("world");
16612        }
16613      "#
16614        .unindent(),
16615    );
16616
16617    cx.update_editor(|editor, window, cx| {
16618        editor.delete_line(&DeleteLine, window, cx);
16619    });
16620    executor.run_until_parked();
16621    cx.assert_state_with_diff(
16622        r#"
16623        use some::mod1;
16624        use some::mod2;
16625
16626      - const A: u32 = 42;
16627      - const B: u32 = 42;
16628      - const C: u32 = 42;
16629        ˇ
16630
16631        fn main() {
16632            println!("hello");
16633
16634            println!("world");
16635        }
16636      "#
16637        .unindent(),
16638    );
16639
16640    cx.update_editor(|editor, window, cx| {
16641        editor.handle_input("replacement", window, cx);
16642    });
16643    executor.run_until_parked();
16644    cx.assert_state_with_diff(
16645        r#"
16646        use some::mod1;
16647        use some::mod2;
16648
16649      - const A: u32 = 42;
16650      - const B: u32 = 42;
16651      - const C: u32 = 42;
16652      -
16653      + replacementˇ
16654
16655        fn main() {
16656            println!("hello");
16657
16658            println!("world");
16659        }
16660      "#
16661        .unindent(),
16662    );
16663}
16664
16665#[gpui::test]
16666async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16667    init_test(cx, |_| {});
16668
16669    let mut cx = EditorTestContext::new(cx).await;
16670
16671    let base_text = r#"
16672        one
16673        two
16674        three
16675        four
16676        five
16677    "#
16678    .unindent();
16679    executor.run_until_parked();
16680    cx.set_state(
16681        &r#"
16682        one
16683        two
16684        fˇour
16685        five
16686        "#
16687        .unindent(),
16688    );
16689
16690    cx.set_head_text(&base_text);
16691    executor.run_until_parked();
16692
16693    cx.update_editor(|editor, window, cx| {
16694        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16695    });
16696    executor.run_until_parked();
16697
16698    cx.assert_state_with_diff(
16699        r#"
16700          one
16701          two
16702        - three
16703          fˇour
16704          five
16705        "#
16706        .unindent(),
16707    );
16708
16709    cx.update_editor(|editor, window, cx| {
16710        editor.backspace(&Backspace, window, cx);
16711        editor.backspace(&Backspace, window, cx);
16712    });
16713    executor.run_until_parked();
16714    cx.assert_state_with_diff(
16715        r#"
16716          one
16717          two
16718        - threeˇ
16719        - four
16720        + our
16721          five
16722        "#
16723        .unindent(),
16724    );
16725}
16726
16727#[gpui::test]
16728async fn test_edit_after_expanded_modification_hunk(
16729    executor: BackgroundExecutor,
16730    cx: &mut TestAppContext,
16731) {
16732    init_test(cx, |_| {});
16733
16734    let mut cx = EditorTestContext::new(cx).await;
16735
16736    let diff_base = r#"
16737        use some::mod1;
16738        use some::mod2;
16739
16740        const A: u32 = 42;
16741        const B: u32 = 42;
16742        const C: u32 = 42;
16743        const D: u32 = 42;
16744
16745
16746        fn main() {
16747            println!("hello");
16748
16749            println!("world");
16750        }"#
16751    .unindent();
16752
16753    cx.set_state(
16754        &r#"
16755        use some::mod1;
16756        use some::mod2;
16757
16758        const A: u32 = 42;
16759        const B: u32 = 42;
16760        const C: u32 = 43ˇ
16761        const D: u32 = 42;
16762
16763
16764        fn main() {
16765            println!("hello");
16766
16767            println!("world");
16768        }"#
16769        .unindent(),
16770    );
16771
16772    cx.set_head_text(&diff_base);
16773    executor.run_until_parked();
16774    cx.update_editor(|editor, window, cx| {
16775        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16776    });
16777    executor.run_until_parked();
16778
16779    cx.assert_state_with_diff(
16780        r#"
16781        use some::mod1;
16782        use some::mod2;
16783
16784        const A: u32 = 42;
16785        const B: u32 = 42;
16786      - const C: u32 = 42;
16787      + const C: u32 = 43ˇ
16788        const D: u32 = 42;
16789
16790
16791        fn main() {
16792            println!("hello");
16793
16794            println!("world");
16795        }"#
16796        .unindent(),
16797    );
16798
16799    cx.update_editor(|editor, window, cx| {
16800        editor.handle_input("\nnew_line\n", window, cx);
16801    });
16802    executor.run_until_parked();
16803
16804    cx.assert_state_with_diff(
16805        r#"
16806        use some::mod1;
16807        use some::mod2;
16808
16809        const A: u32 = 42;
16810        const B: u32 = 42;
16811      - const C: u32 = 42;
16812      + const C: u32 = 43
16813      + new_line
16814      + ˇ
16815        const D: u32 = 42;
16816
16817
16818        fn main() {
16819            println!("hello");
16820
16821            println!("world");
16822        }"#
16823        .unindent(),
16824    );
16825}
16826
16827#[gpui::test]
16828async fn test_stage_and_unstage_added_file_hunk(
16829    executor: BackgroundExecutor,
16830    cx: &mut TestAppContext,
16831) {
16832    init_test(cx, |_| {});
16833
16834    let mut cx = EditorTestContext::new(cx).await;
16835    cx.update_editor(|editor, _, cx| {
16836        editor.set_expand_all_diff_hunks(cx);
16837    });
16838
16839    let working_copy = r#"
16840            ˇfn main() {
16841                println!("hello, world!");
16842            }
16843        "#
16844    .unindent();
16845
16846    cx.set_state(&working_copy);
16847    executor.run_until_parked();
16848
16849    cx.assert_state_with_diff(
16850        r#"
16851            + ˇfn main() {
16852            +     println!("hello, world!");
16853            + }
16854        "#
16855        .unindent(),
16856    );
16857    cx.assert_index_text(None);
16858
16859    cx.update_editor(|editor, window, cx| {
16860        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16861    });
16862    executor.run_until_parked();
16863    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16864    cx.assert_state_with_diff(
16865        r#"
16866            + ˇfn main() {
16867            +     println!("hello, world!");
16868            + }
16869        "#
16870        .unindent(),
16871    );
16872
16873    cx.update_editor(|editor, window, cx| {
16874        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16875    });
16876    executor.run_until_parked();
16877    cx.assert_index_text(None);
16878}
16879
16880async fn setup_indent_guides_editor(
16881    text: &str,
16882    cx: &mut TestAppContext,
16883) -> (BufferId, EditorTestContext) {
16884    init_test(cx, |_| {});
16885
16886    let mut cx = EditorTestContext::new(cx).await;
16887
16888    let buffer_id = cx.update_editor(|editor, window, cx| {
16889        editor.set_text(text, window, cx);
16890        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16891
16892        buffer_ids[0]
16893    });
16894
16895    (buffer_id, cx)
16896}
16897
16898fn assert_indent_guides(
16899    range: Range<u32>,
16900    expected: Vec<IndentGuide>,
16901    active_indices: Option<Vec<usize>>,
16902    cx: &mut EditorTestContext,
16903) {
16904    let indent_guides = cx.update_editor(|editor, window, cx| {
16905        let snapshot = editor.snapshot(window, cx).display_snapshot;
16906        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16907            editor,
16908            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16909            true,
16910            &snapshot,
16911            cx,
16912        );
16913
16914        indent_guides.sort_by(|a, b| {
16915            a.depth.cmp(&b.depth).then(
16916                a.start_row
16917                    .cmp(&b.start_row)
16918                    .then(a.end_row.cmp(&b.end_row)),
16919            )
16920        });
16921        indent_guides
16922    });
16923
16924    if let Some(expected) = active_indices {
16925        let active_indices = cx.update_editor(|editor, window, cx| {
16926            let snapshot = editor.snapshot(window, cx).display_snapshot;
16927            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16928        });
16929
16930        assert_eq!(
16931            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16932            expected,
16933            "Active indent guide indices do not match"
16934        );
16935    }
16936
16937    assert_eq!(indent_guides, expected, "Indent guides do not match");
16938}
16939
16940fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16941    IndentGuide {
16942        buffer_id,
16943        start_row: MultiBufferRow(start_row),
16944        end_row: MultiBufferRow(end_row),
16945        depth,
16946        tab_size: 4,
16947        settings: IndentGuideSettings {
16948            enabled: true,
16949            line_width: 1,
16950            active_line_width: 1,
16951            ..Default::default()
16952        },
16953    }
16954}
16955
16956#[gpui::test]
16957async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16958    let (buffer_id, mut cx) = setup_indent_guides_editor(
16959        &"
16960        fn main() {
16961            let a = 1;
16962        }"
16963        .unindent(),
16964        cx,
16965    )
16966    .await;
16967
16968    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16969}
16970
16971#[gpui::test]
16972async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16973    let (buffer_id, mut cx) = setup_indent_guides_editor(
16974        &"
16975        fn main() {
16976            let a = 1;
16977            let b = 2;
16978        }"
16979        .unindent(),
16980        cx,
16981    )
16982    .await;
16983
16984    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16985}
16986
16987#[gpui::test]
16988async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16989    let (buffer_id, mut cx) = setup_indent_guides_editor(
16990        &"
16991        fn main() {
16992            let a = 1;
16993            if a == 3 {
16994                let b = 2;
16995            } else {
16996                let c = 3;
16997            }
16998        }"
16999        .unindent(),
17000        cx,
17001    )
17002    .await;
17003
17004    assert_indent_guides(
17005        0..8,
17006        vec![
17007            indent_guide(buffer_id, 1, 6, 0),
17008            indent_guide(buffer_id, 3, 3, 1),
17009            indent_guide(buffer_id, 5, 5, 1),
17010        ],
17011        None,
17012        &mut cx,
17013    );
17014}
17015
17016#[gpui::test]
17017async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17018    let (buffer_id, mut cx) = setup_indent_guides_editor(
17019        &"
17020        fn main() {
17021            let a = 1;
17022                let b = 2;
17023            let c = 3;
17024        }"
17025        .unindent(),
17026        cx,
17027    )
17028    .await;
17029
17030    assert_indent_guides(
17031        0..5,
17032        vec![
17033            indent_guide(buffer_id, 1, 3, 0),
17034            indent_guide(buffer_id, 2, 2, 1),
17035        ],
17036        None,
17037        &mut cx,
17038    );
17039}
17040
17041#[gpui::test]
17042async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17043    let (buffer_id, mut cx) = setup_indent_guides_editor(
17044        &"
17045        fn main() {
17046            let a = 1;
17047
17048            let c = 3;
17049        }"
17050        .unindent(),
17051        cx,
17052    )
17053    .await;
17054
17055    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17056}
17057
17058#[gpui::test]
17059async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17060    let (buffer_id, mut cx) = setup_indent_guides_editor(
17061        &"
17062        fn main() {
17063            let a = 1;
17064
17065            let c = 3;
17066
17067            if a == 3 {
17068                let b = 2;
17069            } else {
17070                let c = 3;
17071            }
17072        }"
17073        .unindent(),
17074        cx,
17075    )
17076    .await;
17077
17078    assert_indent_guides(
17079        0..11,
17080        vec![
17081            indent_guide(buffer_id, 1, 9, 0),
17082            indent_guide(buffer_id, 6, 6, 1),
17083            indent_guide(buffer_id, 8, 8, 1),
17084        ],
17085        None,
17086        &mut cx,
17087    );
17088}
17089
17090#[gpui::test]
17091async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17092    let (buffer_id, mut cx) = setup_indent_guides_editor(
17093        &"
17094        fn main() {
17095            let a = 1;
17096
17097            let c = 3;
17098
17099            if a == 3 {
17100                let b = 2;
17101            } else {
17102                let c = 3;
17103            }
17104        }"
17105        .unindent(),
17106        cx,
17107    )
17108    .await;
17109
17110    assert_indent_guides(
17111        1..11,
17112        vec![
17113            indent_guide(buffer_id, 1, 9, 0),
17114            indent_guide(buffer_id, 6, 6, 1),
17115            indent_guide(buffer_id, 8, 8, 1),
17116        ],
17117        None,
17118        &mut cx,
17119    );
17120}
17121
17122#[gpui::test]
17123async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17124    let (buffer_id, mut cx) = setup_indent_guides_editor(
17125        &"
17126        fn main() {
17127            let a = 1;
17128
17129            let c = 3;
17130
17131            if a == 3 {
17132                let b = 2;
17133            } else {
17134                let c = 3;
17135            }
17136        }"
17137        .unindent(),
17138        cx,
17139    )
17140    .await;
17141
17142    assert_indent_guides(
17143        1..10,
17144        vec![
17145            indent_guide(buffer_id, 1, 9, 0),
17146            indent_guide(buffer_id, 6, 6, 1),
17147            indent_guide(buffer_id, 8, 8, 1),
17148        ],
17149        None,
17150        &mut cx,
17151    );
17152}
17153
17154#[gpui::test]
17155async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17156    let (buffer_id, mut cx) = setup_indent_guides_editor(
17157        &"
17158        fn main() {
17159            if a {
17160                b(
17161                    c,
17162                    d,
17163                )
17164            } else {
17165                e(
17166                    f
17167                )
17168            }
17169        }"
17170        .unindent(),
17171        cx,
17172    )
17173    .await;
17174
17175    assert_indent_guides(
17176        0..11,
17177        vec![
17178            indent_guide(buffer_id, 1, 10, 0),
17179            indent_guide(buffer_id, 2, 5, 1),
17180            indent_guide(buffer_id, 7, 9, 1),
17181            indent_guide(buffer_id, 3, 4, 2),
17182            indent_guide(buffer_id, 8, 8, 2),
17183        ],
17184        None,
17185        &mut cx,
17186    );
17187
17188    cx.update_editor(|editor, window, cx| {
17189        editor.fold_at(MultiBufferRow(2), window, cx);
17190        assert_eq!(
17191            editor.display_text(cx),
17192            "
17193            fn main() {
17194                if a {
17195                    b(⋯
17196                    )
17197                } else {
17198                    e(
17199                        f
17200                    )
17201                }
17202            }"
17203            .unindent()
17204        );
17205    });
17206
17207    assert_indent_guides(
17208        0..11,
17209        vec![
17210            indent_guide(buffer_id, 1, 10, 0),
17211            indent_guide(buffer_id, 2, 5, 1),
17212            indent_guide(buffer_id, 7, 9, 1),
17213            indent_guide(buffer_id, 8, 8, 2),
17214        ],
17215        None,
17216        &mut cx,
17217    );
17218}
17219
17220#[gpui::test]
17221async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17222    let (buffer_id, mut cx) = setup_indent_guides_editor(
17223        &"
17224        block1
17225            block2
17226                block3
17227                    block4
17228            block2
17229        block1
17230        block1"
17231            .unindent(),
17232        cx,
17233    )
17234    .await;
17235
17236    assert_indent_guides(
17237        1..10,
17238        vec![
17239            indent_guide(buffer_id, 1, 4, 0),
17240            indent_guide(buffer_id, 2, 3, 1),
17241            indent_guide(buffer_id, 3, 3, 2),
17242        ],
17243        None,
17244        &mut cx,
17245    );
17246}
17247
17248#[gpui::test]
17249async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17250    let (buffer_id, mut cx) = setup_indent_guides_editor(
17251        &"
17252        block1
17253            block2
17254                block3
17255
17256        block1
17257        block1"
17258            .unindent(),
17259        cx,
17260    )
17261    .await;
17262
17263    assert_indent_guides(
17264        0..6,
17265        vec![
17266            indent_guide(buffer_id, 1, 2, 0),
17267            indent_guide(buffer_id, 2, 2, 1),
17268        ],
17269        None,
17270        &mut cx,
17271    );
17272}
17273
17274#[gpui::test]
17275async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17276    let (buffer_id, mut cx) = setup_indent_guides_editor(
17277        &"
17278        function component() {
17279        \treturn (
17280        \t\t\t
17281        \t\t<div>
17282        \t\t\t<abc></abc>
17283        \t\t</div>
17284        \t)
17285        }"
17286        .unindent(),
17287        cx,
17288    )
17289    .await;
17290
17291    assert_indent_guides(
17292        0..8,
17293        vec![
17294            indent_guide(buffer_id, 1, 6, 0),
17295            indent_guide(buffer_id, 2, 5, 1),
17296            indent_guide(buffer_id, 4, 4, 2),
17297        ],
17298        None,
17299        &mut cx,
17300    );
17301}
17302
17303#[gpui::test]
17304async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17305    let (buffer_id, mut cx) = setup_indent_guides_editor(
17306        &"
17307        function component() {
17308        \treturn (
17309        \t
17310        \t\t<div>
17311        \t\t\t<abc></abc>
17312        \t\t</div>
17313        \t)
17314        }"
17315        .unindent(),
17316        cx,
17317    )
17318    .await;
17319
17320    assert_indent_guides(
17321        0..8,
17322        vec![
17323            indent_guide(buffer_id, 1, 6, 0),
17324            indent_guide(buffer_id, 2, 5, 1),
17325            indent_guide(buffer_id, 4, 4, 2),
17326        ],
17327        None,
17328        &mut cx,
17329    );
17330}
17331
17332#[gpui::test]
17333async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17334    let (buffer_id, mut cx) = setup_indent_guides_editor(
17335        &"
17336        block1
17337
17338
17339
17340            block2
17341        "
17342        .unindent(),
17343        cx,
17344    )
17345    .await;
17346
17347    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17348}
17349
17350#[gpui::test]
17351async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17352    let (buffer_id, mut cx) = setup_indent_guides_editor(
17353        &"
17354        def a:
17355        \tb = 3
17356        \tif True:
17357        \t\tc = 4
17358        \t\td = 5
17359        \tprint(b)
17360        "
17361        .unindent(),
17362        cx,
17363    )
17364    .await;
17365
17366    assert_indent_guides(
17367        0..6,
17368        vec![
17369            indent_guide(buffer_id, 1, 5, 0),
17370            indent_guide(buffer_id, 3, 4, 1),
17371        ],
17372        None,
17373        &mut cx,
17374    );
17375}
17376
17377#[gpui::test]
17378async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17379    let (buffer_id, mut cx) = setup_indent_guides_editor(
17380        &"
17381    fn main() {
17382        let a = 1;
17383    }"
17384        .unindent(),
17385        cx,
17386    )
17387    .await;
17388
17389    cx.update_editor(|editor, window, cx| {
17390        editor.change_selections(None, window, cx, |s| {
17391            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17392        });
17393    });
17394
17395    assert_indent_guides(
17396        0..3,
17397        vec![indent_guide(buffer_id, 1, 1, 0)],
17398        Some(vec![0]),
17399        &mut cx,
17400    );
17401}
17402
17403#[gpui::test]
17404async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17405    let (buffer_id, mut cx) = setup_indent_guides_editor(
17406        &"
17407    fn main() {
17408        if 1 == 2 {
17409            let a = 1;
17410        }
17411    }"
17412        .unindent(),
17413        cx,
17414    )
17415    .await;
17416
17417    cx.update_editor(|editor, window, cx| {
17418        editor.change_selections(None, window, cx, |s| {
17419            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17420        });
17421    });
17422
17423    assert_indent_guides(
17424        0..4,
17425        vec![
17426            indent_guide(buffer_id, 1, 3, 0),
17427            indent_guide(buffer_id, 2, 2, 1),
17428        ],
17429        Some(vec![1]),
17430        &mut cx,
17431    );
17432
17433    cx.update_editor(|editor, window, cx| {
17434        editor.change_selections(None, window, cx, |s| {
17435            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17436        });
17437    });
17438
17439    assert_indent_guides(
17440        0..4,
17441        vec![
17442            indent_guide(buffer_id, 1, 3, 0),
17443            indent_guide(buffer_id, 2, 2, 1),
17444        ],
17445        Some(vec![1]),
17446        &mut cx,
17447    );
17448
17449    cx.update_editor(|editor, window, cx| {
17450        editor.change_selections(None, window, cx, |s| {
17451            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17452        });
17453    });
17454
17455    assert_indent_guides(
17456        0..4,
17457        vec![
17458            indent_guide(buffer_id, 1, 3, 0),
17459            indent_guide(buffer_id, 2, 2, 1),
17460        ],
17461        Some(vec![0]),
17462        &mut cx,
17463    );
17464}
17465
17466#[gpui::test]
17467async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17468    let (buffer_id, mut cx) = setup_indent_guides_editor(
17469        &"
17470    fn main() {
17471        let a = 1;
17472
17473        let b = 2;
17474    }"
17475        .unindent(),
17476        cx,
17477    )
17478    .await;
17479
17480    cx.update_editor(|editor, window, cx| {
17481        editor.change_selections(None, window, cx, |s| {
17482            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17483        });
17484    });
17485
17486    assert_indent_guides(
17487        0..5,
17488        vec![indent_guide(buffer_id, 1, 3, 0)],
17489        Some(vec![0]),
17490        &mut cx,
17491    );
17492}
17493
17494#[gpui::test]
17495async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17496    let (buffer_id, mut cx) = setup_indent_guides_editor(
17497        &"
17498    def m:
17499        a = 1
17500        pass"
17501            .unindent(),
17502        cx,
17503    )
17504    .await;
17505
17506    cx.update_editor(|editor, window, cx| {
17507        editor.change_selections(None, window, cx, |s| {
17508            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17509        });
17510    });
17511
17512    assert_indent_guides(
17513        0..3,
17514        vec![indent_guide(buffer_id, 1, 2, 0)],
17515        Some(vec![0]),
17516        &mut cx,
17517    );
17518}
17519
17520#[gpui::test]
17521async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17522    init_test(cx, |_| {});
17523    let mut cx = EditorTestContext::new(cx).await;
17524    let text = indoc! {
17525        "
17526        impl A {
17527            fn b() {
17528                0;
17529                3;
17530                5;
17531                6;
17532                7;
17533            }
17534        }
17535        "
17536    };
17537    let base_text = indoc! {
17538        "
17539        impl A {
17540            fn b() {
17541                0;
17542                1;
17543                2;
17544                3;
17545                4;
17546            }
17547            fn c() {
17548                5;
17549                6;
17550                7;
17551            }
17552        }
17553        "
17554    };
17555
17556    cx.update_editor(|editor, window, cx| {
17557        editor.set_text(text, window, cx);
17558
17559        editor.buffer().update(cx, |multibuffer, cx| {
17560            let buffer = multibuffer.as_singleton().unwrap();
17561            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17562
17563            multibuffer.set_all_diff_hunks_expanded(cx);
17564            multibuffer.add_diff(diff, cx);
17565
17566            buffer.read(cx).remote_id()
17567        })
17568    });
17569    cx.run_until_parked();
17570
17571    cx.assert_state_with_diff(
17572        indoc! { "
17573          impl A {
17574              fn b() {
17575                  0;
17576        -         1;
17577        -         2;
17578                  3;
17579        -         4;
17580        -     }
17581        -     fn c() {
17582                  5;
17583                  6;
17584                  7;
17585              }
17586          }
17587          ˇ"
17588        }
17589        .to_string(),
17590    );
17591
17592    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17593        editor
17594            .snapshot(window, cx)
17595            .buffer_snapshot
17596            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17597            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17598            .collect::<Vec<_>>()
17599    });
17600    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17601    assert_eq!(
17602        actual_guides,
17603        vec![
17604            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17605            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17606            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17607        ]
17608    );
17609}
17610
17611#[gpui::test]
17612async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17613    init_test(cx, |_| {});
17614    let mut cx = EditorTestContext::new(cx).await;
17615
17616    let diff_base = r#"
17617        a
17618        b
17619        c
17620        "#
17621    .unindent();
17622
17623    cx.set_state(
17624        &r#"
17625        ˇA
17626        b
17627        C
17628        "#
17629        .unindent(),
17630    );
17631    cx.set_head_text(&diff_base);
17632    cx.update_editor(|editor, window, cx| {
17633        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17634    });
17635    executor.run_until_parked();
17636
17637    let both_hunks_expanded = r#"
17638        - a
17639        + ˇA
17640          b
17641        - c
17642        + C
17643        "#
17644    .unindent();
17645
17646    cx.assert_state_with_diff(both_hunks_expanded.clone());
17647
17648    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17649        let snapshot = editor.snapshot(window, cx);
17650        let hunks = editor
17651            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17652            .collect::<Vec<_>>();
17653        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17654        let buffer_id = hunks[0].buffer_id;
17655        hunks
17656            .into_iter()
17657            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17658            .collect::<Vec<_>>()
17659    });
17660    assert_eq!(hunk_ranges.len(), 2);
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    let second_hunk_expanded = r#"
17668          ˇA
17669          b
17670        - c
17671        + C
17672        "#
17673    .unindent();
17674
17675    cx.assert_state_with_diff(second_hunk_expanded);
17676
17677    cx.update_editor(|editor, _, cx| {
17678        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17679    });
17680    executor.run_until_parked();
17681
17682    cx.assert_state_with_diff(both_hunks_expanded.clone());
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    let first_hunk_expanded = r#"
17690        - a
17691        + ˇA
17692          b
17693          C
17694        "#
17695    .unindent();
17696
17697    cx.assert_state_with_diff(first_hunk_expanded);
17698
17699    cx.update_editor(|editor, _, cx| {
17700        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17701    });
17702    executor.run_until_parked();
17703
17704    cx.assert_state_with_diff(both_hunks_expanded);
17705
17706    cx.set_state(
17707        &r#"
17708        ˇA
17709        b
17710        "#
17711        .unindent(),
17712    );
17713    cx.run_until_parked();
17714
17715    // TODO this cursor position seems bad
17716    cx.assert_state_with_diff(
17717        r#"
17718        - ˇa
17719        + A
17720          b
17721        "#
17722        .unindent(),
17723    );
17724
17725    cx.update_editor(|editor, window, cx| {
17726        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17727    });
17728
17729    cx.assert_state_with_diff(
17730        r#"
17731            - ˇa
17732            + A
17733              b
17734            - c
17735            "#
17736        .unindent(),
17737    );
17738
17739    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17740        let snapshot = editor.snapshot(window, cx);
17741        let hunks = editor
17742            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17743            .collect::<Vec<_>>();
17744        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17745        let buffer_id = hunks[0].buffer_id;
17746        hunks
17747            .into_iter()
17748            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17749            .collect::<Vec<_>>()
17750    });
17751    assert_eq!(hunk_ranges.len(), 2);
17752
17753    cx.update_editor(|editor, _, cx| {
17754        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17755    });
17756    executor.run_until_parked();
17757
17758    cx.assert_state_with_diff(
17759        r#"
17760        - ˇa
17761        + A
17762          b
17763        "#
17764        .unindent(),
17765    );
17766}
17767
17768#[gpui::test]
17769async fn test_toggle_deletion_hunk_at_start_of_file(
17770    executor: BackgroundExecutor,
17771    cx: &mut TestAppContext,
17772) {
17773    init_test(cx, |_| {});
17774    let mut cx = EditorTestContext::new(cx).await;
17775
17776    let diff_base = r#"
17777        a
17778        b
17779        c
17780        "#
17781    .unindent();
17782
17783    cx.set_state(
17784        &r#"
17785        ˇb
17786        c
17787        "#
17788        .unindent(),
17789    );
17790    cx.set_head_text(&diff_base);
17791    cx.update_editor(|editor, window, cx| {
17792        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17793    });
17794    executor.run_until_parked();
17795
17796    let hunk_expanded = r#"
17797        - a
17798          ˇb
17799          c
17800        "#
17801    .unindent();
17802
17803    cx.assert_state_with_diff(hunk_expanded.clone());
17804
17805    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17806        let snapshot = editor.snapshot(window, cx);
17807        let hunks = editor
17808            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17809            .collect::<Vec<_>>();
17810        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17811        let buffer_id = hunks[0].buffer_id;
17812        hunks
17813            .into_iter()
17814            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17815            .collect::<Vec<_>>()
17816    });
17817    assert_eq!(hunk_ranges.len(), 1);
17818
17819    cx.update_editor(|editor, _, cx| {
17820        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17821    });
17822    executor.run_until_parked();
17823
17824    let hunk_collapsed = r#"
17825          ˇb
17826          c
17827        "#
17828    .unindent();
17829
17830    cx.assert_state_with_diff(hunk_collapsed);
17831
17832    cx.update_editor(|editor, _, cx| {
17833        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17834    });
17835    executor.run_until_parked();
17836
17837    cx.assert_state_with_diff(hunk_expanded.clone());
17838}
17839
17840#[gpui::test]
17841async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17842    init_test(cx, |_| {});
17843
17844    let fs = FakeFs::new(cx.executor());
17845    fs.insert_tree(
17846        path!("/test"),
17847        json!({
17848            ".git": {},
17849            "file-1": "ONE\n",
17850            "file-2": "TWO\n",
17851            "file-3": "THREE\n",
17852        }),
17853    )
17854    .await;
17855
17856    fs.set_head_for_repo(
17857        path!("/test/.git").as_ref(),
17858        &[
17859            ("file-1".into(), "one\n".into()),
17860            ("file-2".into(), "two\n".into()),
17861            ("file-3".into(), "three\n".into()),
17862        ],
17863    );
17864
17865    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17866    let mut buffers = vec![];
17867    for i in 1..=3 {
17868        let buffer = project
17869            .update(cx, |project, cx| {
17870                let path = format!(path!("/test/file-{}"), i);
17871                project.open_local_buffer(path, cx)
17872            })
17873            .await
17874            .unwrap();
17875        buffers.push(buffer);
17876    }
17877
17878    let multibuffer = cx.new(|cx| {
17879        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17880        multibuffer.set_all_diff_hunks_expanded(cx);
17881        for buffer in &buffers {
17882            let snapshot = buffer.read(cx).snapshot();
17883            multibuffer.set_excerpts_for_path(
17884                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17885                buffer.clone(),
17886                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17887                DEFAULT_MULTIBUFFER_CONTEXT,
17888                cx,
17889            );
17890        }
17891        multibuffer
17892    });
17893
17894    let editor = cx.add_window(|window, cx| {
17895        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17896    });
17897    cx.run_until_parked();
17898
17899    let snapshot = editor
17900        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17901        .unwrap();
17902    let hunks = snapshot
17903        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17904        .map(|hunk| match hunk {
17905            DisplayDiffHunk::Unfolded {
17906                display_row_range, ..
17907            } => display_row_range,
17908            DisplayDiffHunk::Folded { .. } => unreachable!(),
17909        })
17910        .collect::<Vec<_>>();
17911    assert_eq!(
17912        hunks,
17913        [
17914            DisplayRow(2)..DisplayRow(4),
17915            DisplayRow(7)..DisplayRow(9),
17916            DisplayRow(12)..DisplayRow(14),
17917        ]
17918    );
17919}
17920
17921#[gpui::test]
17922async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17923    init_test(cx, |_| {});
17924
17925    let mut cx = EditorTestContext::new(cx).await;
17926    cx.set_head_text(indoc! { "
17927        one
17928        two
17929        three
17930        four
17931        five
17932        "
17933    });
17934    cx.set_index_text(indoc! { "
17935        one
17936        two
17937        three
17938        four
17939        five
17940        "
17941    });
17942    cx.set_state(indoc! {"
17943        one
17944        TWO
17945        ˇTHREE
17946        FOUR
17947        five
17948    "});
17949    cx.run_until_parked();
17950    cx.update_editor(|editor, window, cx| {
17951        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17952    });
17953    cx.run_until_parked();
17954    cx.assert_index_text(Some(indoc! {"
17955        one
17956        TWO
17957        THREE
17958        FOUR
17959        five
17960    "}));
17961    cx.set_state(indoc! { "
17962        one
17963        TWO
17964        ˇTHREE-HUNDRED
17965        FOUR
17966        five
17967    "});
17968    cx.run_until_parked();
17969    cx.update_editor(|editor, window, cx| {
17970        let snapshot = editor.snapshot(window, cx);
17971        let hunks = editor
17972            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17973            .collect::<Vec<_>>();
17974        assert_eq!(hunks.len(), 1);
17975        assert_eq!(
17976            hunks[0].status(),
17977            DiffHunkStatus {
17978                kind: DiffHunkStatusKind::Modified,
17979                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17980            }
17981        );
17982
17983        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17984    });
17985    cx.run_until_parked();
17986    cx.assert_index_text(Some(indoc! {"
17987        one
17988        TWO
17989        THREE-HUNDRED
17990        FOUR
17991        five
17992    "}));
17993}
17994
17995#[gpui::test]
17996fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17997    init_test(cx, |_| {});
17998
17999    let editor = cx.add_window(|window, cx| {
18000        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18001        build_editor(buffer, window, cx)
18002    });
18003
18004    let render_args = Arc::new(Mutex::new(None));
18005    let snapshot = editor
18006        .update(cx, |editor, window, cx| {
18007            let snapshot = editor.buffer().read(cx).snapshot(cx);
18008            let range =
18009                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18010
18011            struct RenderArgs {
18012                row: MultiBufferRow,
18013                folded: bool,
18014                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18015            }
18016
18017            let crease = Crease::inline(
18018                range,
18019                FoldPlaceholder::test(),
18020                {
18021                    let toggle_callback = render_args.clone();
18022                    move |row, folded, callback, _window, _cx| {
18023                        *toggle_callback.lock() = Some(RenderArgs {
18024                            row,
18025                            folded,
18026                            callback,
18027                        });
18028                        div()
18029                    }
18030                },
18031                |_row, _folded, _window, _cx| div(),
18032            );
18033
18034            editor.insert_creases(Some(crease), cx);
18035            let snapshot = editor.snapshot(window, cx);
18036            let _div = snapshot.render_crease_toggle(
18037                MultiBufferRow(1),
18038                false,
18039                cx.entity().clone(),
18040                window,
18041                cx,
18042            );
18043            snapshot
18044        })
18045        .unwrap();
18046
18047    let render_args = render_args.lock().take().unwrap();
18048    assert_eq!(render_args.row, MultiBufferRow(1));
18049    assert!(!render_args.folded);
18050    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18051
18052    cx.update_window(*editor, |_, window, cx| {
18053        (render_args.callback)(true, window, cx)
18054    })
18055    .unwrap();
18056    let snapshot = editor
18057        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18058        .unwrap();
18059    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18060
18061    cx.update_window(*editor, |_, window, cx| {
18062        (render_args.callback)(false, window, cx)
18063    })
18064    .unwrap();
18065    let snapshot = editor
18066        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18067        .unwrap();
18068    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18069}
18070
18071#[gpui::test]
18072async fn test_input_text(cx: &mut TestAppContext) {
18073    init_test(cx, |_| {});
18074    let mut cx = EditorTestContext::new(cx).await;
18075
18076    cx.set_state(
18077        &r#"ˇone
18078        two
18079
18080        three
18081        fourˇ
18082        five
18083
18084        siˇx"#
18085            .unindent(),
18086    );
18087
18088    cx.dispatch_action(HandleInput(String::new()));
18089    cx.assert_editor_state(
18090        &r#"ˇone
18091        two
18092
18093        three
18094        fourˇ
18095        five
18096
18097        siˇx"#
18098            .unindent(),
18099    );
18100
18101    cx.dispatch_action(HandleInput("AAAA".to_string()));
18102    cx.assert_editor_state(
18103        &r#"AAAAˇone
18104        two
18105
18106        three
18107        fourAAAAˇ
18108        five
18109
18110        siAAAAˇx"#
18111            .unindent(),
18112    );
18113}
18114
18115#[gpui::test]
18116async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18117    init_test(cx, |_| {});
18118
18119    let mut cx = EditorTestContext::new(cx).await;
18120    cx.set_state(
18121        r#"let foo = 1;
18122let foo = 2;
18123let foo = 3;
18124let fooˇ = 4;
18125let foo = 5;
18126let foo = 6;
18127let foo = 7;
18128let foo = 8;
18129let foo = 9;
18130let foo = 10;
18131let foo = 11;
18132let foo = 12;
18133let foo = 13;
18134let foo = 14;
18135let foo = 15;"#,
18136    );
18137
18138    cx.update_editor(|e, window, cx| {
18139        assert_eq!(
18140            e.next_scroll_position,
18141            NextScrollCursorCenterTopBottom::Center,
18142            "Default next scroll direction is center",
18143        );
18144
18145        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18146        assert_eq!(
18147            e.next_scroll_position,
18148            NextScrollCursorCenterTopBottom::Top,
18149            "After center, next scroll direction should be top",
18150        );
18151
18152        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18153        assert_eq!(
18154            e.next_scroll_position,
18155            NextScrollCursorCenterTopBottom::Bottom,
18156            "After top, next scroll direction should be bottom",
18157        );
18158
18159        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18160        assert_eq!(
18161            e.next_scroll_position,
18162            NextScrollCursorCenterTopBottom::Center,
18163            "After bottom, scrolling should start over",
18164        );
18165
18166        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18167        assert_eq!(
18168            e.next_scroll_position,
18169            NextScrollCursorCenterTopBottom::Top,
18170            "Scrolling continues if retriggered fast enough"
18171        );
18172    });
18173
18174    cx.executor()
18175        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18176    cx.executor().run_until_parked();
18177    cx.update_editor(|e, _, _| {
18178        assert_eq!(
18179            e.next_scroll_position,
18180            NextScrollCursorCenterTopBottom::Center,
18181            "If scrolling is not triggered fast enough, it should reset"
18182        );
18183    });
18184}
18185
18186#[gpui::test]
18187async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18188    init_test(cx, |_| {});
18189    let mut cx = EditorLspTestContext::new_rust(
18190        lsp::ServerCapabilities {
18191            definition_provider: Some(lsp::OneOf::Left(true)),
18192            references_provider: Some(lsp::OneOf::Left(true)),
18193            ..lsp::ServerCapabilities::default()
18194        },
18195        cx,
18196    )
18197    .await;
18198
18199    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18200        let go_to_definition = cx
18201            .lsp
18202            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18203                move |params, _| async move {
18204                    if empty_go_to_definition {
18205                        Ok(None)
18206                    } else {
18207                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18208                            uri: params.text_document_position_params.text_document.uri,
18209                            range: lsp::Range::new(
18210                                lsp::Position::new(4, 3),
18211                                lsp::Position::new(4, 6),
18212                            ),
18213                        })))
18214                    }
18215                },
18216            );
18217        let references = cx
18218            .lsp
18219            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18220                Ok(Some(vec![lsp::Location {
18221                    uri: params.text_document_position.text_document.uri,
18222                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18223                }]))
18224            });
18225        (go_to_definition, references)
18226    };
18227
18228    cx.set_state(
18229        &r#"fn one() {
18230            let mut a = ˇtwo();
18231        }
18232
18233        fn two() {}"#
18234            .unindent(),
18235    );
18236    set_up_lsp_handlers(false, &mut cx);
18237    let navigated = cx
18238        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18239        .await
18240        .expect("Failed to navigate to definition");
18241    assert_eq!(
18242        navigated,
18243        Navigated::Yes,
18244        "Should have navigated to definition from the GetDefinition response"
18245    );
18246    cx.assert_editor_state(
18247        &r#"fn one() {
18248            let mut a = two();
18249        }
18250
18251        fn «twoˇ»() {}"#
18252            .unindent(),
18253    );
18254
18255    let editors = cx.update_workspace(|workspace, _, cx| {
18256        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18257    });
18258    cx.update_editor(|_, _, test_editor_cx| {
18259        assert_eq!(
18260            editors.len(),
18261            1,
18262            "Initially, only one, test, editor should be open in the workspace"
18263        );
18264        assert_eq!(
18265            test_editor_cx.entity(),
18266            editors.last().expect("Asserted len is 1").clone()
18267        );
18268    });
18269
18270    set_up_lsp_handlers(true, &mut cx);
18271    let navigated = cx
18272        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18273        .await
18274        .expect("Failed to navigate to lookup references");
18275    assert_eq!(
18276        navigated,
18277        Navigated::Yes,
18278        "Should have navigated to references as a fallback after empty GoToDefinition response"
18279    );
18280    // We should not change the selections in the existing file,
18281    // if opening another milti buffer with the references
18282    cx.assert_editor_state(
18283        &r#"fn one() {
18284            let mut a = two();
18285        }
18286
18287        fn «twoˇ»() {}"#
18288            .unindent(),
18289    );
18290    let editors = cx.update_workspace(|workspace, _, cx| {
18291        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18292    });
18293    cx.update_editor(|_, _, test_editor_cx| {
18294        assert_eq!(
18295            editors.len(),
18296            2,
18297            "After falling back to references search, we open a new editor with the results"
18298        );
18299        let references_fallback_text = editors
18300            .into_iter()
18301            .find(|new_editor| *new_editor != test_editor_cx.entity())
18302            .expect("Should have one non-test editor now")
18303            .read(test_editor_cx)
18304            .text(test_editor_cx);
18305        assert_eq!(
18306            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18307            "Should use the range from the references response and not the GoToDefinition one"
18308        );
18309    });
18310}
18311
18312#[gpui::test]
18313async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18314    init_test(cx, |_| {});
18315    cx.update(|cx| {
18316        let mut editor_settings = EditorSettings::get_global(cx).clone();
18317        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18318        EditorSettings::override_global(editor_settings, cx);
18319    });
18320    let mut cx = EditorLspTestContext::new_rust(
18321        lsp::ServerCapabilities {
18322            definition_provider: Some(lsp::OneOf::Left(true)),
18323            references_provider: Some(lsp::OneOf::Left(true)),
18324            ..lsp::ServerCapabilities::default()
18325        },
18326        cx,
18327    )
18328    .await;
18329    let original_state = r#"fn one() {
18330        let mut a = ˇtwo();
18331    }
18332
18333    fn two() {}"#
18334        .unindent();
18335    cx.set_state(&original_state);
18336
18337    let mut go_to_definition = cx
18338        .lsp
18339        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18340            move |_, _| async move { Ok(None) },
18341        );
18342    let _references = cx
18343        .lsp
18344        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18345            panic!("Should not call for references with no go to definition fallback")
18346        });
18347
18348    let navigated = cx
18349        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18350        .await
18351        .expect("Failed to navigate to lookup references");
18352    go_to_definition
18353        .next()
18354        .await
18355        .expect("Should have called the go_to_definition handler");
18356
18357    assert_eq!(
18358        navigated,
18359        Navigated::No,
18360        "Should have navigated to references as a fallback after empty GoToDefinition response"
18361    );
18362    cx.assert_editor_state(&original_state);
18363    let editors = cx.update_workspace(|workspace, _, cx| {
18364        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18365    });
18366    cx.update_editor(|_, _, _| {
18367        assert_eq!(
18368            editors.len(),
18369            1,
18370            "After unsuccessful fallback, no other editor should have been opened"
18371        );
18372    });
18373}
18374
18375#[gpui::test]
18376async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18377    init_test(cx, |_| {});
18378
18379    let language = Arc::new(Language::new(
18380        LanguageConfig::default(),
18381        Some(tree_sitter_rust::LANGUAGE.into()),
18382    ));
18383
18384    let text = r#"
18385        #[cfg(test)]
18386        mod tests() {
18387            #[test]
18388            fn runnable_1() {
18389                let a = 1;
18390            }
18391
18392            #[test]
18393            fn runnable_2() {
18394                let a = 1;
18395                let b = 2;
18396            }
18397        }
18398    "#
18399    .unindent();
18400
18401    let fs = FakeFs::new(cx.executor());
18402    fs.insert_file("/file.rs", Default::default()).await;
18403
18404    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18405    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18406    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18407    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18408    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18409
18410    let editor = cx.new_window_entity(|window, cx| {
18411        Editor::new(
18412            EditorMode::full(),
18413            multi_buffer,
18414            Some(project.clone()),
18415            window,
18416            cx,
18417        )
18418    });
18419
18420    editor.update_in(cx, |editor, window, cx| {
18421        let snapshot = editor.buffer().read(cx).snapshot(cx);
18422        editor.tasks.insert(
18423            (buffer.read(cx).remote_id(), 3),
18424            RunnableTasks {
18425                templates: vec![],
18426                offset: snapshot.anchor_before(43),
18427                column: 0,
18428                extra_variables: HashMap::default(),
18429                context_range: BufferOffset(43)..BufferOffset(85),
18430            },
18431        );
18432        editor.tasks.insert(
18433            (buffer.read(cx).remote_id(), 8),
18434            RunnableTasks {
18435                templates: vec![],
18436                offset: snapshot.anchor_before(86),
18437                column: 0,
18438                extra_variables: HashMap::default(),
18439                context_range: BufferOffset(86)..BufferOffset(191),
18440            },
18441        );
18442
18443        // Test finding task when cursor is inside function body
18444        editor.change_selections(None, window, cx, |s| {
18445            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18446        });
18447        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18448        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18449
18450        // Test finding task when cursor is on function name
18451        editor.change_selections(None, window, cx, |s| {
18452            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18453        });
18454        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18455        assert_eq!(row, 8, "Should find task when cursor is on function name");
18456    });
18457}
18458
18459#[gpui::test]
18460async fn test_folding_buffers(cx: &mut TestAppContext) {
18461    init_test(cx, |_| {});
18462
18463    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18464    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18465    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18466
18467    let fs = FakeFs::new(cx.executor());
18468    fs.insert_tree(
18469        path!("/a"),
18470        json!({
18471            "first.rs": sample_text_1,
18472            "second.rs": sample_text_2,
18473            "third.rs": sample_text_3,
18474        }),
18475    )
18476    .await;
18477    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18478    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18479    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18480    let worktree = project.update(cx, |project, cx| {
18481        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18482        assert_eq!(worktrees.len(), 1);
18483        worktrees.pop().unwrap()
18484    });
18485    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18486
18487    let buffer_1 = project
18488        .update(cx, |project, cx| {
18489            project.open_buffer((worktree_id, "first.rs"), cx)
18490        })
18491        .await
18492        .unwrap();
18493    let buffer_2 = project
18494        .update(cx, |project, cx| {
18495            project.open_buffer((worktree_id, "second.rs"), cx)
18496        })
18497        .await
18498        .unwrap();
18499    let buffer_3 = project
18500        .update(cx, |project, cx| {
18501            project.open_buffer((worktree_id, "third.rs"), cx)
18502        })
18503        .await
18504        .unwrap();
18505
18506    let multi_buffer = cx.new(|cx| {
18507        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18508        multi_buffer.push_excerpts(
18509            buffer_1.clone(),
18510            [
18511                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18512                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18513                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18514            ],
18515            cx,
18516        );
18517        multi_buffer.push_excerpts(
18518            buffer_2.clone(),
18519            [
18520                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18521                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18522                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18523            ],
18524            cx,
18525        );
18526        multi_buffer.push_excerpts(
18527            buffer_3.clone(),
18528            [
18529                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18530                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18531                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18532            ],
18533            cx,
18534        );
18535        multi_buffer
18536    });
18537    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18538        Editor::new(
18539            EditorMode::full(),
18540            multi_buffer.clone(),
18541            Some(project.clone()),
18542            window,
18543            cx,
18544        )
18545    });
18546
18547    assert_eq!(
18548        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18549        "\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",
18550    );
18551
18552    multi_buffer_editor.update(cx, |editor, cx| {
18553        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18554    });
18555    assert_eq!(
18556        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18557        "\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",
18558        "After folding the first buffer, its text should not be displayed"
18559    );
18560
18561    multi_buffer_editor.update(cx, |editor, cx| {
18562        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18563    });
18564    assert_eq!(
18565        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18566        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18567        "After folding the second buffer, its text should not be displayed"
18568    );
18569
18570    multi_buffer_editor.update(cx, |editor, cx| {
18571        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18572    });
18573    assert_eq!(
18574        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18575        "\n\n\n\n\n",
18576        "After folding the third buffer, its text should not be displayed"
18577    );
18578
18579    // Emulate selection inside the fold logic, that should work
18580    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18581        editor
18582            .snapshot(window, cx)
18583            .next_line_boundary(Point::new(0, 4));
18584    });
18585
18586    multi_buffer_editor.update(cx, |editor, cx| {
18587        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18588    });
18589    assert_eq!(
18590        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18591        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18592        "After unfolding the second buffer, its text should be displayed"
18593    );
18594
18595    // Typing inside of buffer 1 causes that buffer to be unfolded.
18596    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18597        assert_eq!(
18598            multi_buffer
18599                .read(cx)
18600                .snapshot(cx)
18601                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18602                .collect::<String>(),
18603            "bbbb"
18604        );
18605        editor.change_selections(None, window, cx, |selections| {
18606            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18607        });
18608        editor.handle_input("B", window, cx);
18609    });
18610
18611    assert_eq!(
18612        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18613        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18614        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18615    );
18616
18617    multi_buffer_editor.update(cx, |editor, cx| {
18618        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18619    });
18620    assert_eq!(
18621        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18622        "\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",
18623        "After unfolding the all buffers, all original text should be displayed"
18624    );
18625}
18626
18627#[gpui::test]
18628async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18629    init_test(cx, |_| {});
18630
18631    let sample_text_1 = "1111\n2222\n3333".to_string();
18632    let sample_text_2 = "4444\n5555\n6666".to_string();
18633    let sample_text_3 = "7777\n8888\n9999".to_string();
18634
18635    let fs = FakeFs::new(cx.executor());
18636    fs.insert_tree(
18637        path!("/a"),
18638        json!({
18639            "first.rs": sample_text_1,
18640            "second.rs": sample_text_2,
18641            "third.rs": sample_text_3,
18642        }),
18643    )
18644    .await;
18645    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18646    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18647    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18648    let worktree = project.update(cx, |project, cx| {
18649        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18650        assert_eq!(worktrees.len(), 1);
18651        worktrees.pop().unwrap()
18652    });
18653    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18654
18655    let buffer_1 = project
18656        .update(cx, |project, cx| {
18657            project.open_buffer((worktree_id, "first.rs"), cx)
18658        })
18659        .await
18660        .unwrap();
18661    let buffer_2 = project
18662        .update(cx, |project, cx| {
18663            project.open_buffer((worktree_id, "second.rs"), cx)
18664        })
18665        .await
18666        .unwrap();
18667    let buffer_3 = project
18668        .update(cx, |project, cx| {
18669            project.open_buffer((worktree_id, "third.rs"), cx)
18670        })
18671        .await
18672        .unwrap();
18673
18674    let multi_buffer = cx.new(|cx| {
18675        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18676        multi_buffer.push_excerpts(
18677            buffer_1.clone(),
18678            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18679            cx,
18680        );
18681        multi_buffer.push_excerpts(
18682            buffer_2.clone(),
18683            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18684            cx,
18685        );
18686        multi_buffer.push_excerpts(
18687            buffer_3.clone(),
18688            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18689            cx,
18690        );
18691        multi_buffer
18692    });
18693
18694    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18695        Editor::new(
18696            EditorMode::full(),
18697            multi_buffer,
18698            Some(project.clone()),
18699            window,
18700            cx,
18701        )
18702    });
18703
18704    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18705    assert_eq!(
18706        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18707        full_text,
18708    );
18709
18710    multi_buffer_editor.update(cx, |editor, cx| {
18711        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18712    });
18713    assert_eq!(
18714        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18715        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18716        "After folding the first buffer, its text should not be displayed"
18717    );
18718
18719    multi_buffer_editor.update(cx, |editor, cx| {
18720        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18721    });
18722
18723    assert_eq!(
18724        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18725        "\n\n\n\n\n\n7777\n8888\n9999",
18726        "After folding the second buffer, its text should not be displayed"
18727    );
18728
18729    multi_buffer_editor.update(cx, |editor, cx| {
18730        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18731    });
18732    assert_eq!(
18733        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18734        "\n\n\n\n\n",
18735        "After folding the third buffer, its text should not be displayed"
18736    );
18737
18738    multi_buffer_editor.update(cx, |editor, cx| {
18739        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18740    });
18741    assert_eq!(
18742        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18743        "\n\n\n\n4444\n5555\n6666\n\n",
18744        "After unfolding the second buffer, its text should be displayed"
18745    );
18746
18747    multi_buffer_editor.update(cx, |editor, cx| {
18748        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18749    });
18750    assert_eq!(
18751        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18752        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18753        "After unfolding the first buffer, its text should be displayed"
18754    );
18755
18756    multi_buffer_editor.update(cx, |editor, cx| {
18757        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18758    });
18759    assert_eq!(
18760        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18761        full_text,
18762        "After unfolding all buffers, all original text should be displayed"
18763    );
18764}
18765
18766#[gpui::test]
18767async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18768    init_test(cx, |_| {});
18769
18770    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18771
18772    let fs = FakeFs::new(cx.executor());
18773    fs.insert_tree(
18774        path!("/a"),
18775        json!({
18776            "main.rs": sample_text,
18777        }),
18778    )
18779    .await;
18780    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18781    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18782    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18783    let worktree = project.update(cx, |project, cx| {
18784        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18785        assert_eq!(worktrees.len(), 1);
18786        worktrees.pop().unwrap()
18787    });
18788    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18789
18790    let buffer_1 = project
18791        .update(cx, |project, cx| {
18792            project.open_buffer((worktree_id, "main.rs"), cx)
18793        })
18794        .await
18795        .unwrap();
18796
18797    let multi_buffer = cx.new(|cx| {
18798        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18799        multi_buffer.push_excerpts(
18800            buffer_1.clone(),
18801            [ExcerptRange::new(
18802                Point::new(0, 0)
18803                    ..Point::new(
18804                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18805                        0,
18806                    ),
18807            )],
18808            cx,
18809        );
18810        multi_buffer
18811    });
18812    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18813        Editor::new(
18814            EditorMode::full(),
18815            multi_buffer,
18816            Some(project.clone()),
18817            window,
18818            cx,
18819        )
18820    });
18821
18822    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18823    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18824        enum TestHighlight {}
18825        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18826        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18827        editor.highlight_text::<TestHighlight>(
18828            vec![highlight_range.clone()],
18829            HighlightStyle::color(Hsla::green()),
18830            cx,
18831        );
18832        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18833    });
18834
18835    let full_text = format!("\n\n{sample_text}");
18836    assert_eq!(
18837        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18838        full_text,
18839    );
18840}
18841
18842#[gpui::test]
18843async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18844    init_test(cx, |_| {});
18845    cx.update(|cx| {
18846        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18847            "keymaps/default-linux.json",
18848            cx,
18849        )
18850        .unwrap();
18851        cx.bind_keys(default_key_bindings);
18852    });
18853
18854    let (editor, cx) = cx.add_window_view(|window, cx| {
18855        let multi_buffer = MultiBuffer::build_multi(
18856            [
18857                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18858                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18859                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18860                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18861            ],
18862            cx,
18863        );
18864        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18865
18866        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18867        // fold all but the second buffer, so that we test navigating between two
18868        // adjacent folded buffers, as well as folded buffers at the start and
18869        // end the multibuffer
18870        editor.fold_buffer(buffer_ids[0], cx);
18871        editor.fold_buffer(buffer_ids[2], cx);
18872        editor.fold_buffer(buffer_ids[3], cx);
18873
18874        editor
18875    });
18876    cx.simulate_resize(size(px(1000.), px(1000.)));
18877
18878    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18879    cx.assert_excerpts_with_selections(indoc! {"
18880        [EXCERPT]
18881        ˇ[FOLDED]
18882        [EXCERPT]
18883        a1
18884        b1
18885        [EXCERPT]
18886        [FOLDED]
18887        [EXCERPT]
18888        [FOLDED]
18889        "
18890    });
18891    cx.simulate_keystroke("down");
18892    cx.assert_excerpts_with_selections(indoc! {"
18893        [EXCERPT]
18894        [FOLDED]
18895        [EXCERPT]
18896        ˇa1
18897        b1
18898        [EXCERPT]
18899        [FOLDED]
18900        [EXCERPT]
18901        [FOLDED]
18902        "
18903    });
18904    cx.simulate_keystroke("down");
18905    cx.assert_excerpts_with_selections(indoc! {"
18906        [EXCERPT]
18907        [FOLDED]
18908        [EXCERPT]
18909        a1
18910        ˇb1
18911        [EXCERPT]
18912        [FOLDED]
18913        [EXCERPT]
18914        [FOLDED]
18915        "
18916    });
18917    cx.simulate_keystroke("down");
18918    cx.assert_excerpts_with_selections(indoc! {"
18919        [EXCERPT]
18920        [FOLDED]
18921        [EXCERPT]
18922        a1
18923        b1
18924        ˇ[EXCERPT]
18925        [FOLDED]
18926        [EXCERPT]
18927        [FOLDED]
18928        "
18929    });
18930    cx.simulate_keystroke("down");
18931    cx.assert_excerpts_with_selections(indoc! {"
18932        [EXCERPT]
18933        [FOLDED]
18934        [EXCERPT]
18935        a1
18936        b1
18937        [EXCERPT]
18938        ˇ[FOLDED]
18939        [EXCERPT]
18940        [FOLDED]
18941        "
18942    });
18943    for _ in 0..5 {
18944        cx.simulate_keystroke("down");
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    }
18958
18959    cx.simulate_keystroke("up");
18960    cx.assert_excerpts_with_selections(indoc! {"
18961        [EXCERPT]
18962        [FOLDED]
18963        [EXCERPT]
18964        a1
18965        b1
18966        [EXCERPT]
18967        ˇ[FOLDED]
18968        [EXCERPT]
18969        [FOLDED]
18970        "
18971    });
18972    cx.simulate_keystroke("up");
18973    cx.assert_excerpts_with_selections(indoc! {"
18974        [EXCERPT]
18975        [FOLDED]
18976        [EXCERPT]
18977        a1
18978        b1
18979        ˇ[EXCERPT]
18980        [FOLDED]
18981        [EXCERPT]
18982        [FOLDED]
18983        "
18984    });
18985    cx.simulate_keystroke("up");
18986    cx.assert_excerpts_with_selections(indoc! {"
18987        [EXCERPT]
18988        [FOLDED]
18989        [EXCERPT]
18990        a1
18991        ˇb1
18992        [EXCERPT]
18993        [FOLDED]
18994        [EXCERPT]
18995        [FOLDED]
18996        "
18997    });
18998    cx.simulate_keystroke("up");
18999    cx.assert_excerpts_with_selections(indoc! {"
19000        [EXCERPT]
19001        [FOLDED]
19002        [EXCERPT]
19003        ˇa1
19004        b1
19005        [EXCERPT]
19006        [FOLDED]
19007        [EXCERPT]
19008        [FOLDED]
19009        "
19010    });
19011    for _ in 0..5 {
19012        cx.simulate_keystroke("up");
19013        cx.assert_excerpts_with_selections(indoc! {"
19014            [EXCERPT]
19015            ˇ[FOLDED]
19016            [EXCERPT]
19017            a1
19018            b1
19019            [EXCERPT]
19020            [FOLDED]
19021            [EXCERPT]
19022            [FOLDED]
19023            "
19024        });
19025    }
19026}
19027
19028#[gpui::test]
19029async fn test_inline_completion_text(cx: &mut TestAppContext) {
19030    init_test(cx, |_| {});
19031
19032    // Simple insertion
19033    assert_highlighted_edits(
19034        "Hello, world!",
19035        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19036        true,
19037        cx,
19038        |highlighted_edits, cx| {
19039            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19040            assert_eq!(highlighted_edits.highlights.len(), 1);
19041            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19042            assert_eq!(
19043                highlighted_edits.highlights[0].1.background_color,
19044                Some(cx.theme().status().created_background)
19045            );
19046        },
19047    )
19048    .await;
19049
19050    // Replacement
19051    assert_highlighted_edits(
19052        "This is a test.",
19053        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19054        false,
19055        cx,
19056        |highlighted_edits, cx| {
19057            assert_eq!(highlighted_edits.text, "That is a test.");
19058            assert_eq!(highlighted_edits.highlights.len(), 1);
19059            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19060            assert_eq!(
19061                highlighted_edits.highlights[0].1.background_color,
19062                Some(cx.theme().status().created_background)
19063            );
19064        },
19065    )
19066    .await;
19067
19068    // Multiple edits
19069    assert_highlighted_edits(
19070        "Hello, world!",
19071        vec![
19072            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19073            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19074        ],
19075        false,
19076        cx,
19077        |highlighted_edits, cx| {
19078            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19079            assert_eq!(highlighted_edits.highlights.len(), 2);
19080            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19081            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19082            assert_eq!(
19083                highlighted_edits.highlights[0].1.background_color,
19084                Some(cx.theme().status().created_background)
19085            );
19086            assert_eq!(
19087                highlighted_edits.highlights[1].1.background_color,
19088                Some(cx.theme().status().created_background)
19089            );
19090        },
19091    )
19092    .await;
19093
19094    // Multiple lines with edits
19095    assert_highlighted_edits(
19096        "First line\nSecond line\nThird line\nFourth line",
19097        vec![
19098            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19099            (
19100                Point::new(2, 0)..Point::new(2, 10),
19101                "New third line".to_string(),
19102            ),
19103            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19104        ],
19105        false,
19106        cx,
19107        |highlighted_edits, cx| {
19108            assert_eq!(
19109                highlighted_edits.text,
19110                "Second modified\nNew third line\nFourth updated line"
19111            );
19112            assert_eq!(highlighted_edits.highlights.len(), 3);
19113            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19114            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19115            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19116            for highlight in &highlighted_edits.highlights {
19117                assert_eq!(
19118                    highlight.1.background_color,
19119                    Some(cx.theme().status().created_background)
19120                );
19121            }
19122        },
19123    )
19124    .await;
19125}
19126
19127#[gpui::test]
19128async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19129    init_test(cx, |_| {});
19130
19131    // Deletion
19132    assert_highlighted_edits(
19133        "Hello, world!",
19134        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19135        true,
19136        cx,
19137        |highlighted_edits, cx| {
19138            assert_eq!(highlighted_edits.text, "Hello, world!");
19139            assert_eq!(highlighted_edits.highlights.len(), 1);
19140            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19141            assert_eq!(
19142                highlighted_edits.highlights[0].1.background_color,
19143                Some(cx.theme().status().deleted_background)
19144            );
19145        },
19146    )
19147    .await;
19148
19149    // Insertion
19150    assert_highlighted_edits(
19151        "Hello, world!",
19152        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19153        true,
19154        cx,
19155        |highlighted_edits, cx| {
19156            assert_eq!(highlighted_edits.highlights.len(), 1);
19157            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19158            assert_eq!(
19159                highlighted_edits.highlights[0].1.background_color,
19160                Some(cx.theme().status().created_background)
19161            );
19162        },
19163    )
19164    .await;
19165}
19166
19167async fn assert_highlighted_edits(
19168    text: &str,
19169    edits: Vec<(Range<Point>, String)>,
19170    include_deletions: bool,
19171    cx: &mut TestAppContext,
19172    assertion_fn: impl Fn(HighlightedText, &App),
19173) {
19174    let window = cx.add_window(|window, cx| {
19175        let buffer = MultiBuffer::build_simple(text, cx);
19176        Editor::new(EditorMode::full(), buffer, None, window, cx)
19177    });
19178    let cx = &mut VisualTestContext::from_window(*window, cx);
19179
19180    let (buffer, snapshot) = window
19181        .update(cx, |editor, _window, cx| {
19182            (
19183                editor.buffer().clone(),
19184                editor.buffer().read(cx).snapshot(cx),
19185            )
19186        })
19187        .unwrap();
19188
19189    let edits = edits
19190        .into_iter()
19191        .map(|(range, edit)| {
19192            (
19193                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19194                edit,
19195            )
19196        })
19197        .collect::<Vec<_>>();
19198
19199    let text_anchor_edits = edits
19200        .clone()
19201        .into_iter()
19202        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19203        .collect::<Vec<_>>();
19204
19205    let edit_preview = window
19206        .update(cx, |_, _window, cx| {
19207            buffer
19208                .read(cx)
19209                .as_singleton()
19210                .unwrap()
19211                .read(cx)
19212                .preview_edits(text_anchor_edits.into(), cx)
19213        })
19214        .unwrap()
19215        .await;
19216
19217    cx.update(|_window, cx| {
19218        let highlighted_edits = inline_completion_edit_text(
19219            &snapshot.as_singleton().unwrap().2,
19220            &edits,
19221            &edit_preview,
19222            include_deletions,
19223            cx,
19224        );
19225        assertion_fn(highlighted_edits, cx)
19226    });
19227}
19228
19229#[track_caller]
19230fn assert_breakpoint(
19231    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19232    path: &Arc<Path>,
19233    expected: Vec<(u32, Breakpoint)>,
19234) {
19235    if expected.len() == 0usize {
19236        assert!(!breakpoints.contains_key(path), "{}", path.display());
19237    } else {
19238        let mut breakpoint = breakpoints
19239            .get(path)
19240            .unwrap()
19241            .into_iter()
19242            .map(|breakpoint| {
19243                (
19244                    breakpoint.row,
19245                    Breakpoint {
19246                        message: breakpoint.message.clone(),
19247                        state: breakpoint.state,
19248                        condition: breakpoint.condition.clone(),
19249                        hit_condition: breakpoint.hit_condition.clone(),
19250                    },
19251                )
19252            })
19253            .collect::<Vec<_>>();
19254
19255        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19256
19257        assert_eq!(expected, breakpoint);
19258    }
19259}
19260
19261fn add_log_breakpoint_at_cursor(
19262    editor: &mut Editor,
19263    log_message: &str,
19264    window: &mut Window,
19265    cx: &mut Context<Editor>,
19266) {
19267    let (anchor, bp) = editor
19268        .breakpoints_at_cursors(window, cx)
19269        .first()
19270        .and_then(|(anchor, bp)| {
19271            if let Some(bp) = bp {
19272                Some((*anchor, bp.clone()))
19273            } else {
19274                None
19275            }
19276        })
19277        .unwrap_or_else(|| {
19278            let cursor_position: Point = editor.selections.newest(cx).head();
19279
19280            let breakpoint_position = editor
19281                .snapshot(window, cx)
19282                .display_snapshot
19283                .buffer_snapshot
19284                .anchor_before(Point::new(cursor_position.row, 0));
19285
19286            (breakpoint_position, Breakpoint::new_log(&log_message))
19287        });
19288
19289    editor.edit_breakpoint_at_anchor(
19290        anchor,
19291        bp,
19292        BreakpointEditAction::EditLogMessage(log_message.into()),
19293        cx,
19294    );
19295}
19296
19297#[gpui::test]
19298async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19299    init_test(cx, |_| {});
19300
19301    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19302    let fs = FakeFs::new(cx.executor());
19303    fs.insert_tree(
19304        path!("/a"),
19305        json!({
19306            "main.rs": sample_text,
19307        }),
19308    )
19309    .await;
19310    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19311    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19312    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19313
19314    let fs = FakeFs::new(cx.executor());
19315    fs.insert_tree(
19316        path!("/a"),
19317        json!({
19318            "main.rs": sample_text,
19319        }),
19320    )
19321    .await;
19322    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19323    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19324    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19325    let worktree_id = workspace
19326        .update(cx, |workspace, _window, cx| {
19327            workspace.project().update(cx, |project, cx| {
19328                project.worktrees(cx).next().unwrap().read(cx).id()
19329            })
19330        })
19331        .unwrap();
19332
19333    let buffer = project
19334        .update(cx, |project, cx| {
19335            project.open_buffer((worktree_id, "main.rs"), cx)
19336        })
19337        .await
19338        .unwrap();
19339
19340    let (editor, cx) = cx.add_window_view(|window, cx| {
19341        Editor::new(
19342            EditorMode::full(),
19343            MultiBuffer::build_from_buffer(buffer, cx),
19344            Some(project.clone()),
19345            window,
19346            cx,
19347        )
19348    });
19349
19350    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19351    let abs_path = project.read_with(cx, |project, cx| {
19352        project
19353            .absolute_path(&project_path, cx)
19354            .map(|path_buf| Arc::from(path_buf.to_owned()))
19355            .unwrap()
19356    });
19357
19358    // assert we can add breakpoint on the first line
19359    editor.update_in(cx, |editor, window, cx| {
19360        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19361        editor.move_to_end(&MoveToEnd, window, cx);
19362        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19363    });
19364
19365    let breakpoints = editor.update(cx, |editor, cx| {
19366        editor
19367            .breakpoint_store()
19368            .as_ref()
19369            .unwrap()
19370            .read(cx)
19371            .all_source_breakpoints(cx)
19372            .clone()
19373    });
19374
19375    assert_eq!(1, breakpoints.len());
19376    assert_breakpoint(
19377        &breakpoints,
19378        &abs_path,
19379        vec![
19380            (0, Breakpoint::new_standard()),
19381            (3, Breakpoint::new_standard()),
19382        ],
19383    );
19384
19385    editor.update_in(cx, |editor, window, cx| {
19386        editor.move_to_beginning(&MoveToBeginning, window, cx);
19387        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19388    });
19389
19390    let breakpoints = editor.update(cx, |editor, cx| {
19391        editor
19392            .breakpoint_store()
19393            .as_ref()
19394            .unwrap()
19395            .read(cx)
19396            .all_source_breakpoints(cx)
19397            .clone()
19398    });
19399
19400    assert_eq!(1, breakpoints.len());
19401    assert_breakpoint(
19402        &breakpoints,
19403        &abs_path,
19404        vec![(3, Breakpoint::new_standard())],
19405    );
19406
19407    editor.update_in(cx, |editor, window, cx| {
19408        editor.move_to_end(&MoveToEnd, window, cx);
19409        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19410    });
19411
19412    let breakpoints = editor.update(cx, |editor, cx| {
19413        editor
19414            .breakpoint_store()
19415            .as_ref()
19416            .unwrap()
19417            .read(cx)
19418            .all_source_breakpoints(cx)
19419            .clone()
19420    });
19421
19422    assert_eq!(0, breakpoints.len());
19423    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19424}
19425
19426#[gpui::test]
19427async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19428    init_test(cx, |_| {});
19429
19430    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19431
19432    let fs = FakeFs::new(cx.executor());
19433    fs.insert_tree(
19434        path!("/a"),
19435        json!({
19436            "main.rs": sample_text,
19437        }),
19438    )
19439    .await;
19440    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19441    let (workspace, cx) =
19442        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19443
19444    let worktree_id = workspace.update(cx, |workspace, cx| {
19445        workspace.project().update(cx, |project, cx| {
19446            project.worktrees(cx).next().unwrap().read(cx).id()
19447        })
19448    });
19449
19450    let buffer = project
19451        .update(cx, |project, cx| {
19452            project.open_buffer((worktree_id, "main.rs"), cx)
19453        })
19454        .await
19455        .unwrap();
19456
19457    let (editor, cx) = cx.add_window_view(|window, cx| {
19458        Editor::new(
19459            EditorMode::full(),
19460            MultiBuffer::build_from_buffer(buffer, cx),
19461            Some(project.clone()),
19462            window,
19463            cx,
19464        )
19465    });
19466
19467    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19468    let abs_path = project.read_with(cx, |project, cx| {
19469        project
19470            .absolute_path(&project_path, cx)
19471            .map(|path_buf| Arc::from(path_buf.to_owned()))
19472            .unwrap()
19473    });
19474
19475    editor.update_in(cx, |editor, window, cx| {
19476        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19477    });
19478
19479    let breakpoints = editor.update(cx, |editor, cx| {
19480        editor
19481            .breakpoint_store()
19482            .as_ref()
19483            .unwrap()
19484            .read(cx)
19485            .all_source_breakpoints(cx)
19486            .clone()
19487    });
19488
19489    assert_breakpoint(
19490        &breakpoints,
19491        &abs_path,
19492        vec![(0, Breakpoint::new_log("hello world"))],
19493    );
19494
19495    // Removing a log message from a log breakpoint should remove it
19496    editor.update_in(cx, |editor, window, cx| {
19497        add_log_breakpoint_at_cursor(editor, "", window, cx);
19498    });
19499
19500    let breakpoints = editor.update(cx, |editor, cx| {
19501        editor
19502            .breakpoint_store()
19503            .as_ref()
19504            .unwrap()
19505            .read(cx)
19506            .all_source_breakpoints(cx)
19507            .clone()
19508    });
19509
19510    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19511
19512    editor.update_in(cx, |editor, window, cx| {
19513        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19514        editor.move_to_end(&MoveToEnd, window, cx);
19515        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19516        // Not adding a log message to a standard breakpoint shouldn't remove it
19517        add_log_breakpoint_at_cursor(editor, "", window, cx);
19518    });
19519
19520    let breakpoints = editor.update(cx, |editor, cx| {
19521        editor
19522            .breakpoint_store()
19523            .as_ref()
19524            .unwrap()
19525            .read(cx)
19526            .all_source_breakpoints(cx)
19527            .clone()
19528    });
19529
19530    assert_breakpoint(
19531        &breakpoints,
19532        &abs_path,
19533        vec![
19534            (0, Breakpoint::new_standard()),
19535            (3, Breakpoint::new_standard()),
19536        ],
19537    );
19538
19539    editor.update_in(cx, |editor, window, cx| {
19540        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19541    });
19542
19543    let breakpoints = editor.update(cx, |editor, cx| {
19544        editor
19545            .breakpoint_store()
19546            .as_ref()
19547            .unwrap()
19548            .read(cx)
19549            .all_source_breakpoints(cx)
19550            .clone()
19551    });
19552
19553    assert_breakpoint(
19554        &breakpoints,
19555        &abs_path,
19556        vec![
19557            (0, Breakpoint::new_standard()),
19558            (3, Breakpoint::new_log("hello world")),
19559        ],
19560    );
19561
19562    editor.update_in(cx, |editor, window, cx| {
19563        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19564    });
19565
19566    let breakpoints = editor.update(cx, |editor, cx| {
19567        editor
19568            .breakpoint_store()
19569            .as_ref()
19570            .unwrap()
19571            .read(cx)
19572            .all_source_breakpoints(cx)
19573            .clone()
19574    });
19575
19576    assert_breakpoint(
19577        &breakpoints,
19578        &abs_path,
19579        vec![
19580            (0, Breakpoint::new_standard()),
19581            (3, Breakpoint::new_log("hello Earth!!")),
19582        ],
19583    );
19584}
19585
19586/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19587/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19588/// or when breakpoints were placed out of order. This tests for a regression too
19589#[gpui::test]
19590async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19591    init_test(cx, |_| {});
19592
19593    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19594    let fs = FakeFs::new(cx.executor());
19595    fs.insert_tree(
19596        path!("/a"),
19597        json!({
19598            "main.rs": sample_text,
19599        }),
19600    )
19601    .await;
19602    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19603    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19604    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19605
19606    let fs = FakeFs::new(cx.executor());
19607    fs.insert_tree(
19608        path!("/a"),
19609        json!({
19610            "main.rs": sample_text,
19611        }),
19612    )
19613    .await;
19614    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19615    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19616    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19617    let worktree_id = workspace
19618        .update(cx, |workspace, _window, cx| {
19619            workspace.project().update(cx, |project, cx| {
19620                project.worktrees(cx).next().unwrap().read(cx).id()
19621            })
19622        })
19623        .unwrap();
19624
19625    let buffer = project
19626        .update(cx, |project, cx| {
19627            project.open_buffer((worktree_id, "main.rs"), cx)
19628        })
19629        .await
19630        .unwrap();
19631
19632    let (editor, cx) = cx.add_window_view(|window, cx| {
19633        Editor::new(
19634            EditorMode::full(),
19635            MultiBuffer::build_from_buffer(buffer, cx),
19636            Some(project.clone()),
19637            window,
19638            cx,
19639        )
19640    });
19641
19642    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19643    let abs_path = project.read_with(cx, |project, cx| {
19644        project
19645            .absolute_path(&project_path, cx)
19646            .map(|path_buf| Arc::from(path_buf.to_owned()))
19647            .unwrap()
19648    });
19649
19650    // assert we can add breakpoint on the first line
19651    editor.update_in(cx, |editor, window, cx| {
19652        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19653        editor.move_to_end(&MoveToEnd, window, cx);
19654        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19655        editor.move_up(&MoveUp, window, cx);
19656        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19657    });
19658
19659    let breakpoints = editor.update(cx, |editor, cx| {
19660        editor
19661            .breakpoint_store()
19662            .as_ref()
19663            .unwrap()
19664            .read(cx)
19665            .all_source_breakpoints(cx)
19666            .clone()
19667    });
19668
19669    assert_eq!(1, breakpoints.len());
19670    assert_breakpoint(
19671        &breakpoints,
19672        &abs_path,
19673        vec![
19674            (0, Breakpoint::new_standard()),
19675            (2, Breakpoint::new_standard()),
19676            (3, Breakpoint::new_standard()),
19677        ],
19678    );
19679
19680    editor.update_in(cx, |editor, window, cx| {
19681        editor.move_to_beginning(&MoveToBeginning, window, cx);
19682        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19683        editor.move_to_end(&MoveToEnd, window, cx);
19684        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19685        // Disabling a breakpoint that doesn't exist should do nothing
19686        editor.move_up(&MoveUp, window, cx);
19687        editor.move_up(&MoveUp, window, cx);
19688        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19689    });
19690
19691    let breakpoints = editor.update(cx, |editor, cx| {
19692        editor
19693            .breakpoint_store()
19694            .as_ref()
19695            .unwrap()
19696            .read(cx)
19697            .all_source_breakpoints(cx)
19698            .clone()
19699    });
19700
19701    let disable_breakpoint = {
19702        let mut bp = Breakpoint::new_standard();
19703        bp.state = BreakpointState::Disabled;
19704        bp
19705    };
19706
19707    assert_eq!(1, breakpoints.len());
19708    assert_breakpoint(
19709        &breakpoints,
19710        &abs_path,
19711        vec![
19712            (0, disable_breakpoint.clone()),
19713            (2, Breakpoint::new_standard()),
19714            (3, disable_breakpoint.clone()),
19715        ],
19716    );
19717
19718    editor.update_in(cx, |editor, window, cx| {
19719        editor.move_to_beginning(&MoveToBeginning, window, cx);
19720        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19721        editor.move_to_end(&MoveToEnd, window, cx);
19722        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19723        editor.move_up(&MoveUp, window, cx);
19724        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19725    });
19726
19727    let breakpoints = editor.update(cx, |editor, cx| {
19728        editor
19729            .breakpoint_store()
19730            .as_ref()
19731            .unwrap()
19732            .read(cx)
19733            .all_source_breakpoints(cx)
19734            .clone()
19735    });
19736
19737    assert_eq!(1, breakpoints.len());
19738    assert_breakpoint(
19739        &breakpoints,
19740        &abs_path,
19741        vec![
19742            (0, Breakpoint::new_standard()),
19743            (2, disable_breakpoint),
19744            (3, Breakpoint::new_standard()),
19745        ],
19746    );
19747}
19748
19749#[gpui::test]
19750async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19751    init_test(cx, |_| {});
19752    let capabilities = lsp::ServerCapabilities {
19753        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19754            prepare_provider: Some(true),
19755            work_done_progress_options: Default::default(),
19756        })),
19757        ..Default::default()
19758    };
19759    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19760
19761    cx.set_state(indoc! {"
19762        struct Fˇoo {}
19763    "});
19764
19765    cx.update_editor(|editor, _, cx| {
19766        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19767        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19768        editor.highlight_background::<DocumentHighlightRead>(
19769            &[highlight_range],
19770            |c| c.editor_document_highlight_read_background,
19771            cx,
19772        );
19773    });
19774
19775    let mut prepare_rename_handler = cx
19776        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19777            move |_, _, _| async move {
19778                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19779                    start: lsp::Position {
19780                        line: 0,
19781                        character: 7,
19782                    },
19783                    end: lsp::Position {
19784                        line: 0,
19785                        character: 10,
19786                    },
19787                })))
19788            },
19789        );
19790    let prepare_rename_task = cx
19791        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19792        .expect("Prepare rename was not started");
19793    prepare_rename_handler.next().await.unwrap();
19794    prepare_rename_task.await.expect("Prepare rename failed");
19795
19796    let mut rename_handler =
19797        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19798            let edit = lsp::TextEdit {
19799                range: lsp::Range {
19800                    start: lsp::Position {
19801                        line: 0,
19802                        character: 7,
19803                    },
19804                    end: lsp::Position {
19805                        line: 0,
19806                        character: 10,
19807                    },
19808                },
19809                new_text: "FooRenamed".to_string(),
19810            };
19811            Ok(Some(lsp::WorkspaceEdit::new(
19812                // Specify the same edit twice
19813                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19814            )))
19815        });
19816    let rename_task = cx
19817        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19818        .expect("Confirm rename was not started");
19819    rename_handler.next().await.unwrap();
19820    rename_task.await.expect("Confirm rename failed");
19821    cx.run_until_parked();
19822
19823    // Despite two edits, only one is actually applied as those are identical
19824    cx.assert_editor_state(indoc! {"
19825        struct FooRenamedˇ {}
19826    "});
19827}
19828
19829#[gpui::test]
19830async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19831    init_test(cx, |_| {});
19832    // These capabilities indicate that the server does not support prepare rename.
19833    let capabilities = lsp::ServerCapabilities {
19834        rename_provider: Some(lsp::OneOf::Left(true)),
19835        ..Default::default()
19836    };
19837    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19838
19839    cx.set_state(indoc! {"
19840        struct Fˇoo {}
19841    "});
19842
19843    cx.update_editor(|editor, _window, cx| {
19844        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19845        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19846        editor.highlight_background::<DocumentHighlightRead>(
19847            &[highlight_range],
19848            |c| c.editor_document_highlight_read_background,
19849            cx,
19850        );
19851    });
19852
19853    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19854        .expect("Prepare rename was not started")
19855        .await
19856        .expect("Prepare rename failed");
19857
19858    let mut rename_handler =
19859        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19860            let edit = lsp::TextEdit {
19861                range: lsp::Range {
19862                    start: lsp::Position {
19863                        line: 0,
19864                        character: 7,
19865                    },
19866                    end: lsp::Position {
19867                        line: 0,
19868                        character: 10,
19869                    },
19870                },
19871                new_text: "FooRenamed".to_string(),
19872            };
19873            Ok(Some(lsp::WorkspaceEdit::new(
19874                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19875            )))
19876        });
19877    let rename_task = cx
19878        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19879        .expect("Confirm rename was not started");
19880    rename_handler.next().await.unwrap();
19881    rename_task.await.expect("Confirm rename failed");
19882    cx.run_until_parked();
19883
19884    // Correct range is renamed, as `surrounding_word` is used to find it.
19885    cx.assert_editor_state(indoc! {"
19886        struct FooRenamedˇ {}
19887    "});
19888}
19889
19890#[gpui::test]
19891async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19892    init_test(cx, |_| {});
19893    let mut cx = EditorTestContext::new(cx).await;
19894
19895    let language = Arc::new(
19896        Language::new(
19897            LanguageConfig::default(),
19898            Some(tree_sitter_html::LANGUAGE.into()),
19899        )
19900        .with_brackets_query(
19901            r#"
19902            ("<" @open "/>" @close)
19903            ("</" @open ">" @close)
19904            ("<" @open ">" @close)
19905            ("\"" @open "\"" @close)
19906            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19907        "#,
19908        )
19909        .unwrap(),
19910    );
19911    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19912
19913    cx.set_state(indoc! {"
19914        <span>ˇ</span>
19915    "});
19916    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19917    cx.assert_editor_state(indoc! {"
19918        <span>
19919        ˇ
19920        </span>
19921    "});
19922
19923    cx.set_state(indoc! {"
19924        <span><span></span>ˇ</span>
19925    "});
19926    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19927    cx.assert_editor_state(indoc! {"
19928        <span><span></span>
19929        ˇ</span>
19930    "});
19931
19932    cx.set_state(indoc! {"
19933        <span>ˇ
19934        </span>
19935    "});
19936    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19937    cx.assert_editor_state(indoc! {"
19938        <span>
19939        ˇ
19940        </span>
19941    "});
19942}
19943
19944#[gpui::test(iterations = 10)]
19945async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19946    init_test(cx, |_| {});
19947
19948    let fs = FakeFs::new(cx.executor());
19949    fs.insert_tree(
19950        path!("/dir"),
19951        json!({
19952            "a.ts": "a",
19953        }),
19954    )
19955    .await;
19956
19957    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19958    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19959    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19960
19961    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19962    language_registry.add(Arc::new(Language::new(
19963        LanguageConfig {
19964            name: "TypeScript".into(),
19965            matcher: LanguageMatcher {
19966                path_suffixes: vec!["ts".to_string()],
19967                ..Default::default()
19968            },
19969            ..Default::default()
19970        },
19971        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19972    )));
19973    let mut fake_language_servers = language_registry.register_fake_lsp(
19974        "TypeScript",
19975        FakeLspAdapter {
19976            capabilities: lsp::ServerCapabilities {
19977                code_lens_provider: Some(lsp::CodeLensOptions {
19978                    resolve_provider: Some(true),
19979                }),
19980                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19981                    commands: vec!["_the/command".to_string()],
19982                    ..lsp::ExecuteCommandOptions::default()
19983                }),
19984                ..lsp::ServerCapabilities::default()
19985            },
19986            ..FakeLspAdapter::default()
19987        },
19988    );
19989
19990    let (buffer, _handle) = project
19991        .update(cx, |p, cx| {
19992            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19993        })
19994        .await
19995        .unwrap();
19996    cx.executor().run_until_parked();
19997
19998    let fake_server = fake_language_servers.next().await.unwrap();
19999
20000    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20001    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20002    drop(buffer_snapshot);
20003    let actions = cx
20004        .update_window(*workspace, |_, window, cx| {
20005            project.code_actions(&buffer, anchor..anchor, window, cx)
20006        })
20007        .unwrap();
20008
20009    fake_server
20010        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20011            Ok(Some(vec![
20012                lsp::CodeLens {
20013                    range: lsp::Range::default(),
20014                    command: Some(lsp::Command {
20015                        title: "Code lens command".to_owned(),
20016                        command: "_the/command".to_owned(),
20017                        arguments: None,
20018                    }),
20019                    data: None,
20020                },
20021                lsp::CodeLens {
20022                    range: lsp::Range::default(),
20023                    command: Some(lsp::Command {
20024                        title: "Command not in capabilities".to_owned(),
20025                        command: "not in capabilities".to_owned(),
20026                        arguments: None,
20027                    }),
20028                    data: None,
20029                },
20030                lsp::CodeLens {
20031                    range: lsp::Range {
20032                        start: lsp::Position {
20033                            line: 1,
20034                            character: 1,
20035                        },
20036                        end: lsp::Position {
20037                            line: 1,
20038                            character: 1,
20039                        },
20040                    },
20041                    command: Some(lsp::Command {
20042                        title: "Command not in range".to_owned(),
20043                        command: "_the/command".to_owned(),
20044                        arguments: None,
20045                    }),
20046                    data: None,
20047                },
20048            ]))
20049        })
20050        .next()
20051        .await;
20052
20053    let actions = actions.await.unwrap();
20054    assert_eq!(
20055        actions.len(),
20056        1,
20057        "Should have only one valid action for the 0..0 range"
20058    );
20059    let action = actions[0].clone();
20060    let apply = project.update(cx, |project, cx| {
20061        project.apply_code_action(buffer.clone(), action, true, cx)
20062    });
20063
20064    // Resolving the code action does not populate its edits. In absence of
20065    // edits, we must execute the given command.
20066    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20067        |mut lens, _| async move {
20068            let lens_command = lens.command.as_mut().expect("should have a command");
20069            assert_eq!(lens_command.title, "Code lens command");
20070            lens_command.arguments = Some(vec![json!("the-argument")]);
20071            Ok(lens)
20072        },
20073    );
20074
20075    // While executing the command, the language server sends the editor
20076    // a `workspaceEdit` request.
20077    fake_server
20078        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20079            let fake = fake_server.clone();
20080            move |params, _| {
20081                assert_eq!(params.command, "_the/command");
20082                let fake = fake.clone();
20083                async move {
20084                    fake.server
20085                        .request::<lsp::request::ApplyWorkspaceEdit>(
20086                            lsp::ApplyWorkspaceEditParams {
20087                                label: None,
20088                                edit: lsp::WorkspaceEdit {
20089                                    changes: Some(
20090                                        [(
20091                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20092                                            vec![lsp::TextEdit {
20093                                                range: lsp::Range::new(
20094                                                    lsp::Position::new(0, 0),
20095                                                    lsp::Position::new(0, 0),
20096                                                ),
20097                                                new_text: "X".into(),
20098                                            }],
20099                                        )]
20100                                        .into_iter()
20101                                        .collect(),
20102                                    ),
20103                                    ..Default::default()
20104                                },
20105                            },
20106                        )
20107                        .await
20108                        .into_response()
20109                        .unwrap();
20110                    Ok(Some(json!(null)))
20111                }
20112            }
20113        })
20114        .next()
20115        .await;
20116
20117    // Applying the code lens command returns a project transaction containing the edits
20118    // sent by the language server in its `workspaceEdit` request.
20119    let transaction = apply.await.unwrap();
20120    assert!(transaction.0.contains_key(&buffer));
20121    buffer.update(cx, |buffer, cx| {
20122        assert_eq!(buffer.text(), "Xa");
20123        buffer.undo(cx);
20124        assert_eq!(buffer.text(), "a");
20125    });
20126}
20127
20128#[gpui::test]
20129async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20130    init_test(cx, |_| {});
20131
20132    let fs = FakeFs::new(cx.executor());
20133    let main_text = r#"fn main() {
20134println!("1");
20135println!("2");
20136println!("3");
20137println!("4");
20138println!("5");
20139}"#;
20140    let lib_text = "mod foo {}";
20141    fs.insert_tree(
20142        path!("/a"),
20143        json!({
20144            "lib.rs": lib_text,
20145            "main.rs": main_text,
20146        }),
20147    )
20148    .await;
20149
20150    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20151    let (workspace, cx) =
20152        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20153    let worktree_id = workspace.update(cx, |workspace, cx| {
20154        workspace.project().update(cx, |project, cx| {
20155            project.worktrees(cx).next().unwrap().read(cx).id()
20156        })
20157    });
20158
20159    let expected_ranges = vec![
20160        Point::new(0, 0)..Point::new(0, 0),
20161        Point::new(1, 0)..Point::new(1, 1),
20162        Point::new(2, 0)..Point::new(2, 2),
20163        Point::new(3, 0)..Point::new(3, 3),
20164    ];
20165
20166    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20167    let editor_1 = workspace
20168        .update_in(cx, |workspace, window, cx| {
20169            workspace.open_path(
20170                (worktree_id, "main.rs"),
20171                Some(pane_1.downgrade()),
20172                true,
20173                window,
20174                cx,
20175            )
20176        })
20177        .unwrap()
20178        .await
20179        .downcast::<Editor>()
20180        .unwrap();
20181    pane_1.update(cx, |pane, cx| {
20182        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20183        open_editor.update(cx, |editor, cx| {
20184            assert_eq!(
20185                editor.display_text(cx),
20186                main_text,
20187                "Original main.rs text on initial open",
20188            );
20189            assert_eq!(
20190                editor
20191                    .selections
20192                    .all::<Point>(cx)
20193                    .into_iter()
20194                    .map(|s| s.range())
20195                    .collect::<Vec<_>>(),
20196                vec![Point::zero()..Point::zero()],
20197                "Default selections on initial open",
20198            );
20199        })
20200    });
20201    editor_1.update_in(cx, |editor, window, cx| {
20202        editor.change_selections(None, window, cx, |s| {
20203            s.select_ranges(expected_ranges.clone());
20204        });
20205    });
20206
20207    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20208        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20209    });
20210    let editor_2 = workspace
20211        .update_in(cx, |workspace, window, cx| {
20212            workspace.open_path(
20213                (worktree_id, "main.rs"),
20214                Some(pane_2.downgrade()),
20215                true,
20216                window,
20217                cx,
20218            )
20219        })
20220        .unwrap()
20221        .await
20222        .downcast::<Editor>()
20223        .unwrap();
20224    pane_2.update(cx, |pane, cx| {
20225        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20226        open_editor.update(cx, |editor, cx| {
20227            assert_eq!(
20228                editor.display_text(cx),
20229                main_text,
20230                "Original main.rs text on initial open in another panel",
20231            );
20232            assert_eq!(
20233                editor
20234                    .selections
20235                    .all::<Point>(cx)
20236                    .into_iter()
20237                    .map(|s| s.range())
20238                    .collect::<Vec<_>>(),
20239                vec![Point::zero()..Point::zero()],
20240                "Default selections on initial open in another panel",
20241            );
20242        })
20243    });
20244
20245    editor_2.update_in(cx, |editor, window, cx| {
20246        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20247    });
20248
20249    let _other_editor_1 = workspace
20250        .update_in(cx, |workspace, window, cx| {
20251            workspace.open_path(
20252                (worktree_id, "lib.rs"),
20253                Some(pane_1.downgrade()),
20254                true,
20255                window,
20256                cx,
20257            )
20258        })
20259        .unwrap()
20260        .await
20261        .downcast::<Editor>()
20262        .unwrap();
20263    pane_1
20264        .update_in(cx, |pane, window, cx| {
20265            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20266        })
20267        .await
20268        .unwrap();
20269    drop(editor_1);
20270    pane_1.update(cx, |pane, cx| {
20271        pane.active_item()
20272            .unwrap()
20273            .downcast::<Editor>()
20274            .unwrap()
20275            .update(cx, |editor, cx| {
20276                assert_eq!(
20277                    editor.display_text(cx),
20278                    lib_text,
20279                    "Other file should be open and active",
20280                );
20281            });
20282        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20283    });
20284
20285    let _other_editor_2 = workspace
20286        .update_in(cx, |workspace, window, cx| {
20287            workspace.open_path(
20288                (worktree_id, "lib.rs"),
20289                Some(pane_2.downgrade()),
20290                true,
20291                window,
20292                cx,
20293            )
20294        })
20295        .unwrap()
20296        .await
20297        .downcast::<Editor>()
20298        .unwrap();
20299    pane_2
20300        .update_in(cx, |pane, window, cx| {
20301            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20302        })
20303        .await
20304        .unwrap();
20305    drop(editor_2);
20306    pane_2.update(cx, |pane, cx| {
20307        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20308        open_editor.update(cx, |editor, cx| {
20309            assert_eq!(
20310                editor.display_text(cx),
20311                lib_text,
20312                "Other file should be open and active in another panel too",
20313            );
20314        });
20315        assert_eq!(
20316            pane.items().count(),
20317            1,
20318            "No other editors should be open in another pane",
20319        );
20320    });
20321
20322    let _editor_1_reopened = workspace
20323        .update_in(cx, |workspace, window, cx| {
20324            workspace.open_path(
20325                (worktree_id, "main.rs"),
20326                Some(pane_1.downgrade()),
20327                true,
20328                window,
20329                cx,
20330            )
20331        })
20332        .unwrap()
20333        .await
20334        .downcast::<Editor>()
20335        .unwrap();
20336    let _editor_2_reopened = workspace
20337        .update_in(cx, |workspace, window, cx| {
20338            workspace.open_path(
20339                (worktree_id, "main.rs"),
20340                Some(pane_2.downgrade()),
20341                true,
20342                window,
20343                cx,
20344            )
20345        })
20346        .unwrap()
20347        .await
20348        .downcast::<Editor>()
20349        .unwrap();
20350    pane_1.update(cx, |pane, cx| {
20351        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20352        open_editor.update(cx, |editor, cx| {
20353            assert_eq!(
20354                editor.display_text(cx),
20355                main_text,
20356                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20357            );
20358            assert_eq!(
20359                editor
20360                    .selections
20361                    .all::<Point>(cx)
20362                    .into_iter()
20363                    .map(|s| s.range())
20364                    .collect::<Vec<_>>(),
20365                expected_ranges,
20366                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20367            );
20368        })
20369    });
20370    pane_2.update(cx, |pane, cx| {
20371        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20372        open_editor.update(cx, |editor, cx| {
20373            assert_eq!(
20374                editor.display_text(cx),
20375                r#"fn main() {
20376⋯rintln!("1");
20377⋯intln!("2");
20378⋯ntln!("3");
20379println!("4");
20380println!("5");
20381}"#,
20382                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20383            );
20384            assert_eq!(
20385                editor
20386                    .selections
20387                    .all::<Point>(cx)
20388                    .into_iter()
20389                    .map(|s| s.range())
20390                    .collect::<Vec<_>>(),
20391                vec![Point::zero()..Point::zero()],
20392                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20393            );
20394        })
20395    });
20396}
20397
20398#[gpui::test]
20399async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20400    init_test(cx, |_| {});
20401
20402    let fs = FakeFs::new(cx.executor());
20403    let main_text = r#"fn main() {
20404println!("1");
20405println!("2");
20406println!("3");
20407println!("4");
20408println!("5");
20409}"#;
20410    let lib_text = "mod foo {}";
20411    fs.insert_tree(
20412        path!("/a"),
20413        json!({
20414            "lib.rs": lib_text,
20415            "main.rs": main_text,
20416        }),
20417    )
20418    .await;
20419
20420    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20421    let (workspace, cx) =
20422        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20423    let worktree_id = workspace.update(cx, |workspace, cx| {
20424        workspace.project().update(cx, |project, cx| {
20425            project.worktrees(cx).next().unwrap().read(cx).id()
20426        })
20427    });
20428
20429    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20430    let editor = workspace
20431        .update_in(cx, |workspace, window, cx| {
20432            workspace.open_path(
20433                (worktree_id, "main.rs"),
20434                Some(pane.downgrade()),
20435                true,
20436                window,
20437                cx,
20438            )
20439        })
20440        .unwrap()
20441        .await
20442        .downcast::<Editor>()
20443        .unwrap();
20444    pane.update(cx, |pane, cx| {
20445        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20446        open_editor.update(cx, |editor, cx| {
20447            assert_eq!(
20448                editor.display_text(cx),
20449                main_text,
20450                "Original main.rs text on initial open",
20451            );
20452        })
20453    });
20454    editor.update_in(cx, |editor, window, cx| {
20455        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20456    });
20457
20458    cx.update_global(|store: &mut SettingsStore, cx| {
20459        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20460            s.restore_on_file_reopen = Some(false);
20461        });
20462    });
20463    editor.update_in(cx, |editor, window, cx| {
20464        editor.fold_ranges(
20465            vec![
20466                Point::new(1, 0)..Point::new(1, 1),
20467                Point::new(2, 0)..Point::new(2, 2),
20468                Point::new(3, 0)..Point::new(3, 3),
20469            ],
20470            false,
20471            window,
20472            cx,
20473        );
20474    });
20475    pane.update_in(cx, |pane, window, cx| {
20476        pane.close_all_items(&CloseAllItems::default(), window, cx)
20477    })
20478    .await
20479    .unwrap();
20480    pane.update(cx, |pane, _| {
20481        assert!(pane.active_item().is_none());
20482    });
20483    cx.update_global(|store: &mut SettingsStore, cx| {
20484        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20485            s.restore_on_file_reopen = Some(true);
20486        });
20487    });
20488
20489    let _editor_reopened = workspace
20490        .update_in(cx, |workspace, window, cx| {
20491            workspace.open_path(
20492                (worktree_id, "main.rs"),
20493                Some(pane.downgrade()),
20494                true,
20495                window,
20496                cx,
20497            )
20498        })
20499        .unwrap()
20500        .await
20501        .downcast::<Editor>()
20502        .unwrap();
20503    pane.update(cx, |pane, cx| {
20504        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20505        open_editor.update(cx, |editor, cx| {
20506            assert_eq!(
20507                editor.display_text(cx),
20508                main_text,
20509                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20510            );
20511        })
20512    });
20513}
20514
20515#[gpui::test]
20516async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20517    struct EmptyModalView {
20518        focus_handle: gpui::FocusHandle,
20519    }
20520    impl EventEmitter<DismissEvent> for EmptyModalView {}
20521    impl Render for EmptyModalView {
20522        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20523            div()
20524        }
20525    }
20526    impl Focusable for EmptyModalView {
20527        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20528            self.focus_handle.clone()
20529        }
20530    }
20531    impl workspace::ModalView for EmptyModalView {}
20532    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20533        EmptyModalView {
20534            focus_handle: cx.focus_handle(),
20535        }
20536    }
20537
20538    init_test(cx, |_| {});
20539
20540    let fs = FakeFs::new(cx.executor());
20541    let project = Project::test(fs, [], cx).await;
20542    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20543    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20544    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20545    let editor = cx.new_window_entity(|window, cx| {
20546        Editor::new(
20547            EditorMode::full(),
20548            buffer,
20549            Some(project.clone()),
20550            window,
20551            cx,
20552        )
20553    });
20554    workspace
20555        .update(cx, |workspace, window, cx| {
20556            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20557        })
20558        .unwrap();
20559    editor.update_in(cx, |editor, window, cx| {
20560        editor.open_context_menu(&OpenContextMenu, window, cx);
20561        assert!(editor.mouse_context_menu.is_some());
20562    });
20563    workspace
20564        .update(cx, |workspace, window, cx| {
20565            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20566        })
20567        .unwrap();
20568    cx.read(|cx| {
20569        assert!(editor.read(cx).mouse_context_menu.is_none());
20570    });
20571}
20572
20573#[gpui::test]
20574async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20575    init_test(cx, |_| {});
20576
20577    let fs = FakeFs::new(cx.executor());
20578    fs.insert_file(path!("/file.html"), Default::default())
20579        .await;
20580
20581    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20582
20583    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20584    let html_language = Arc::new(Language::new(
20585        LanguageConfig {
20586            name: "HTML".into(),
20587            matcher: LanguageMatcher {
20588                path_suffixes: vec!["html".to_string()],
20589                ..LanguageMatcher::default()
20590            },
20591            brackets: BracketPairConfig {
20592                pairs: vec![BracketPair {
20593                    start: "<".into(),
20594                    end: ">".into(),
20595                    close: true,
20596                    ..Default::default()
20597                }],
20598                ..Default::default()
20599            },
20600            ..Default::default()
20601        },
20602        Some(tree_sitter_html::LANGUAGE.into()),
20603    ));
20604    language_registry.add(html_language);
20605    let mut fake_servers = language_registry.register_fake_lsp(
20606        "HTML",
20607        FakeLspAdapter {
20608            capabilities: lsp::ServerCapabilities {
20609                completion_provider: Some(lsp::CompletionOptions {
20610                    resolve_provider: Some(true),
20611                    ..Default::default()
20612                }),
20613                ..Default::default()
20614            },
20615            ..Default::default()
20616        },
20617    );
20618
20619    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20620    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20621
20622    let worktree_id = workspace
20623        .update(cx, |workspace, _window, cx| {
20624            workspace.project().update(cx, |project, cx| {
20625                project.worktrees(cx).next().unwrap().read(cx).id()
20626            })
20627        })
20628        .unwrap();
20629    project
20630        .update(cx, |project, cx| {
20631            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20632        })
20633        .await
20634        .unwrap();
20635    let editor = workspace
20636        .update(cx, |workspace, window, cx| {
20637            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20638        })
20639        .unwrap()
20640        .await
20641        .unwrap()
20642        .downcast::<Editor>()
20643        .unwrap();
20644
20645    let fake_server = fake_servers.next().await.unwrap();
20646    editor.update_in(cx, |editor, window, cx| {
20647        editor.set_text("<ad></ad>", window, cx);
20648        editor.change_selections(None, window, cx, |selections| {
20649            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20650        });
20651        let Some((buffer, _)) = editor
20652            .buffer
20653            .read(cx)
20654            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20655        else {
20656            panic!("Failed to get buffer for selection position");
20657        };
20658        let buffer = buffer.read(cx);
20659        let buffer_id = buffer.remote_id();
20660        let opening_range =
20661            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20662        let closing_range =
20663            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20664        let mut linked_ranges = HashMap::default();
20665        linked_ranges.insert(
20666            buffer_id,
20667            vec![(opening_range.clone(), vec![closing_range.clone()])],
20668        );
20669        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20670    });
20671    let mut completion_handle =
20672        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20673            Ok(Some(lsp::CompletionResponse::Array(vec![
20674                lsp::CompletionItem {
20675                    label: "head".to_string(),
20676                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20677                        lsp::InsertReplaceEdit {
20678                            new_text: "head".to_string(),
20679                            insert: lsp::Range::new(
20680                                lsp::Position::new(0, 1),
20681                                lsp::Position::new(0, 3),
20682                            ),
20683                            replace: lsp::Range::new(
20684                                lsp::Position::new(0, 1),
20685                                lsp::Position::new(0, 3),
20686                            ),
20687                        },
20688                    )),
20689                    ..Default::default()
20690                },
20691            ])))
20692        });
20693    editor.update_in(cx, |editor, window, cx| {
20694        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20695    });
20696    cx.run_until_parked();
20697    completion_handle.next().await.unwrap();
20698    editor.update(cx, |editor, _| {
20699        assert!(
20700            editor.context_menu_visible(),
20701            "Completion menu should be visible"
20702        );
20703    });
20704    editor.update_in(cx, |editor, window, cx| {
20705        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20706    });
20707    cx.executor().run_until_parked();
20708    editor.update(cx, |editor, cx| {
20709        assert_eq!(editor.text(cx), "<head></head>");
20710    });
20711}
20712
20713#[gpui::test]
20714async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20715    init_test(cx, |_| {});
20716
20717    let fs = FakeFs::new(cx.executor());
20718    fs.insert_tree(
20719        path!("/root"),
20720        json!({
20721            "a": {
20722                "main.rs": "fn main() {}",
20723            },
20724            "foo": {
20725                "bar": {
20726                    "external_file.rs": "pub mod external {}",
20727                }
20728            }
20729        }),
20730    )
20731    .await;
20732
20733    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20734    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20735    language_registry.add(rust_lang());
20736    let _fake_servers = language_registry.register_fake_lsp(
20737        "Rust",
20738        FakeLspAdapter {
20739            ..FakeLspAdapter::default()
20740        },
20741    );
20742    let (workspace, cx) =
20743        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20744    let worktree_id = workspace.update(cx, |workspace, cx| {
20745        workspace.project().update(cx, |project, cx| {
20746            project.worktrees(cx).next().unwrap().read(cx).id()
20747        })
20748    });
20749
20750    let assert_language_servers_count =
20751        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20752            project.update(cx, |project, cx| {
20753                let current = project
20754                    .lsp_store()
20755                    .read(cx)
20756                    .as_local()
20757                    .unwrap()
20758                    .language_servers
20759                    .len();
20760                assert_eq!(expected, current, "{context}");
20761            });
20762        };
20763
20764    assert_language_servers_count(
20765        0,
20766        "No servers should be running before any file is open",
20767        cx,
20768    );
20769    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20770    let main_editor = workspace
20771        .update_in(cx, |workspace, window, cx| {
20772            workspace.open_path(
20773                (worktree_id, "main.rs"),
20774                Some(pane.downgrade()),
20775                true,
20776                window,
20777                cx,
20778            )
20779        })
20780        .unwrap()
20781        .await
20782        .downcast::<Editor>()
20783        .unwrap();
20784    pane.update(cx, |pane, cx| {
20785        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20786        open_editor.update(cx, |editor, cx| {
20787            assert_eq!(
20788                editor.display_text(cx),
20789                "fn main() {}",
20790                "Original main.rs text on initial open",
20791            );
20792        });
20793        assert_eq!(open_editor, main_editor);
20794    });
20795    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20796
20797    let external_editor = workspace
20798        .update_in(cx, |workspace, window, cx| {
20799            workspace.open_abs_path(
20800                PathBuf::from("/root/foo/bar/external_file.rs"),
20801                OpenOptions::default(),
20802                window,
20803                cx,
20804            )
20805        })
20806        .await
20807        .expect("opening external file")
20808        .downcast::<Editor>()
20809        .expect("downcasted external file's open element to editor");
20810    pane.update(cx, |pane, cx| {
20811        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20812        open_editor.update(cx, |editor, cx| {
20813            assert_eq!(
20814                editor.display_text(cx),
20815                "pub mod external {}",
20816                "External file is open now",
20817            );
20818        });
20819        assert_eq!(open_editor, external_editor);
20820    });
20821    assert_language_servers_count(
20822        1,
20823        "Second, external, *.rs file should join the existing server",
20824        cx,
20825    );
20826
20827    pane.update_in(cx, |pane, window, cx| {
20828        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20829    })
20830    .await
20831    .unwrap();
20832    pane.update_in(cx, |pane, window, cx| {
20833        pane.navigate_backward(window, cx);
20834    });
20835    cx.run_until_parked();
20836    pane.update(cx, |pane, cx| {
20837        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20838        open_editor.update(cx, |editor, cx| {
20839            assert_eq!(
20840                editor.display_text(cx),
20841                "pub mod external {}",
20842                "External file is open now",
20843            );
20844        });
20845    });
20846    assert_language_servers_count(
20847        1,
20848        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20849        cx,
20850    );
20851
20852    cx.update(|_, cx| {
20853        workspace::reload(&workspace::Reload::default(), cx);
20854    });
20855    assert_language_servers_count(
20856        1,
20857        "After reloading the worktree with local and external files opened, only one project should be started",
20858        cx,
20859    );
20860}
20861
20862#[gpui::test]
20863async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20864    init_test(cx, |_| {});
20865
20866    let mut cx = EditorTestContext::new(cx).await;
20867    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20868    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20869
20870    // test cursor move to start of each line on tab
20871    // for `if`, `elif`, `else`, `while`, `with` and `for`
20872    cx.set_state(indoc! {"
20873        def main():
20874        ˇ    for item in items:
20875        ˇ        while item.active:
20876        ˇ            if item.value > 10:
20877        ˇ                continue
20878        ˇ            elif item.value < 0:
20879        ˇ                break
20880        ˇ            else:
20881        ˇ                with item.context() as ctx:
20882        ˇ                    yield count
20883        ˇ        else:
20884        ˇ            log('while else')
20885        ˇ    else:
20886        ˇ        log('for else')
20887    "});
20888    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20889    cx.assert_editor_state(indoc! {"
20890        def main():
20891            ˇfor item in items:
20892                ˇwhile item.active:
20893                    ˇif item.value > 10:
20894                        ˇcontinue
20895                    ˇelif item.value < 0:
20896                        ˇbreak
20897                    ˇelse:
20898                        ˇwith item.context() as ctx:
20899                            ˇyield count
20900                ˇelse:
20901                    ˇlog('while else')
20902            ˇelse:
20903                ˇlog('for else')
20904    "});
20905    // test relative indent is preserved when tab
20906    // for `if`, `elif`, `else`, `while`, `with` and `for`
20907    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20908    cx.assert_editor_state(indoc! {"
20909        def main():
20910                ˇfor item in items:
20911                    ˇwhile item.active:
20912                        ˇif item.value > 10:
20913                            ˇcontinue
20914                        ˇelif item.value < 0:
20915                            ˇbreak
20916                        ˇelse:
20917                            ˇwith item.context() as ctx:
20918                                ˇyield count
20919                    ˇelse:
20920                        ˇlog('while else')
20921                ˇelse:
20922                    ˇlog('for else')
20923    "});
20924
20925    // test cursor move to start of each line on tab
20926    // for `try`, `except`, `else`, `finally`, `match` and `def`
20927    cx.set_state(indoc! {"
20928        def main():
20929        ˇ    try:
20930        ˇ       fetch()
20931        ˇ    except ValueError:
20932        ˇ       handle_error()
20933        ˇ    else:
20934        ˇ        match value:
20935        ˇ            case _:
20936        ˇ    finally:
20937        ˇ        def status():
20938        ˇ            return 0
20939    "});
20940    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20941    cx.assert_editor_state(indoc! {"
20942        def main():
20943            ˇtry:
20944                ˇfetch()
20945            ˇexcept ValueError:
20946                ˇhandle_error()
20947            ˇelse:
20948                ˇmatch value:
20949                    ˇcase _:
20950            ˇfinally:
20951                ˇdef status():
20952                    ˇreturn 0
20953    "});
20954    // test relative indent is preserved when tab
20955    // for `try`, `except`, `else`, `finally`, `match` and `def`
20956    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20957    cx.assert_editor_state(indoc! {"
20958        def main():
20959                ˇtry:
20960                    ˇfetch()
20961                ˇexcept ValueError:
20962                    ˇhandle_error()
20963                ˇelse:
20964                    ˇmatch value:
20965                        ˇcase _:
20966                ˇfinally:
20967                    ˇdef status():
20968                        ˇreturn 0
20969    "});
20970}
20971
20972#[gpui::test]
20973async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
20974    init_test(cx, |_| {});
20975
20976    let mut cx = EditorTestContext::new(cx).await;
20977    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20978    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20979
20980    // test `else` auto outdents when typed inside `if` block
20981    cx.set_state(indoc! {"
20982        def main():
20983            if i == 2:
20984                return
20985                ˇ
20986    "});
20987    cx.update_editor(|editor, window, cx| {
20988        editor.handle_input("else:", window, cx);
20989    });
20990    cx.assert_editor_state(indoc! {"
20991        def main():
20992            if i == 2:
20993                return
20994            else:ˇ
20995    "});
20996
20997    // test `except` auto outdents when typed inside `try` block
20998    cx.set_state(indoc! {"
20999        def main():
21000            try:
21001                i = 2
21002                ˇ
21003    "});
21004    cx.update_editor(|editor, window, cx| {
21005        editor.handle_input("except:", window, cx);
21006    });
21007    cx.assert_editor_state(indoc! {"
21008        def main():
21009            try:
21010                i = 2
21011            except:ˇ
21012    "});
21013
21014    // test `else` auto outdents when typed inside `except` block
21015    cx.set_state(indoc! {"
21016        def main():
21017            try:
21018                i = 2
21019            except:
21020                j = 2
21021                ˇ
21022    "});
21023    cx.update_editor(|editor, window, cx| {
21024        editor.handle_input("else:", window, cx);
21025    });
21026    cx.assert_editor_state(indoc! {"
21027        def main():
21028            try:
21029                i = 2
21030            except:
21031                j = 2
21032            else:ˇ
21033    "});
21034
21035    // test `finally` auto outdents when typed inside `else` block
21036    cx.set_state(indoc! {"
21037        def main():
21038            try:
21039                i = 2
21040            except:
21041                j = 2
21042            else:
21043                k = 2
21044                ˇ
21045    "});
21046    cx.update_editor(|editor, window, cx| {
21047        editor.handle_input("finally:", window, cx);
21048    });
21049    cx.assert_editor_state(indoc! {"
21050        def main():
21051            try:
21052                i = 2
21053            except:
21054                j = 2
21055            else:
21056                k = 2
21057            finally:ˇ
21058    "});
21059
21060    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21061    // cx.set_state(indoc! {"
21062    //     def main():
21063    //         try:
21064    //             for i in range(n):
21065    //                 pass
21066    //             ˇ
21067    // "});
21068    // cx.update_editor(|editor, window, cx| {
21069    //     editor.handle_input("except:", window, cx);
21070    // });
21071    // cx.assert_editor_state(indoc! {"
21072    //     def main():
21073    //         try:
21074    //             for i in range(n):
21075    //                 pass
21076    //         except:ˇ
21077    // "});
21078
21079    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21080    // cx.set_state(indoc! {"
21081    //     def main():
21082    //         try:
21083    //             i = 2
21084    //         except:
21085    //             for i in range(n):
21086    //                 pass
21087    //             ˇ
21088    // "});
21089    // cx.update_editor(|editor, window, cx| {
21090    //     editor.handle_input("else:", window, cx);
21091    // });
21092    // cx.assert_editor_state(indoc! {"
21093    //     def main():
21094    //         try:
21095    //             i = 2
21096    //         except:
21097    //             for i in range(n):
21098    //                 pass
21099    //         else:ˇ
21100    // "});
21101
21102    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21103    // cx.set_state(indoc! {"
21104    //     def main():
21105    //         try:
21106    //             i = 2
21107    //         except:
21108    //             j = 2
21109    //         else:
21110    //             for i in range(n):
21111    //                 pass
21112    //             ˇ
21113    // "});
21114    // cx.update_editor(|editor, window, cx| {
21115    //     editor.handle_input("finally:", window, cx);
21116    // });
21117    // cx.assert_editor_state(indoc! {"
21118    //     def main():
21119    //         try:
21120    //             i = 2
21121    //         except:
21122    //             j = 2
21123    //         else:
21124    //             for i in range(n):
21125    //                 pass
21126    //         finally:ˇ
21127    // "});
21128
21129    // test `else` stays at correct indent when typed after `for` block
21130    cx.set_state(indoc! {"
21131        def main():
21132            for i in range(10):
21133                if i == 3:
21134                    break
21135            ˇ
21136    "});
21137    cx.update_editor(|editor, window, cx| {
21138        editor.handle_input("else:", window, cx);
21139    });
21140    cx.assert_editor_state(indoc! {"
21141        def main():
21142            for i in range(10):
21143                if i == 3:
21144                    break
21145            else:ˇ
21146    "});
21147
21148    // test does not outdent on typing after line with square brackets
21149    cx.set_state(indoc! {"
21150        def f() -> list[str]:
21151            ˇ
21152    "});
21153    cx.update_editor(|editor, window, cx| {
21154        editor.handle_input("a", window, cx);
21155    });
21156    cx.assert_editor_state(indoc! {"
21157        def f() -> list[str]:
2115821159    "});
21160}
21161
21162#[gpui::test]
21163async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21164    init_test(cx, |_| {});
21165    update_test_language_settings(cx, |settings| {
21166        settings.defaults.extend_comment_on_newline = Some(false);
21167    });
21168    let mut cx = EditorTestContext::new(cx).await;
21169    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21170    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21171
21172    // test correct indent after newline on comment
21173    cx.set_state(indoc! {"
21174        # COMMENT:ˇ
21175    "});
21176    cx.update_editor(|editor, window, cx| {
21177        editor.newline(&Newline, window, cx);
21178    });
21179    cx.assert_editor_state(indoc! {"
21180        # COMMENT:
21181        ˇ
21182    "});
21183
21184    // test correct indent after newline in brackets
21185    cx.set_state(indoc! {"
21186        {ˇ}
21187    "});
21188    cx.update_editor(|editor, window, cx| {
21189        editor.newline(&Newline, window, cx);
21190    });
21191    cx.run_until_parked();
21192    cx.assert_editor_state(indoc! {"
21193        {
21194            ˇ
21195        }
21196    "});
21197
21198    cx.set_state(indoc! {"
21199        (ˇ)
21200    "});
21201    cx.update_editor(|editor, window, cx| {
21202        editor.newline(&Newline, window, cx);
21203    });
21204    cx.run_until_parked();
21205    cx.assert_editor_state(indoc! {"
21206        (
21207            ˇ
21208        )
21209    "});
21210
21211    // do not indent after empty lists or dictionaries
21212    cx.set_state(indoc! {"
21213        a = []ˇ
21214    "});
21215    cx.update_editor(|editor, window, cx| {
21216        editor.newline(&Newline, window, cx);
21217    });
21218    cx.run_until_parked();
21219    cx.assert_editor_state(indoc! {"
21220        a = []
21221        ˇ
21222    "});
21223}
21224
21225fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21226    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21227    point..point
21228}
21229
21230fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21231    let (text, ranges) = marked_text_ranges(marked_text, true);
21232    assert_eq!(editor.text(cx), text);
21233    assert_eq!(
21234        editor.selections.ranges(cx),
21235        ranges,
21236        "Assert selections are {}",
21237        marked_text
21238    );
21239}
21240
21241pub fn handle_signature_help_request(
21242    cx: &mut EditorLspTestContext,
21243    mocked_response: lsp::SignatureHelp,
21244) -> impl Future<Output = ()> + use<> {
21245    let mut request =
21246        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21247            let mocked_response = mocked_response.clone();
21248            async move { Ok(Some(mocked_response)) }
21249        });
21250
21251    async move {
21252        request.next().await;
21253    }
21254}
21255
21256#[track_caller]
21257pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21258    cx.update_editor(|editor, _, _| {
21259        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21260            let entries = menu.entries.borrow();
21261            let entries = entries
21262                .iter()
21263                .map(|entry| entry.string.as_str())
21264                .collect::<Vec<_>>();
21265            assert_eq!(entries, expected);
21266        } else {
21267            panic!("Expected completions menu");
21268        }
21269    });
21270}
21271
21272/// Handle completion request passing a marked string specifying where the completion
21273/// should be triggered from using '|' character, what range should be replaced, and what completions
21274/// should be returned using '<' and '>' to delimit the range.
21275///
21276/// Also see `handle_completion_request_with_insert_and_replace`.
21277#[track_caller]
21278pub fn handle_completion_request(
21279    marked_string: &str,
21280    completions: Vec<&'static str>,
21281    is_incomplete: bool,
21282    counter: Arc<AtomicUsize>,
21283    cx: &mut EditorLspTestContext,
21284) -> impl Future<Output = ()> {
21285    let complete_from_marker: TextRangeMarker = '|'.into();
21286    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21287    let (_, mut marked_ranges) = marked_text_ranges_by(
21288        marked_string,
21289        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21290    );
21291
21292    let complete_from_position =
21293        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21294    let replace_range =
21295        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21296
21297    let mut request =
21298        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21299            let completions = completions.clone();
21300            counter.fetch_add(1, atomic::Ordering::Release);
21301            async move {
21302                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21303                assert_eq!(
21304                    params.text_document_position.position,
21305                    complete_from_position
21306                );
21307                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21308                    is_incomplete: is_incomplete,
21309                    item_defaults: None,
21310                    items: completions
21311                        .iter()
21312                        .map(|completion_text| lsp::CompletionItem {
21313                            label: completion_text.to_string(),
21314                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21315                                range: replace_range,
21316                                new_text: completion_text.to_string(),
21317                            })),
21318                            ..Default::default()
21319                        })
21320                        .collect(),
21321                })))
21322            }
21323        });
21324
21325    async move {
21326        request.next().await;
21327    }
21328}
21329
21330/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21331/// given instead, which also contains an `insert` range.
21332///
21333/// This function uses markers to define ranges:
21334/// - `|` marks the cursor position
21335/// - `<>` marks the replace range
21336/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21337pub fn handle_completion_request_with_insert_and_replace(
21338    cx: &mut EditorLspTestContext,
21339    marked_string: &str,
21340    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21341    counter: Arc<AtomicUsize>,
21342) -> impl Future<Output = ()> {
21343    let complete_from_marker: TextRangeMarker = '|'.into();
21344    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21345    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21346
21347    let (_, mut marked_ranges) = marked_text_ranges_by(
21348        marked_string,
21349        vec![
21350            complete_from_marker.clone(),
21351            replace_range_marker.clone(),
21352            insert_range_marker.clone(),
21353        ],
21354    );
21355
21356    let complete_from_position =
21357        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21358    let replace_range =
21359        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21360
21361    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21362        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21363        _ => lsp::Range {
21364            start: replace_range.start,
21365            end: complete_from_position,
21366        },
21367    };
21368
21369    let mut request =
21370        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21371            let completions = completions.clone();
21372            counter.fetch_add(1, atomic::Ordering::Release);
21373            async move {
21374                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21375                assert_eq!(
21376                    params.text_document_position.position, complete_from_position,
21377                    "marker `|` position doesn't match",
21378                );
21379                Ok(Some(lsp::CompletionResponse::Array(
21380                    completions
21381                        .iter()
21382                        .map(|(label, new_text)| lsp::CompletionItem {
21383                            label: label.to_string(),
21384                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21385                                lsp::InsertReplaceEdit {
21386                                    insert: insert_range,
21387                                    replace: replace_range,
21388                                    new_text: new_text.to_string(),
21389                                },
21390                            )),
21391                            ..Default::default()
21392                        })
21393                        .collect(),
21394                )))
21395            }
21396        });
21397
21398    async move {
21399        request.next().await;
21400    }
21401}
21402
21403fn handle_resolve_completion_request(
21404    cx: &mut EditorLspTestContext,
21405    edits: Option<Vec<(&'static str, &'static str)>>,
21406) -> impl Future<Output = ()> {
21407    let edits = edits.map(|edits| {
21408        edits
21409            .iter()
21410            .map(|(marked_string, new_text)| {
21411                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21412                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21413                lsp::TextEdit::new(replace_range, new_text.to_string())
21414            })
21415            .collect::<Vec<_>>()
21416    });
21417
21418    let mut request =
21419        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21420            let edits = edits.clone();
21421            async move {
21422                Ok(lsp::CompletionItem {
21423                    additional_text_edits: edits,
21424                    ..Default::default()
21425                })
21426            }
21427        });
21428
21429    async move {
21430        request.next().await;
21431    }
21432}
21433
21434pub(crate) fn update_test_language_settings(
21435    cx: &mut TestAppContext,
21436    f: impl Fn(&mut AllLanguageSettingsContent),
21437) {
21438    cx.update(|cx| {
21439        SettingsStore::update_global(cx, |store, cx| {
21440            store.update_user_settings::<AllLanguageSettings>(cx, f);
21441        });
21442    });
21443}
21444
21445pub(crate) fn update_test_project_settings(
21446    cx: &mut TestAppContext,
21447    f: impl Fn(&mut ProjectSettings),
21448) {
21449    cx.update(|cx| {
21450        SettingsStore::update_global(cx, |store, cx| {
21451            store.update_user_settings::<ProjectSettings>(cx, f);
21452        });
21453    });
21454}
21455
21456pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21457    cx.update(|cx| {
21458        assets::Assets.load_test_fonts(cx);
21459        let store = SettingsStore::test(cx);
21460        cx.set_global(store);
21461        theme::init(theme::LoadThemes::JustBase, cx);
21462        release_channel::init(SemanticVersion::default(), cx);
21463        client::init_settings(cx);
21464        language::init(cx);
21465        Project::init_settings(cx);
21466        workspace::init_settings(cx);
21467        crate::init(cx);
21468    });
21469
21470    update_test_language_settings(cx, f);
21471}
21472
21473#[track_caller]
21474fn assert_hunk_revert(
21475    not_reverted_text_with_selections: &str,
21476    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21477    expected_reverted_text_with_selections: &str,
21478    base_text: &str,
21479    cx: &mut EditorLspTestContext,
21480) {
21481    cx.set_state(not_reverted_text_with_selections);
21482    cx.set_head_text(base_text);
21483    cx.executor().run_until_parked();
21484
21485    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21486        let snapshot = editor.snapshot(window, cx);
21487        let reverted_hunk_statuses = snapshot
21488            .buffer_snapshot
21489            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21490            .map(|hunk| hunk.status().kind)
21491            .collect::<Vec<_>>();
21492
21493        editor.git_restore(&Default::default(), window, cx);
21494        reverted_hunk_statuses
21495    });
21496    cx.executor().run_until_parked();
21497    cx.assert_editor_state(expected_reverted_text_with_selections);
21498    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21499}