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.clone(),
 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    assert_rewrap(
 5178        indoc! {"
 5179            «ˇone one one one one one one one one one one one one one one one one one one one one one one one one
 5180
 5181            two»
 5182
 5183            three
 5184
 5185            «ˇ\t
 5186
 5187            four four four four four four four four four four four four four four four four four four four four»
 5188
 5189            «ˇfive five five five five five five five five five five five five five five five five five five five
 5190            \t»
 5191            six six six six six six six six six six six six six six six six six six six six six six six six six
 5192        "},
 5193        indoc! {"
 5194            «ˇone one one one one one one one one one one one one one one one one one one one
 5195            one one one one one
 5196
 5197            two»
 5198
 5199            three
 5200
 5201            «ˇ\t
 5202
 5203            four four four four four four four four four four four four four four four four
 5204            four four four four»
 5205
 5206            «ˇfive five five five five five five five five five five five five five five five
 5207            five five five five
 5208            \t»
 5209            six six six six six six six six six six six six six six six six six six six six six six six six six
 5210        "},
 5211        plaintext_language.clone(),
 5212        &mut cx,
 5213    );
 5214
 5215    assert_rewrap(
 5216        indoc! {"
 5217            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5218            //ˇ
 5219            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5220            //ˇ short short short
 5221            int main(void) {
 5222                return 17;
 5223            }
 5224        "},
 5225        indoc! {"
 5226            //ˇ long long long long long long long long long long long long long long long
 5227            // long long long long long long long long long long long long long
 5228            //ˇ
 5229            //ˇ long long long long long long long long long long long long long long long
 5230            //ˇ long long long long long long long long long long long long long short short
 5231            // short
 5232            int main(void) {
 5233                return 17;
 5234            }
 5235        "},
 5236        language_with_c_comments,
 5237        &mut cx,
 5238    );
 5239
 5240    #[track_caller]
 5241    fn assert_rewrap(
 5242        unwrapped_text: &str,
 5243        wrapped_text: &str,
 5244        language: Arc<Language>,
 5245        cx: &mut EditorTestContext,
 5246    ) {
 5247        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5248        cx.set_state(unwrapped_text);
 5249        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5250        cx.assert_editor_state(wrapped_text);
 5251    }
 5252}
 5253
 5254#[gpui::test]
 5255async fn test_hard_wrap(cx: &mut TestAppContext) {
 5256    init_test(cx, |_| {});
 5257    let mut cx = EditorTestContext::new(cx).await;
 5258
 5259    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5260    cx.update_editor(|editor, _, cx| {
 5261        editor.set_hard_wrap(Some(14), cx);
 5262    });
 5263
 5264    cx.set_state(indoc!(
 5265        "
 5266        one two three ˇ
 5267        "
 5268    ));
 5269    cx.simulate_input("four");
 5270    cx.run_until_parked();
 5271
 5272    cx.assert_editor_state(indoc!(
 5273        "
 5274        one two three
 5275        fourˇ
 5276        "
 5277    ));
 5278
 5279    cx.update_editor(|editor, window, cx| {
 5280        editor.newline(&Default::default(), window, cx);
 5281    });
 5282    cx.run_until_parked();
 5283    cx.assert_editor_state(indoc!(
 5284        "
 5285        one two three
 5286        four
 5287        ˇ
 5288        "
 5289    ));
 5290
 5291    cx.simulate_input("five");
 5292    cx.run_until_parked();
 5293    cx.assert_editor_state(indoc!(
 5294        "
 5295        one two three
 5296        four
 5297        fiveˇ
 5298        "
 5299    ));
 5300
 5301    cx.update_editor(|editor, window, cx| {
 5302        editor.newline(&Default::default(), window, cx);
 5303    });
 5304    cx.run_until_parked();
 5305    cx.simulate_input("# ");
 5306    cx.run_until_parked();
 5307    cx.assert_editor_state(indoc!(
 5308        "
 5309        one two three
 5310        four
 5311        five
 5312        # ˇ
 5313        "
 5314    ));
 5315
 5316    cx.update_editor(|editor, window, cx| {
 5317        editor.newline(&Default::default(), window, cx);
 5318    });
 5319    cx.run_until_parked();
 5320    cx.assert_editor_state(indoc!(
 5321        "
 5322        one two three
 5323        four
 5324        five
 5325        #\x20
 5326 5327        "
 5328    ));
 5329
 5330    cx.simulate_input(" 6");
 5331    cx.run_until_parked();
 5332    cx.assert_editor_state(indoc!(
 5333        "
 5334        one two three
 5335        four
 5336        five
 5337        #
 5338        # 6ˇ
 5339        "
 5340    ));
 5341}
 5342
 5343#[gpui::test]
 5344async fn test_clipboard(cx: &mut TestAppContext) {
 5345    init_test(cx, |_| {});
 5346
 5347    let mut cx = EditorTestContext::new(cx).await;
 5348
 5349    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5350    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5351    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5352
 5353    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5354    cx.set_state("two ˇfour ˇsix ˇ");
 5355    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5356    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5357
 5358    // Paste again but with only two cursors. Since the number of cursors doesn't
 5359    // match the number of slices in the clipboard, the entire clipboard text
 5360    // is pasted at each cursor.
 5361    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5362    cx.update_editor(|e, window, cx| {
 5363        e.handle_input("( ", window, cx);
 5364        e.paste(&Paste, window, cx);
 5365        e.handle_input(") ", window, cx);
 5366    });
 5367    cx.assert_editor_state(
 5368        &([
 5369            "( one✅ ",
 5370            "three ",
 5371            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5372            "three ",
 5373            "five ) ˇ",
 5374        ]
 5375        .join("\n")),
 5376    );
 5377
 5378    // Cut with three selections, one of which is full-line.
 5379    cx.set_state(indoc! {"
 5380        1«2ˇ»3
 5381        4ˇ567
 5382        «8ˇ»9"});
 5383    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5384    cx.assert_editor_state(indoc! {"
 5385        1ˇ3
 5386        ˇ9"});
 5387
 5388    // Paste with three selections, noticing how the copied selection that was full-line
 5389    // gets inserted before the second cursor.
 5390    cx.set_state(indoc! {"
 5391        1ˇ3
 5392 5393        «oˇ»ne"});
 5394    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5395    cx.assert_editor_state(indoc! {"
 5396        12ˇ3
 5397        4567
 5398 5399        8ˇne"});
 5400
 5401    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5402    cx.set_state(indoc! {"
 5403        The quick brown
 5404        fox juˇmps over
 5405        the lazy dog"});
 5406    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5407    assert_eq!(
 5408        cx.read_from_clipboard()
 5409            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5410        Some("fox jumps over\n".to_string())
 5411    );
 5412
 5413    // Paste with three selections, noticing how the copied full-line selection is inserted
 5414    // before the empty selections but replaces the selection that is non-empty.
 5415    cx.set_state(indoc! {"
 5416        Tˇhe quick brown
 5417        «foˇ»x jumps over
 5418        tˇhe lazy dog"});
 5419    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5420    cx.assert_editor_state(indoc! {"
 5421        fox jumps over
 5422        Tˇhe quick brown
 5423        fox jumps over
 5424        ˇx jumps over
 5425        fox jumps over
 5426        tˇhe lazy dog"});
 5427}
 5428
 5429#[gpui::test]
 5430async fn test_copy_trim(cx: &mut TestAppContext) {
 5431    init_test(cx, |_| {});
 5432
 5433    let mut cx = EditorTestContext::new(cx).await;
 5434    cx.set_state(
 5435        r#"            «for selection in selections.iter() {
 5436            let mut start = selection.start;
 5437            let mut end = selection.end;
 5438            let is_entire_line = selection.is_empty();
 5439            if is_entire_line {
 5440                start = Point::new(start.row, 0);ˇ»
 5441                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5442            }
 5443        "#,
 5444    );
 5445    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5446    assert_eq!(
 5447        cx.read_from_clipboard()
 5448            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5449        Some(
 5450            "for selection in selections.iter() {
 5451            let mut start = selection.start;
 5452            let mut end = selection.end;
 5453            let is_entire_line = selection.is_empty();
 5454            if is_entire_line {
 5455                start = Point::new(start.row, 0);"
 5456                .to_string()
 5457        ),
 5458        "Regular copying preserves all indentation selected",
 5459    );
 5460    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5461    assert_eq!(
 5462        cx.read_from_clipboard()
 5463            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5464        Some(
 5465            "for selection in selections.iter() {
 5466let mut start = selection.start;
 5467let mut end = selection.end;
 5468let is_entire_line = selection.is_empty();
 5469if is_entire_line {
 5470    start = Point::new(start.row, 0);"
 5471                .to_string()
 5472        ),
 5473        "Copying with stripping should strip all leading whitespaces"
 5474    );
 5475
 5476    cx.set_state(
 5477        r#"       «     for selection in selections.iter() {
 5478            let mut start = selection.start;
 5479            let mut end = selection.end;
 5480            let is_entire_line = selection.is_empty();
 5481            if is_entire_line {
 5482                start = Point::new(start.row, 0);ˇ»
 5483                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5484            }
 5485        "#,
 5486    );
 5487    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5488    assert_eq!(
 5489        cx.read_from_clipboard()
 5490            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5491        Some(
 5492            "     for selection in selections.iter() {
 5493            let mut start = selection.start;
 5494            let mut end = selection.end;
 5495            let is_entire_line = selection.is_empty();
 5496            if is_entire_line {
 5497                start = Point::new(start.row, 0);"
 5498                .to_string()
 5499        ),
 5500        "Regular copying preserves all indentation selected",
 5501    );
 5502    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5503    assert_eq!(
 5504        cx.read_from_clipboard()
 5505            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5506        Some(
 5507            "for selection in selections.iter() {
 5508let mut start = selection.start;
 5509let mut end = selection.end;
 5510let is_entire_line = selection.is_empty();
 5511if is_entire_line {
 5512    start = Point::new(start.row, 0);"
 5513                .to_string()
 5514        ),
 5515        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5516    );
 5517
 5518    cx.set_state(
 5519        r#"       «ˇ     for selection in selections.iter() {
 5520            let mut start = selection.start;
 5521            let mut end = selection.end;
 5522            let is_entire_line = selection.is_empty();
 5523            if is_entire_line {
 5524                start = Point::new(start.row, 0);»
 5525                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5526            }
 5527        "#,
 5528    );
 5529    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5530    assert_eq!(
 5531        cx.read_from_clipboard()
 5532            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5533        Some(
 5534            "     for selection in selections.iter() {
 5535            let mut start = selection.start;
 5536            let mut end = selection.end;
 5537            let is_entire_line = selection.is_empty();
 5538            if is_entire_line {
 5539                start = Point::new(start.row, 0);"
 5540                .to_string()
 5541        ),
 5542        "Regular copying for reverse selection works the same",
 5543    );
 5544    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5545    assert_eq!(
 5546        cx.read_from_clipboard()
 5547            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5548        Some(
 5549            "for selection in selections.iter() {
 5550let mut start = selection.start;
 5551let mut end = selection.end;
 5552let is_entire_line = selection.is_empty();
 5553if is_entire_line {
 5554    start = Point::new(start.row, 0);"
 5555                .to_string()
 5556        ),
 5557        "Copying with stripping for reverse selection works the same"
 5558    );
 5559
 5560    cx.set_state(
 5561        r#"            for selection «in selections.iter() {
 5562            let mut start = selection.start;
 5563            let mut end = selection.end;
 5564            let is_entire_line = selection.is_empty();
 5565            if is_entire_line {
 5566                start = Point::new(start.row, 0);ˇ»
 5567                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5568            }
 5569        "#,
 5570    );
 5571    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5572    assert_eq!(
 5573        cx.read_from_clipboard()
 5574            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5575        Some(
 5576            "in selections.iter() {
 5577            let mut start = selection.start;
 5578            let mut end = selection.end;
 5579            let is_entire_line = selection.is_empty();
 5580            if is_entire_line {
 5581                start = Point::new(start.row, 0);"
 5582                .to_string()
 5583        ),
 5584        "When selecting past the indent, the copying works as usual",
 5585    );
 5586    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5587    assert_eq!(
 5588        cx.read_from_clipboard()
 5589            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5590        Some(
 5591            "in selections.iter() {
 5592            let mut start = selection.start;
 5593            let mut end = selection.end;
 5594            let is_entire_line = selection.is_empty();
 5595            if is_entire_line {
 5596                start = Point::new(start.row, 0);"
 5597                .to_string()
 5598        ),
 5599        "When selecting past the indent, nothing is trimmed"
 5600    );
 5601
 5602    cx.set_state(
 5603        r#"            «for selection in selections.iter() {
 5604            let mut start = selection.start;
 5605
 5606            let mut end = selection.end;
 5607            let is_entire_line = selection.is_empty();
 5608            if is_entire_line {
 5609                start = Point::new(start.row, 0);
 5610ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5611            }
 5612        "#,
 5613    );
 5614    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5615    assert_eq!(
 5616        cx.read_from_clipboard()
 5617            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5618        Some(
 5619            "for selection in selections.iter() {
 5620let mut start = selection.start;
 5621
 5622let mut end = selection.end;
 5623let is_entire_line = selection.is_empty();
 5624if is_entire_line {
 5625    start = Point::new(start.row, 0);
 5626"
 5627            .to_string()
 5628        ),
 5629        "Copying with stripping should ignore empty lines"
 5630    );
 5631}
 5632
 5633#[gpui::test]
 5634async fn test_paste_multiline(cx: &mut TestAppContext) {
 5635    init_test(cx, |_| {});
 5636
 5637    let mut cx = EditorTestContext::new(cx).await;
 5638    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5639
 5640    // Cut an indented block, without the leading whitespace.
 5641    cx.set_state(indoc! {"
 5642        const a: B = (
 5643            c(),
 5644            «d(
 5645                e,
 5646                f
 5647            )ˇ»
 5648        );
 5649    "});
 5650    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5651    cx.assert_editor_state(indoc! {"
 5652        const a: B = (
 5653            c(),
 5654            ˇ
 5655        );
 5656    "});
 5657
 5658    // Paste it at the same position.
 5659    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5660    cx.assert_editor_state(indoc! {"
 5661        const a: B = (
 5662            c(),
 5663            d(
 5664                e,
 5665                f
 5666 5667        );
 5668    "});
 5669
 5670    // Paste it at a line with a lower indent level.
 5671    cx.set_state(indoc! {"
 5672        ˇ
 5673        const a: B = (
 5674            c(),
 5675        );
 5676    "});
 5677    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5678    cx.assert_editor_state(indoc! {"
 5679        d(
 5680            e,
 5681            f
 5682 5683        const a: B = (
 5684            c(),
 5685        );
 5686    "});
 5687
 5688    // Cut an indented block, with the leading whitespace.
 5689    cx.set_state(indoc! {"
 5690        const a: B = (
 5691            c(),
 5692        «    d(
 5693                e,
 5694                f
 5695            )
 5696        ˇ»);
 5697    "});
 5698    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5699    cx.assert_editor_state(indoc! {"
 5700        const a: B = (
 5701            c(),
 5702        ˇ);
 5703    "});
 5704
 5705    // Paste it at the same position.
 5706    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5707    cx.assert_editor_state(indoc! {"
 5708        const a: B = (
 5709            c(),
 5710            d(
 5711                e,
 5712                f
 5713            )
 5714        ˇ);
 5715    "});
 5716
 5717    // Paste it at a line with a higher indent level.
 5718    cx.set_state(indoc! {"
 5719        const a: B = (
 5720            c(),
 5721            d(
 5722                e,
 5723 5724            )
 5725        );
 5726    "});
 5727    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5728    cx.assert_editor_state(indoc! {"
 5729        const a: B = (
 5730            c(),
 5731            d(
 5732                e,
 5733                f    d(
 5734                    e,
 5735                    f
 5736                )
 5737        ˇ
 5738            )
 5739        );
 5740    "});
 5741
 5742    // Copy an indented block, starting mid-line
 5743    cx.set_state(indoc! {"
 5744        const a: B = (
 5745            c(),
 5746            somethin«g(
 5747                e,
 5748                f
 5749            )ˇ»
 5750        );
 5751    "});
 5752    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5753
 5754    // Paste it on a line with a lower indent level
 5755    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5756    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5757    cx.assert_editor_state(indoc! {"
 5758        const a: B = (
 5759            c(),
 5760            something(
 5761                e,
 5762                f
 5763            )
 5764        );
 5765        g(
 5766            e,
 5767            f
 5768"});
 5769}
 5770
 5771#[gpui::test]
 5772async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5773    init_test(cx, |_| {});
 5774
 5775    cx.write_to_clipboard(ClipboardItem::new_string(
 5776        "    d(\n        e\n    );\n".into(),
 5777    ));
 5778
 5779    let mut cx = EditorTestContext::new(cx).await;
 5780    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5781
 5782    cx.set_state(indoc! {"
 5783        fn a() {
 5784            b();
 5785            if c() {
 5786                ˇ
 5787            }
 5788        }
 5789    "});
 5790
 5791    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5792    cx.assert_editor_state(indoc! {"
 5793        fn a() {
 5794            b();
 5795            if c() {
 5796                d(
 5797                    e
 5798                );
 5799        ˇ
 5800            }
 5801        }
 5802    "});
 5803
 5804    cx.set_state(indoc! {"
 5805        fn a() {
 5806            b();
 5807            ˇ
 5808        }
 5809    "});
 5810
 5811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5812    cx.assert_editor_state(indoc! {"
 5813        fn a() {
 5814            b();
 5815            d(
 5816                e
 5817            );
 5818        ˇ
 5819        }
 5820    "});
 5821}
 5822
 5823#[gpui::test]
 5824fn test_select_all(cx: &mut TestAppContext) {
 5825    init_test(cx, |_| {});
 5826
 5827    let editor = cx.add_window(|window, cx| {
 5828        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5829        build_editor(buffer, window, cx)
 5830    });
 5831    _ = editor.update(cx, |editor, window, cx| {
 5832        editor.select_all(&SelectAll, window, cx);
 5833        assert_eq!(
 5834            editor.selections.display_ranges(cx),
 5835            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5836        );
 5837    });
 5838}
 5839
 5840#[gpui::test]
 5841fn test_select_line(cx: &mut TestAppContext) {
 5842    init_test(cx, |_| {});
 5843
 5844    let editor = cx.add_window(|window, cx| {
 5845        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5846        build_editor(buffer, window, cx)
 5847    });
 5848    _ = editor.update(cx, |editor, window, cx| {
 5849        editor.change_selections(None, window, cx, |s| {
 5850            s.select_display_ranges([
 5851                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5852                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5853                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5854                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5855            ])
 5856        });
 5857        editor.select_line(&SelectLine, window, cx);
 5858        assert_eq!(
 5859            editor.selections.display_ranges(cx),
 5860            vec![
 5861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5862                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5863            ]
 5864        );
 5865    });
 5866
 5867    _ = editor.update(cx, |editor, window, cx| {
 5868        editor.select_line(&SelectLine, window, cx);
 5869        assert_eq!(
 5870            editor.selections.display_ranges(cx),
 5871            vec![
 5872                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5873                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5874            ]
 5875        );
 5876    });
 5877
 5878    _ = editor.update(cx, |editor, window, cx| {
 5879        editor.select_line(&SelectLine, window, cx);
 5880        assert_eq!(
 5881            editor.selections.display_ranges(cx),
 5882            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5883        );
 5884    });
 5885}
 5886
 5887#[gpui::test]
 5888async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890    let mut cx = EditorTestContext::new(cx).await;
 5891
 5892    #[track_caller]
 5893    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5894        cx.set_state(initial_state);
 5895        cx.update_editor(|e, window, cx| {
 5896            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5897        });
 5898        cx.assert_editor_state(expected_state);
 5899    }
 5900
 5901    // Selection starts and ends at the middle of lines, left-to-right
 5902    test(
 5903        &mut cx,
 5904        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5905        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5906    );
 5907    // Same thing, right-to-left
 5908    test(
 5909        &mut cx,
 5910        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5911        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5912    );
 5913
 5914    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5915    test(
 5916        &mut cx,
 5917        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5918        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5919    );
 5920    // Same thing, right-to-left
 5921    test(
 5922        &mut cx,
 5923        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5924        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5925    );
 5926
 5927    // Whole buffer, left-to-right, last line ends with newline
 5928    test(
 5929        &mut cx,
 5930        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5931        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5932    );
 5933    // Same thing, right-to-left
 5934    test(
 5935        &mut cx,
 5936        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5937        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5938    );
 5939
 5940    // Starts at the end of a line, ends at the start of another
 5941    test(
 5942        &mut cx,
 5943        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5944        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5945    );
 5946}
 5947
 5948#[gpui::test]
 5949async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5950    init_test(cx, |_| {});
 5951
 5952    let editor = cx.add_window(|window, cx| {
 5953        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5954        build_editor(buffer, window, cx)
 5955    });
 5956
 5957    // setup
 5958    _ = editor.update(cx, |editor, window, cx| {
 5959        editor.fold_creases(
 5960            vec![
 5961                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5962                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5963                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5964            ],
 5965            true,
 5966            window,
 5967            cx,
 5968        );
 5969        assert_eq!(
 5970            editor.display_text(cx),
 5971            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5972        );
 5973    });
 5974
 5975    _ = editor.update(cx, |editor, window, cx| {
 5976        editor.change_selections(None, window, cx, |s| {
 5977            s.select_display_ranges([
 5978                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5979                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5980                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5981                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5982            ])
 5983        });
 5984        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5985        assert_eq!(
 5986            editor.display_text(cx),
 5987            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5988        );
 5989    });
 5990    EditorTestContext::for_editor(editor, cx)
 5991        .await
 5992        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5993
 5994    _ = editor.update(cx, |editor, window, cx| {
 5995        editor.change_selections(None, window, cx, |s| {
 5996            s.select_display_ranges([
 5997                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5998            ])
 5999        });
 6000        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6001        assert_eq!(
 6002            editor.display_text(cx),
 6003            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6004        );
 6005        assert_eq!(
 6006            editor.selections.display_ranges(cx),
 6007            [
 6008                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6009                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6010                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6011                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6012                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6013                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6014                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6015            ]
 6016        );
 6017    });
 6018    EditorTestContext::for_editor(editor, cx)
 6019        .await
 6020        .assert_editor_state(
 6021            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6022        );
 6023}
 6024
 6025#[gpui::test]
 6026async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6027    init_test(cx, |_| {});
 6028
 6029    let mut cx = EditorTestContext::new(cx).await;
 6030
 6031    cx.set_state(indoc!(
 6032        r#"abc
 6033           defˇghi
 6034
 6035           jk
 6036           nlmo
 6037           "#
 6038    ));
 6039
 6040    cx.update_editor(|editor, window, cx| {
 6041        editor.add_selection_above(&Default::default(), window, cx);
 6042    });
 6043
 6044    cx.assert_editor_state(indoc!(
 6045        r#"abcˇ
 6046           defˇghi
 6047
 6048           jk
 6049           nlmo
 6050           "#
 6051    ));
 6052
 6053    cx.update_editor(|editor, window, cx| {
 6054        editor.add_selection_above(&Default::default(), window, cx);
 6055    });
 6056
 6057    cx.assert_editor_state(indoc!(
 6058        r#"abcˇ
 6059            defˇghi
 6060
 6061            jk
 6062            nlmo
 6063            "#
 6064    ));
 6065
 6066    cx.update_editor(|editor, window, cx| {
 6067        editor.add_selection_below(&Default::default(), window, cx);
 6068    });
 6069
 6070    cx.assert_editor_state(indoc!(
 6071        r#"abc
 6072           defˇghi
 6073
 6074           jk
 6075           nlmo
 6076           "#
 6077    ));
 6078
 6079    cx.update_editor(|editor, window, cx| {
 6080        editor.undo_selection(&Default::default(), window, cx);
 6081    });
 6082
 6083    cx.assert_editor_state(indoc!(
 6084        r#"abcˇ
 6085           defˇghi
 6086
 6087           jk
 6088           nlmo
 6089           "#
 6090    ));
 6091
 6092    cx.update_editor(|editor, window, cx| {
 6093        editor.redo_selection(&Default::default(), window, cx);
 6094    });
 6095
 6096    cx.assert_editor_state(indoc!(
 6097        r#"abc
 6098           defˇghi
 6099
 6100           jk
 6101           nlmo
 6102           "#
 6103    ));
 6104
 6105    cx.update_editor(|editor, window, cx| {
 6106        editor.add_selection_below(&Default::default(), window, cx);
 6107    });
 6108
 6109    cx.assert_editor_state(indoc!(
 6110        r#"abc
 6111           defˇghi
 6112           ˇ
 6113           jk
 6114           nlmo
 6115           "#
 6116    ));
 6117
 6118    cx.update_editor(|editor, window, cx| {
 6119        editor.add_selection_below(&Default::default(), window, cx);
 6120    });
 6121
 6122    cx.assert_editor_state(indoc!(
 6123        r#"abc
 6124           defˇghi
 6125           ˇ
 6126           jkˇ
 6127           nlmo
 6128           "#
 6129    ));
 6130
 6131    cx.update_editor(|editor, window, cx| {
 6132        editor.add_selection_below(&Default::default(), window, cx);
 6133    });
 6134
 6135    cx.assert_editor_state(indoc!(
 6136        r#"abc
 6137           defˇghi
 6138           ˇ
 6139           jkˇ
 6140           nlmˇo
 6141           "#
 6142    ));
 6143
 6144    cx.update_editor(|editor, window, cx| {
 6145        editor.add_selection_below(&Default::default(), window, cx);
 6146    });
 6147
 6148    cx.assert_editor_state(indoc!(
 6149        r#"abc
 6150           defˇghi
 6151           ˇ
 6152           jkˇ
 6153           nlmˇo
 6154           ˇ"#
 6155    ));
 6156
 6157    // change selections
 6158    cx.set_state(indoc!(
 6159        r#"abc
 6160           def«ˇg»hi
 6161
 6162           jk
 6163           nlmo
 6164           "#
 6165    ));
 6166
 6167    cx.update_editor(|editor, window, cx| {
 6168        editor.add_selection_below(&Default::default(), window, cx);
 6169    });
 6170
 6171    cx.assert_editor_state(indoc!(
 6172        r#"abc
 6173           def«ˇg»hi
 6174
 6175           jk
 6176           nlm«ˇo»
 6177           "#
 6178    ));
 6179
 6180    cx.update_editor(|editor, window, cx| {
 6181        editor.add_selection_below(&Default::default(), window, cx);
 6182    });
 6183
 6184    cx.assert_editor_state(indoc!(
 6185        r#"abc
 6186           def«ˇg»hi
 6187
 6188           jk
 6189           nlm«ˇo»
 6190           "#
 6191    ));
 6192
 6193    cx.update_editor(|editor, window, cx| {
 6194        editor.add_selection_above(&Default::default(), window, cx);
 6195    });
 6196
 6197    cx.assert_editor_state(indoc!(
 6198        r#"abc
 6199           def«ˇg»hi
 6200
 6201           jk
 6202           nlmo
 6203           "#
 6204    ));
 6205
 6206    cx.update_editor(|editor, window, cx| {
 6207        editor.add_selection_above(&Default::default(), window, cx);
 6208    });
 6209
 6210    cx.assert_editor_state(indoc!(
 6211        r#"abc
 6212           def«ˇg»hi
 6213
 6214           jk
 6215           nlmo
 6216           "#
 6217    ));
 6218
 6219    // Change selections again
 6220    cx.set_state(indoc!(
 6221        r#"a«bc
 6222           defgˇ»hi
 6223
 6224           jk
 6225           nlmo
 6226           "#
 6227    ));
 6228
 6229    cx.update_editor(|editor, window, cx| {
 6230        editor.add_selection_below(&Default::default(), window, cx);
 6231    });
 6232
 6233    cx.assert_editor_state(indoc!(
 6234        r#"a«bcˇ»
 6235           d«efgˇ»hi
 6236
 6237           j«kˇ»
 6238           nlmo
 6239           "#
 6240    ));
 6241
 6242    cx.update_editor(|editor, window, cx| {
 6243        editor.add_selection_below(&Default::default(), window, cx);
 6244    });
 6245    cx.assert_editor_state(indoc!(
 6246        r#"a«bcˇ»
 6247           d«efgˇ»hi
 6248
 6249           j«kˇ»
 6250           n«lmoˇ»
 6251           "#
 6252    ));
 6253    cx.update_editor(|editor, window, cx| {
 6254        editor.add_selection_above(&Default::default(), window, cx);
 6255    });
 6256
 6257    cx.assert_editor_state(indoc!(
 6258        r#"a«bcˇ»
 6259           d«efgˇ»hi
 6260
 6261           j«kˇ»
 6262           nlmo
 6263           "#
 6264    ));
 6265
 6266    // Change selections again
 6267    cx.set_state(indoc!(
 6268        r#"abc
 6269           d«ˇefghi
 6270
 6271           jk
 6272           nlm»o
 6273           "#
 6274    ));
 6275
 6276    cx.update_editor(|editor, window, cx| {
 6277        editor.add_selection_above(&Default::default(), window, cx);
 6278    });
 6279
 6280    cx.assert_editor_state(indoc!(
 6281        r#"a«ˇbc»
 6282           d«ˇef»ghi
 6283
 6284           j«ˇk»
 6285           n«ˇlm»o
 6286           "#
 6287    ));
 6288
 6289    cx.update_editor(|editor, window, cx| {
 6290        editor.add_selection_below(&Default::default(), window, cx);
 6291    });
 6292
 6293    cx.assert_editor_state(indoc!(
 6294        r#"abc
 6295           d«ˇef»ghi
 6296
 6297           j«ˇk»
 6298           n«ˇlm»o
 6299           "#
 6300    ));
 6301}
 6302
 6303#[gpui::test]
 6304async fn test_select_next(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306
 6307    let mut cx = EditorTestContext::new(cx).await;
 6308    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6309
 6310    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6311        .unwrap();
 6312    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6313
 6314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6315        .unwrap();
 6316    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6317
 6318    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6319    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6320
 6321    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6322    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6323
 6324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6325        .unwrap();
 6326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6327
 6328    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6329        .unwrap();
 6330    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6331
 6332    // Test selection direction should be preserved
 6333    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6334
 6335    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6336        .unwrap();
 6337    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6338}
 6339
 6340#[gpui::test]
 6341async fn test_select_all_matches(cx: &mut TestAppContext) {
 6342    init_test(cx, |_| {});
 6343
 6344    let mut cx = EditorTestContext::new(cx).await;
 6345
 6346    // Test caret-only selections
 6347    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6348    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6349        .unwrap();
 6350    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6351
 6352    // Test left-to-right selections
 6353    cx.set_state("abc\n«abcˇ»\nabc");
 6354    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6355        .unwrap();
 6356    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6357
 6358    // Test right-to-left selections
 6359    cx.set_state("abc\n«ˇabc»\nabc");
 6360    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6361        .unwrap();
 6362    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6363
 6364    // Test selecting whitespace with caret selection
 6365    cx.set_state("abc\nˇ   abc\nabc");
 6366    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6367        .unwrap();
 6368    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6369
 6370    // Test selecting whitespace with left-to-right selection
 6371    cx.set_state("abc\n«ˇ  »abc\nabc");
 6372    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6373        .unwrap();
 6374    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6375
 6376    // Test no matches with right-to-left selection
 6377    cx.set_state("abc\n«  ˇ»abc\nabc");
 6378    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6379        .unwrap();
 6380    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6381}
 6382
 6383#[gpui::test]
 6384async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6385    init_test(cx, |_| {});
 6386
 6387    let mut cx = EditorTestContext::new(cx).await;
 6388
 6389    let large_body_1 = "\nd".repeat(200);
 6390    let large_body_2 = "\ne".repeat(200);
 6391
 6392    cx.set_state(&format!(
 6393        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6394    ));
 6395    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6396        let scroll_position = editor.scroll_position(cx);
 6397        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6398        scroll_position
 6399    });
 6400
 6401    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6402        .unwrap();
 6403    cx.assert_editor_state(&format!(
 6404        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6405    ));
 6406    let scroll_position_after_selection =
 6407        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6408    assert_eq!(
 6409        initial_scroll_position, scroll_position_after_selection,
 6410        "Scroll position should not change after selecting all matches"
 6411    );
 6412}
 6413
 6414#[gpui::test]
 6415async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6416    init_test(cx, |_| {});
 6417
 6418    let mut cx = EditorLspTestContext::new_rust(
 6419        lsp::ServerCapabilities {
 6420            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6421            ..Default::default()
 6422        },
 6423        cx,
 6424    )
 6425    .await;
 6426
 6427    cx.set_state(indoc! {"
 6428        line 1
 6429        line 2
 6430        linˇe 3
 6431        line 4
 6432        line 5
 6433    "});
 6434
 6435    // Make an edit
 6436    cx.update_editor(|editor, window, cx| {
 6437        editor.handle_input("X", window, cx);
 6438    });
 6439
 6440    // Move cursor to a different position
 6441    cx.update_editor(|editor, window, cx| {
 6442        editor.change_selections(None, window, cx, |s| {
 6443            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6444        });
 6445    });
 6446
 6447    cx.assert_editor_state(indoc! {"
 6448        line 1
 6449        line 2
 6450        linXe 3
 6451        line 4
 6452        liˇne 5
 6453    "});
 6454
 6455    cx.lsp
 6456        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6457            Ok(Some(vec![lsp::TextEdit::new(
 6458                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6459                "PREFIX ".to_string(),
 6460            )]))
 6461        });
 6462
 6463    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6464        .unwrap()
 6465        .await
 6466        .unwrap();
 6467
 6468    cx.assert_editor_state(indoc! {"
 6469        PREFIX line 1
 6470        line 2
 6471        linXe 3
 6472        line 4
 6473        liˇne 5
 6474    "});
 6475
 6476    // Undo formatting
 6477    cx.update_editor(|editor, window, cx| {
 6478        editor.undo(&Default::default(), window, cx);
 6479    });
 6480
 6481    // Verify cursor moved back to position after edit
 6482    cx.assert_editor_state(indoc! {"
 6483        line 1
 6484        line 2
 6485        linXˇe 3
 6486        line 4
 6487        line 5
 6488    "});
 6489}
 6490
 6491#[gpui::test]
 6492async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6493    init_test(cx, |_| {});
 6494
 6495    let mut cx = EditorTestContext::new(cx).await;
 6496
 6497    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6498    cx.update_editor(|editor, window, cx| {
 6499        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6500    });
 6501
 6502    cx.set_state(indoc! {"
 6503        line 1
 6504        line 2
 6505        linˇe 3
 6506        line 4
 6507        line 5
 6508        line 6
 6509        line 7
 6510        line 8
 6511        line 9
 6512        line 10
 6513    "});
 6514
 6515    let snapshot = cx.buffer_snapshot();
 6516    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6517
 6518    cx.update(|_, cx| {
 6519        provider.update(cx, |provider, _| {
 6520            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6521                id: None,
 6522                edits: vec![(edit_position..edit_position, "X".into())],
 6523                edit_preview: None,
 6524            }))
 6525        })
 6526    });
 6527
 6528    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6529    cx.update_editor(|editor, window, cx| {
 6530        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6531    });
 6532
 6533    cx.assert_editor_state(indoc! {"
 6534        line 1
 6535        line 2
 6536        lineXˇ 3
 6537        line 4
 6538        line 5
 6539        line 6
 6540        line 7
 6541        line 8
 6542        line 9
 6543        line 10
 6544    "});
 6545
 6546    cx.update_editor(|editor, window, cx| {
 6547        editor.change_selections(None, window, cx, |s| {
 6548            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6549        });
 6550    });
 6551
 6552    cx.assert_editor_state(indoc! {"
 6553        line 1
 6554        line 2
 6555        lineX 3
 6556        line 4
 6557        line 5
 6558        line 6
 6559        line 7
 6560        line 8
 6561        line 9
 6562        liˇne 10
 6563    "});
 6564
 6565    cx.update_editor(|editor, window, cx| {
 6566        editor.undo(&Default::default(), window, cx);
 6567    });
 6568
 6569    cx.assert_editor_state(indoc! {"
 6570        line 1
 6571        line 2
 6572        lineˇ 3
 6573        line 4
 6574        line 5
 6575        line 6
 6576        line 7
 6577        line 8
 6578        line 9
 6579        line 10
 6580    "});
 6581}
 6582
 6583#[gpui::test]
 6584async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6585    init_test(cx, |_| {});
 6586
 6587    let mut cx = EditorTestContext::new(cx).await;
 6588    cx.set_state(
 6589        r#"let foo = 2;
 6590lˇet foo = 2;
 6591let fooˇ = 2;
 6592let foo = 2;
 6593let foo = ˇ2;"#,
 6594    );
 6595
 6596    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6597        .unwrap();
 6598    cx.assert_editor_state(
 6599        r#"let foo = 2;
 6600«letˇ» foo = 2;
 6601let «fooˇ» = 2;
 6602let foo = 2;
 6603let foo = «2ˇ»;"#,
 6604    );
 6605
 6606    // noop for multiple selections with different contents
 6607    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6608        .unwrap();
 6609    cx.assert_editor_state(
 6610        r#"let foo = 2;
 6611«letˇ» foo = 2;
 6612let «fooˇ» = 2;
 6613let foo = 2;
 6614let foo = «2ˇ»;"#,
 6615    );
 6616
 6617    // Test last selection direction should be preserved
 6618    cx.set_state(
 6619        r#"let foo = 2;
 6620let foo = 2;
 6621let «fooˇ» = 2;
 6622let «ˇfoo» = 2;
 6623let foo = 2;"#,
 6624    );
 6625
 6626    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6627        .unwrap();
 6628    cx.assert_editor_state(
 6629        r#"let foo = 2;
 6630let foo = 2;
 6631let «fooˇ» = 2;
 6632let «ˇfoo» = 2;
 6633let «ˇfoo» = 2;"#,
 6634    );
 6635}
 6636
 6637#[gpui::test]
 6638async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6639    init_test(cx, |_| {});
 6640
 6641    let mut cx =
 6642        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6643
 6644    cx.assert_editor_state(indoc! {"
 6645        ˇbbb
 6646        ccc
 6647
 6648        bbb
 6649        ccc
 6650        "});
 6651    cx.dispatch_action(SelectPrevious::default());
 6652    cx.assert_editor_state(indoc! {"
 6653                «bbbˇ»
 6654                ccc
 6655
 6656                bbb
 6657                ccc
 6658                "});
 6659    cx.dispatch_action(SelectPrevious::default());
 6660    cx.assert_editor_state(indoc! {"
 6661                «bbbˇ»
 6662                ccc
 6663
 6664                «bbbˇ»
 6665                ccc
 6666                "});
 6667}
 6668
 6669#[gpui::test]
 6670async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6671    init_test(cx, |_| {});
 6672
 6673    let mut cx = EditorTestContext::new(cx).await;
 6674    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6675
 6676    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6677        .unwrap();
 6678    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6679
 6680    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6681        .unwrap();
 6682    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6683
 6684    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6685    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6686
 6687    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6688    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6689
 6690    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6691        .unwrap();
 6692    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6693
 6694    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6695        .unwrap();
 6696    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6697}
 6698
 6699#[gpui::test]
 6700async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6701    init_test(cx, |_| {});
 6702
 6703    let mut cx = EditorTestContext::new(cx).await;
 6704    cx.set_state("");
 6705
 6706    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6707        .unwrap();
 6708    cx.assert_editor_state("«aˇ»");
 6709    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6710        .unwrap();
 6711    cx.assert_editor_state("«aˇ»");
 6712}
 6713
 6714#[gpui::test]
 6715async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6716    init_test(cx, |_| {});
 6717
 6718    let mut cx = EditorTestContext::new(cx).await;
 6719    cx.set_state(
 6720        r#"let foo = 2;
 6721lˇet foo = 2;
 6722let fooˇ = 2;
 6723let foo = 2;
 6724let foo = ˇ2;"#,
 6725    );
 6726
 6727    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6728        .unwrap();
 6729    cx.assert_editor_state(
 6730        r#"let foo = 2;
 6731«letˇ» foo = 2;
 6732let «fooˇ» = 2;
 6733let foo = 2;
 6734let foo = «2ˇ»;"#,
 6735    );
 6736
 6737    // noop for multiple selections with different contents
 6738    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6739        .unwrap();
 6740    cx.assert_editor_state(
 6741        r#"let foo = 2;
 6742«letˇ» foo = 2;
 6743let «fooˇ» = 2;
 6744let foo = 2;
 6745let foo = «2ˇ»;"#,
 6746    );
 6747}
 6748
 6749#[gpui::test]
 6750async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6751    init_test(cx, |_| {});
 6752
 6753    let mut cx = EditorTestContext::new(cx).await;
 6754    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6755
 6756    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6757        .unwrap();
 6758    // selection direction is preserved
 6759    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6760
 6761    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6762        .unwrap();
 6763    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6764
 6765    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6766    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6767
 6768    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6769    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6770
 6771    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6772        .unwrap();
 6773    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6774
 6775    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6776        .unwrap();
 6777    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6778}
 6779
 6780#[gpui::test]
 6781async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6782    init_test(cx, |_| {});
 6783
 6784    let language = Arc::new(Language::new(
 6785        LanguageConfig::default(),
 6786        Some(tree_sitter_rust::LANGUAGE.into()),
 6787    ));
 6788
 6789    let text = r#"
 6790        use mod1::mod2::{mod3, mod4};
 6791
 6792        fn fn_1(param1: bool, param2: &str) {
 6793            let var1 = "text";
 6794        }
 6795    "#
 6796    .unindent();
 6797
 6798    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6799    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6800    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6801
 6802    editor
 6803        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6804        .await;
 6805
 6806    editor.update_in(cx, |editor, window, cx| {
 6807        editor.change_selections(None, window, cx, |s| {
 6808            s.select_display_ranges([
 6809                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6810                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6811                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6812            ]);
 6813        });
 6814        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6815    });
 6816    editor.update(cx, |editor, cx| {
 6817        assert_text_with_selections(
 6818            editor,
 6819            indoc! {r#"
 6820                use mod1::mod2::{mod3, «mod4ˇ»};
 6821
 6822                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6823                    let var1 = "«ˇtext»";
 6824                }
 6825            "#},
 6826            cx,
 6827        );
 6828    });
 6829
 6830    editor.update_in(cx, |editor, window, cx| {
 6831        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6832    });
 6833    editor.update(cx, |editor, cx| {
 6834        assert_text_with_selections(
 6835            editor,
 6836            indoc! {r#"
 6837                use mod1::mod2::«{mod3, mod4}ˇ»;
 6838
 6839                «ˇfn fn_1(param1: bool, param2: &str) {
 6840                    let var1 = "text";
 6841 6842            "#},
 6843            cx,
 6844        );
 6845    });
 6846
 6847    editor.update_in(cx, |editor, window, cx| {
 6848        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6849    });
 6850    assert_eq!(
 6851        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6852        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6853    );
 6854
 6855    // Trying to expand the selected syntax node one more time has no effect.
 6856    editor.update_in(cx, |editor, window, cx| {
 6857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6858    });
 6859    assert_eq!(
 6860        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6861        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6862    );
 6863
 6864    editor.update_in(cx, |editor, window, cx| {
 6865        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6866    });
 6867    editor.update(cx, |editor, cx| {
 6868        assert_text_with_selections(
 6869            editor,
 6870            indoc! {r#"
 6871                use mod1::mod2::«{mod3, mod4}ˇ»;
 6872
 6873                «ˇfn fn_1(param1: bool, param2: &str) {
 6874                    let var1 = "text";
 6875 6876            "#},
 6877            cx,
 6878        );
 6879    });
 6880
 6881    editor.update_in(cx, |editor, window, cx| {
 6882        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6883    });
 6884    editor.update(cx, |editor, cx| {
 6885        assert_text_with_selections(
 6886            editor,
 6887            indoc! {r#"
 6888                use mod1::mod2::{mod3, «mod4ˇ»};
 6889
 6890                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6891                    let var1 = "«ˇtext»";
 6892                }
 6893            "#},
 6894            cx,
 6895        );
 6896    });
 6897
 6898    editor.update_in(cx, |editor, window, cx| {
 6899        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6900    });
 6901    editor.update(cx, |editor, cx| {
 6902        assert_text_with_selections(
 6903            editor,
 6904            indoc! {r#"
 6905                use mod1::mod2::{mod3, mo«ˇ»d4};
 6906
 6907                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6908                    let var1 = "te«ˇ»xt";
 6909                }
 6910            "#},
 6911            cx,
 6912        );
 6913    });
 6914
 6915    // Trying to shrink the selected syntax node one more time has no effect.
 6916    editor.update_in(cx, |editor, window, cx| {
 6917        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6918    });
 6919    editor.update_in(cx, |editor, _, cx| {
 6920        assert_text_with_selections(
 6921            editor,
 6922            indoc! {r#"
 6923                use mod1::mod2::{mod3, mo«ˇ»d4};
 6924
 6925                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6926                    let var1 = "te«ˇ»xt";
 6927                }
 6928            "#},
 6929            cx,
 6930        );
 6931    });
 6932
 6933    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6934    // a fold.
 6935    editor.update_in(cx, |editor, window, cx| {
 6936        editor.fold_creases(
 6937            vec![
 6938                Crease::simple(
 6939                    Point::new(0, 21)..Point::new(0, 24),
 6940                    FoldPlaceholder::test(),
 6941                ),
 6942                Crease::simple(
 6943                    Point::new(3, 20)..Point::new(3, 22),
 6944                    FoldPlaceholder::test(),
 6945                ),
 6946            ],
 6947            true,
 6948            window,
 6949            cx,
 6950        );
 6951        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6952    });
 6953    editor.update(cx, |editor, cx| {
 6954        assert_text_with_selections(
 6955            editor,
 6956            indoc! {r#"
 6957                use mod1::mod2::«{mod3, mod4}ˇ»;
 6958
 6959                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6960                    let var1 = "«ˇtext»";
 6961                }
 6962            "#},
 6963            cx,
 6964        );
 6965    });
 6966}
 6967
 6968#[gpui::test]
 6969async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6970    init_test(cx, |_| {});
 6971
 6972    let language = Arc::new(Language::new(
 6973        LanguageConfig::default(),
 6974        Some(tree_sitter_rust::LANGUAGE.into()),
 6975    ));
 6976
 6977    let text = "let a = 2;";
 6978
 6979    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6980    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6981    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6982
 6983    editor
 6984        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6985        .await;
 6986
 6987    // Test case 1: Cursor at end of word
 6988    editor.update_in(cx, |editor, window, cx| {
 6989        editor.change_selections(None, window, cx, |s| {
 6990            s.select_display_ranges([
 6991                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6992            ]);
 6993        });
 6994    });
 6995    editor.update(cx, |editor, cx| {
 6996        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6997    });
 6998    editor.update_in(cx, |editor, window, cx| {
 6999        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7000    });
 7001    editor.update(cx, |editor, cx| {
 7002        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7003    });
 7004    editor.update_in(cx, |editor, window, cx| {
 7005        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7006    });
 7007    editor.update(cx, |editor, cx| {
 7008        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7009    });
 7010
 7011    // Test case 2: Cursor at end of statement
 7012    editor.update_in(cx, |editor, window, cx| {
 7013        editor.change_selections(None, window, cx, |s| {
 7014            s.select_display_ranges([
 7015                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7016            ]);
 7017        });
 7018    });
 7019    editor.update(cx, |editor, cx| {
 7020        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7021    });
 7022    editor.update_in(cx, |editor, window, cx| {
 7023        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7024    });
 7025    editor.update(cx, |editor, cx| {
 7026        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7027    });
 7028}
 7029
 7030#[gpui::test]
 7031async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7032    init_test(cx, |_| {});
 7033
 7034    let language = Arc::new(Language::new(
 7035        LanguageConfig::default(),
 7036        Some(tree_sitter_rust::LANGUAGE.into()),
 7037    ));
 7038
 7039    let text = r#"
 7040        use mod1::mod2::{mod3, mod4};
 7041
 7042        fn fn_1(param1: bool, param2: &str) {
 7043            let var1 = "hello world";
 7044        }
 7045    "#
 7046    .unindent();
 7047
 7048    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7049    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7050    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7051
 7052    editor
 7053        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7054        .await;
 7055
 7056    // Test 1: Cursor on a letter of a string word
 7057    editor.update_in(cx, |editor, window, cx| {
 7058        editor.change_selections(None, window, cx, |s| {
 7059            s.select_display_ranges([
 7060                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7061            ]);
 7062        });
 7063    });
 7064    editor.update_in(cx, |editor, window, cx| {
 7065        assert_text_with_selections(
 7066            editor,
 7067            indoc! {r#"
 7068                use mod1::mod2::{mod3, mod4};
 7069
 7070                fn fn_1(param1: bool, param2: &str) {
 7071                    let var1 = "hˇello world";
 7072                }
 7073            "#},
 7074            cx,
 7075        );
 7076        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7077        assert_text_with_selections(
 7078            editor,
 7079            indoc! {r#"
 7080                use mod1::mod2::{mod3, mod4};
 7081
 7082                fn fn_1(param1: bool, param2: &str) {
 7083                    let var1 = "«ˇhello» world";
 7084                }
 7085            "#},
 7086            cx,
 7087        );
 7088    });
 7089
 7090    // Test 2: Partial selection within a word
 7091    editor.update_in(cx, |editor, window, cx| {
 7092        editor.change_selections(None, window, cx, |s| {
 7093            s.select_display_ranges([
 7094                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7095            ]);
 7096        });
 7097    });
 7098    editor.update_in(cx, |editor, window, cx| {
 7099        assert_text_with_selections(
 7100            editor,
 7101            indoc! {r#"
 7102                use mod1::mod2::{mod3, mod4};
 7103
 7104                fn fn_1(param1: bool, param2: &str) {
 7105                    let var1 = "h«elˇ»lo world";
 7106                }
 7107            "#},
 7108            cx,
 7109        );
 7110        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7111        assert_text_with_selections(
 7112            editor,
 7113            indoc! {r#"
 7114                use mod1::mod2::{mod3, mod4};
 7115
 7116                fn fn_1(param1: bool, param2: &str) {
 7117                    let var1 = "«ˇhello» world";
 7118                }
 7119            "#},
 7120            cx,
 7121        );
 7122    });
 7123
 7124    // Test 3: Complete word already selected
 7125    editor.update_in(cx, |editor, window, cx| {
 7126        editor.change_selections(None, window, cx, |s| {
 7127            s.select_display_ranges([
 7128                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7129            ]);
 7130        });
 7131    });
 7132    editor.update_in(cx, |editor, 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        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7145        assert_text_with_selections(
 7146            editor,
 7147            indoc! {r#"
 7148                use mod1::mod2::{mod3, mod4};
 7149
 7150                fn fn_1(param1: bool, param2: &str) {
 7151                    let var1 = "«hello worldˇ»";
 7152                }
 7153            "#},
 7154            cx,
 7155        );
 7156    });
 7157
 7158    // Test 4: Selection spanning across words
 7159    editor.update_in(cx, |editor, window, cx| {
 7160        editor.change_selections(None, window, cx, |s| {
 7161            s.select_display_ranges([
 7162                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7163            ]);
 7164        });
 7165    });
 7166    editor.update_in(cx, |editor, window, cx| {
 7167        assert_text_with_selections(
 7168            editor,
 7169            indoc! {r#"
 7170                use mod1::mod2::{mod3, mod4};
 7171
 7172                fn fn_1(param1: bool, param2: &str) {
 7173                    let var1 = "hel«lo woˇ»rld";
 7174                }
 7175            "#},
 7176            cx,
 7177        );
 7178        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7179        assert_text_with_selections(
 7180            editor,
 7181            indoc! {r#"
 7182                use mod1::mod2::{mod3, mod4};
 7183
 7184                fn fn_1(param1: bool, param2: &str) {
 7185                    let var1 = "«ˇhello world»";
 7186                }
 7187            "#},
 7188            cx,
 7189        );
 7190    });
 7191
 7192    // Test 5: Expansion beyond string
 7193    editor.update_in(cx, |editor, window, cx| {
 7194        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7195        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7196        assert_text_with_selections(
 7197            editor,
 7198            indoc! {r#"
 7199                use mod1::mod2::{mod3, mod4};
 7200
 7201                fn fn_1(param1: bool, param2: &str) {
 7202                    «ˇlet var1 = "hello world";»
 7203                }
 7204            "#},
 7205            cx,
 7206        );
 7207    });
 7208}
 7209
 7210#[gpui::test]
 7211async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7212    init_test(cx, |_| {});
 7213
 7214    let base_text = r#"
 7215        impl A {
 7216            // this is an uncommitted comment
 7217
 7218            fn b() {
 7219                c();
 7220            }
 7221
 7222            // this is another uncommitted comment
 7223
 7224            fn d() {
 7225                // e
 7226                // f
 7227            }
 7228        }
 7229
 7230        fn g() {
 7231            // h
 7232        }
 7233    "#
 7234    .unindent();
 7235
 7236    let text = r#"
 7237        ˇimpl A {
 7238
 7239            fn b() {
 7240                c();
 7241            }
 7242
 7243            fn d() {
 7244                // e
 7245                // f
 7246            }
 7247        }
 7248
 7249        fn g() {
 7250            // h
 7251        }
 7252    "#
 7253    .unindent();
 7254
 7255    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7256    cx.set_state(&text);
 7257    cx.set_head_text(&base_text);
 7258    cx.update_editor(|editor, window, cx| {
 7259        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7260    });
 7261
 7262    cx.assert_state_with_diff(
 7263        "
 7264        ˇimpl A {
 7265      -     // this is an uncommitted comment
 7266
 7267            fn b() {
 7268                c();
 7269            }
 7270
 7271      -     // this is another uncommitted comment
 7272      -
 7273            fn d() {
 7274                // e
 7275                // f
 7276            }
 7277        }
 7278
 7279        fn g() {
 7280            // h
 7281        }
 7282    "
 7283        .unindent(),
 7284    );
 7285
 7286    let expected_display_text = "
 7287        impl A {
 7288            // this is an uncommitted comment
 7289
 7290            fn b() {
 7291 7292            }
 7293
 7294            // this is another uncommitted comment
 7295
 7296            fn d() {
 7297 7298            }
 7299        }
 7300
 7301        fn g() {
 7302 7303        }
 7304        "
 7305    .unindent();
 7306
 7307    cx.update_editor(|editor, window, cx| {
 7308        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7309        assert_eq!(editor.display_text(cx), expected_display_text);
 7310    });
 7311}
 7312
 7313#[gpui::test]
 7314async fn test_autoindent(cx: &mut TestAppContext) {
 7315    init_test(cx, |_| {});
 7316
 7317    let language = Arc::new(
 7318        Language::new(
 7319            LanguageConfig {
 7320                brackets: BracketPairConfig {
 7321                    pairs: vec![
 7322                        BracketPair {
 7323                            start: "{".to_string(),
 7324                            end: "}".to_string(),
 7325                            close: false,
 7326                            surround: false,
 7327                            newline: true,
 7328                        },
 7329                        BracketPair {
 7330                            start: "(".to_string(),
 7331                            end: ")".to_string(),
 7332                            close: false,
 7333                            surround: false,
 7334                            newline: true,
 7335                        },
 7336                    ],
 7337                    ..Default::default()
 7338                },
 7339                ..Default::default()
 7340            },
 7341            Some(tree_sitter_rust::LANGUAGE.into()),
 7342        )
 7343        .with_indents_query(
 7344            r#"
 7345                (_ "(" ")" @end) @indent
 7346                (_ "{" "}" @end) @indent
 7347            "#,
 7348        )
 7349        .unwrap(),
 7350    );
 7351
 7352    let text = "fn a() {}";
 7353
 7354    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7355    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7356    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7357    editor
 7358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7359        .await;
 7360
 7361    editor.update_in(cx, |editor, window, cx| {
 7362        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7363        editor.newline(&Newline, window, cx);
 7364        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7365        assert_eq!(
 7366            editor.selections.ranges(cx),
 7367            &[
 7368                Point::new(1, 4)..Point::new(1, 4),
 7369                Point::new(3, 4)..Point::new(3, 4),
 7370                Point::new(5, 0)..Point::new(5, 0)
 7371            ]
 7372        );
 7373    });
 7374}
 7375
 7376#[gpui::test]
 7377async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7378    init_test(cx, |_| {});
 7379
 7380    {
 7381        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7382        cx.set_state(indoc! {"
 7383            impl A {
 7384
 7385                fn b() {}
 7386
 7387            «fn c() {
 7388
 7389            }ˇ»
 7390            }
 7391        "});
 7392
 7393        cx.update_editor(|editor, window, cx| {
 7394            editor.autoindent(&Default::default(), window, cx);
 7395        });
 7396
 7397        cx.assert_editor_state(indoc! {"
 7398            impl A {
 7399
 7400                fn b() {}
 7401
 7402                «fn c() {
 7403
 7404                }ˇ»
 7405            }
 7406        "});
 7407    }
 7408
 7409    {
 7410        let mut cx = EditorTestContext::new_multibuffer(
 7411            cx,
 7412            [indoc! { "
 7413                impl A {
 7414                «
 7415                // a
 7416                fn b(){}
 7417                »
 7418                «
 7419                    }
 7420                    fn c(){}
 7421                »
 7422            "}],
 7423        );
 7424
 7425        let buffer = cx.update_editor(|editor, _, cx| {
 7426            let buffer = editor.buffer().update(cx, |buffer, _| {
 7427                buffer.all_buffers().iter().next().unwrap().clone()
 7428            });
 7429            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7430            buffer
 7431        });
 7432
 7433        cx.run_until_parked();
 7434        cx.update_editor(|editor, window, cx| {
 7435            editor.select_all(&Default::default(), window, cx);
 7436            editor.autoindent(&Default::default(), window, cx)
 7437        });
 7438        cx.run_until_parked();
 7439
 7440        cx.update(|_, cx| {
 7441            assert_eq!(
 7442                buffer.read(cx).text(),
 7443                indoc! { "
 7444                    impl A {
 7445
 7446                        // a
 7447                        fn b(){}
 7448
 7449
 7450                    }
 7451                    fn c(){}
 7452
 7453                " }
 7454            )
 7455        });
 7456    }
 7457}
 7458
 7459#[gpui::test]
 7460async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7461    init_test(cx, |_| {});
 7462
 7463    let mut cx = EditorTestContext::new(cx).await;
 7464
 7465    let language = Arc::new(Language::new(
 7466        LanguageConfig {
 7467            brackets: BracketPairConfig {
 7468                pairs: vec![
 7469                    BracketPair {
 7470                        start: "{".to_string(),
 7471                        end: "}".to_string(),
 7472                        close: true,
 7473                        surround: true,
 7474                        newline: true,
 7475                    },
 7476                    BracketPair {
 7477                        start: "(".to_string(),
 7478                        end: ")".to_string(),
 7479                        close: true,
 7480                        surround: true,
 7481                        newline: true,
 7482                    },
 7483                    BracketPair {
 7484                        start: "/*".to_string(),
 7485                        end: " */".to_string(),
 7486                        close: true,
 7487                        surround: true,
 7488                        newline: true,
 7489                    },
 7490                    BracketPair {
 7491                        start: "[".to_string(),
 7492                        end: "]".to_string(),
 7493                        close: false,
 7494                        surround: false,
 7495                        newline: true,
 7496                    },
 7497                    BracketPair {
 7498                        start: "\"".to_string(),
 7499                        end: "\"".to_string(),
 7500                        close: true,
 7501                        surround: true,
 7502                        newline: false,
 7503                    },
 7504                    BracketPair {
 7505                        start: "<".to_string(),
 7506                        end: ">".to_string(),
 7507                        close: false,
 7508                        surround: true,
 7509                        newline: true,
 7510                    },
 7511                ],
 7512                ..Default::default()
 7513            },
 7514            autoclose_before: "})]".to_string(),
 7515            ..Default::default()
 7516        },
 7517        Some(tree_sitter_rust::LANGUAGE.into()),
 7518    ));
 7519
 7520    cx.language_registry().add(language.clone());
 7521    cx.update_buffer(|buffer, cx| {
 7522        buffer.set_language(Some(language), cx);
 7523    });
 7524
 7525    cx.set_state(
 7526        &r#"
 7527            🏀ˇ
 7528            εˇ
 7529            ❤️ˇ
 7530        "#
 7531        .unindent(),
 7532    );
 7533
 7534    // autoclose multiple nested brackets at multiple cursors
 7535    cx.update_editor(|editor, window, cx| {
 7536        editor.handle_input("{", window, cx);
 7537        editor.handle_input("{", window, cx);
 7538        editor.handle_input("{", window, cx);
 7539    });
 7540    cx.assert_editor_state(
 7541        &"
 7542            🏀{{{ˇ}}}
 7543            ε{{{ˇ}}}
 7544            ❤️{{{ˇ}}}
 7545        "
 7546        .unindent(),
 7547    );
 7548
 7549    // insert a different closing bracket
 7550    cx.update_editor(|editor, window, cx| {
 7551        editor.handle_input(")", window, cx);
 7552    });
 7553    cx.assert_editor_state(
 7554        &"
 7555            🏀{{{)ˇ}}}
 7556            ε{{{)ˇ}}}
 7557            ❤️{{{)ˇ}}}
 7558        "
 7559        .unindent(),
 7560    );
 7561
 7562    // skip over the auto-closed brackets when typing a closing bracket
 7563    cx.update_editor(|editor, window, cx| {
 7564        editor.move_right(&MoveRight, window, cx);
 7565        editor.handle_input("}", window, cx);
 7566        editor.handle_input("}", window, cx);
 7567        editor.handle_input("}", window, cx);
 7568    });
 7569    cx.assert_editor_state(
 7570        &"
 7571            🏀{{{)}}}}ˇ
 7572            ε{{{)}}}}ˇ
 7573            ❤️{{{)}}}}ˇ
 7574        "
 7575        .unindent(),
 7576    );
 7577
 7578    // autoclose multi-character pairs
 7579    cx.set_state(
 7580        &"
 7581            ˇ
 7582            ˇ
 7583        "
 7584        .unindent(),
 7585    );
 7586    cx.update_editor(|editor, window, cx| {
 7587        editor.handle_input("/", window, cx);
 7588        editor.handle_input("*", window, cx);
 7589    });
 7590    cx.assert_editor_state(
 7591        &"
 7592            /*ˇ */
 7593            /*ˇ */
 7594        "
 7595        .unindent(),
 7596    );
 7597
 7598    // one cursor autocloses a multi-character pair, one cursor
 7599    // does not autoclose.
 7600    cx.set_state(
 7601        &"
 7602 7603            ˇ
 7604        "
 7605        .unindent(),
 7606    );
 7607    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7608    cx.assert_editor_state(
 7609        &"
 7610            /*ˇ */
 7611 7612        "
 7613        .unindent(),
 7614    );
 7615
 7616    // Don't autoclose if the next character isn't whitespace and isn't
 7617    // listed in the language's "autoclose_before" section.
 7618    cx.set_state("ˇa b");
 7619    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7620    cx.assert_editor_state("{ˇa b");
 7621
 7622    // Don't autoclose if `close` is false for the bracket pair
 7623    cx.set_state("ˇ");
 7624    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7625    cx.assert_editor_state("");
 7626
 7627    // Surround with brackets if text is selected
 7628    cx.set_state("«aˇ» b");
 7629    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7630    cx.assert_editor_state("{«aˇ»} b");
 7631
 7632    // Autoclose when not immediately after a word character
 7633    cx.set_state("a ˇ");
 7634    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7635    cx.assert_editor_state("a \"ˇ\"");
 7636
 7637    // Autoclose pair where the start and end characters are the same
 7638    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7639    cx.assert_editor_state("a \"\"ˇ");
 7640
 7641    // Don't autoclose when immediately after a word character
 7642    cx.set_state("");
 7643    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7644    cx.assert_editor_state("a\"ˇ");
 7645
 7646    // Do autoclose when after a non-word character
 7647    cx.set_state("");
 7648    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7649    cx.assert_editor_state("{\"ˇ\"");
 7650
 7651    // Non identical pairs autoclose regardless of preceding character
 7652    cx.set_state("");
 7653    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7654    cx.assert_editor_state("a{ˇ}");
 7655
 7656    // Don't autoclose pair if autoclose is disabled
 7657    cx.set_state("ˇ");
 7658    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7659    cx.assert_editor_state("");
 7660
 7661    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7662    cx.set_state("«aˇ» b");
 7663    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7664    cx.assert_editor_state("<«aˇ»> b");
 7665}
 7666
 7667#[gpui::test]
 7668async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7669    init_test(cx, |settings| {
 7670        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7671    });
 7672
 7673    let mut cx = EditorTestContext::new(cx).await;
 7674
 7675    let language = Arc::new(Language::new(
 7676        LanguageConfig {
 7677            brackets: BracketPairConfig {
 7678                pairs: vec![
 7679                    BracketPair {
 7680                        start: "{".to_string(),
 7681                        end: "}".to_string(),
 7682                        close: true,
 7683                        surround: true,
 7684                        newline: true,
 7685                    },
 7686                    BracketPair {
 7687                        start: "(".to_string(),
 7688                        end: ")".to_string(),
 7689                        close: true,
 7690                        surround: true,
 7691                        newline: true,
 7692                    },
 7693                    BracketPair {
 7694                        start: "[".to_string(),
 7695                        end: "]".to_string(),
 7696                        close: false,
 7697                        surround: false,
 7698                        newline: true,
 7699                    },
 7700                ],
 7701                ..Default::default()
 7702            },
 7703            autoclose_before: "})]".to_string(),
 7704            ..Default::default()
 7705        },
 7706        Some(tree_sitter_rust::LANGUAGE.into()),
 7707    ));
 7708
 7709    cx.language_registry().add(language.clone());
 7710    cx.update_buffer(|buffer, cx| {
 7711        buffer.set_language(Some(language), cx);
 7712    });
 7713
 7714    cx.set_state(
 7715        &"
 7716            ˇ
 7717            ˇ
 7718            ˇ
 7719        "
 7720        .unindent(),
 7721    );
 7722
 7723    // ensure only matching closing brackets are skipped over
 7724    cx.update_editor(|editor, window, cx| {
 7725        editor.handle_input("}", window, cx);
 7726        editor.move_left(&MoveLeft, window, cx);
 7727        editor.handle_input(")", window, cx);
 7728        editor.move_left(&MoveLeft, window, cx);
 7729    });
 7730    cx.assert_editor_state(
 7731        &"
 7732            ˇ)}
 7733            ˇ)}
 7734            ˇ)}
 7735        "
 7736        .unindent(),
 7737    );
 7738
 7739    // skip-over closing brackets at multiple cursors
 7740    cx.update_editor(|editor, window, cx| {
 7741        editor.handle_input(")", window, cx);
 7742        editor.handle_input("}", window, cx);
 7743    });
 7744    cx.assert_editor_state(
 7745        &"
 7746            )}ˇ
 7747            )}ˇ
 7748            )}ˇ
 7749        "
 7750        .unindent(),
 7751    );
 7752
 7753    // ignore non-close brackets
 7754    cx.update_editor(|editor, window, cx| {
 7755        editor.handle_input("]", window, cx);
 7756        editor.move_left(&MoveLeft, window, cx);
 7757        editor.handle_input("]", window, cx);
 7758    });
 7759    cx.assert_editor_state(
 7760        &"
 7761            )}]ˇ]
 7762            )}]ˇ]
 7763            )}]ˇ]
 7764        "
 7765        .unindent(),
 7766    );
 7767}
 7768
 7769#[gpui::test]
 7770async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7771    init_test(cx, |_| {});
 7772
 7773    let mut cx = EditorTestContext::new(cx).await;
 7774
 7775    let html_language = Arc::new(
 7776        Language::new(
 7777            LanguageConfig {
 7778                name: "HTML".into(),
 7779                brackets: BracketPairConfig {
 7780                    pairs: vec![
 7781                        BracketPair {
 7782                            start: "<".into(),
 7783                            end: ">".into(),
 7784                            close: true,
 7785                            ..Default::default()
 7786                        },
 7787                        BracketPair {
 7788                            start: "{".into(),
 7789                            end: "}".into(),
 7790                            close: true,
 7791                            ..Default::default()
 7792                        },
 7793                        BracketPair {
 7794                            start: "(".into(),
 7795                            end: ")".into(),
 7796                            close: true,
 7797                            ..Default::default()
 7798                        },
 7799                    ],
 7800                    ..Default::default()
 7801                },
 7802                autoclose_before: "})]>".into(),
 7803                ..Default::default()
 7804            },
 7805            Some(tree_sitter_html::LANGUAGE.into()),
 7806        )
 7807        .with_injection_query(
 7808            r#"
 7809            (script_element
 7810                (raw_text) @injection.content
 7811                (#set! injection.language "javascript"))
 7812            "#,
 7813        )
 7814        .unwrap(),
 7815    );
 7816
 7817    let javascript_language = Arc::new(Language::new(
 7818        LanguageConfig {
 7819            name: "JavaScript".into(),
 7820            brackets: BracketPairConfig {
 7821                pairs: vec![
 7822                    BracketPair {
 7823                        start: "/*".into(),
 7824                        end: " */".into(),
 7825                        close: true,
 7826                        ..Default::default()
 7827                    },
 7828                    BracketPair {
 7829                        start: "{".into(),
 7830                        end: "}".into(),
 7831                        close: true,
 7832                        ..Default::default()
 7833                    },
 7834                    BracketPair {
 7835                        start: "(".into(),
 7836                        end: ")".into(),
 7837                        close: true,
 7838                        ..Default::default()
 7839                    },
 7840                ],
 7841                ..Default::default()
 7842            },
 7843            autoclose_before: "})]>".into(),
 7844            ..Default::default()
 7845        },
 7846        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7847    ));
 7848
 7849    cx.language_registry().add(html_language.clone());
 7850    cx.language_registry().add(javascript_language.clone());
 7851
 7852    cx.update_buffer(|buffer, cx| {
 7853        buffer.set_language(Some(html_language), cx);
 7854    });
 7855
 7856    cx.set_state(
 7857        &r#"
 7858            <body>ˇ
 7859                <script>
 7860                    var x = 1;ˇ
 7861                </script>
 7862            </body>ˇ
 7863        "#
 7864        .unindent(),
 7865    );
 7866
 7867    // Precondition: different languages are active at different locations.
 7868    cx.update_editor(|editor, window, cx| {
 7869        let snapshot = editor.snapshot(window, cx);
 7870        let cursors = editor.selections.ranges::<usize>(cx);
 7871        let languages = cursors
 7872            .iter()
 7873            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7874            .collect::<Vec<_>>();
 7875        assert_eq!(
 7876            languages,
 7877            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7878        );
 7879    });
 7880
 7881    // Angle brackets autoclose in HTML, but not JavaScript.
 7882    cx.update_editor(|editor, window, cx| {
 7883        editor.handle_input("<", window, cx);
 7884        editor.handle_input("a", window, cx);
 7885    });
 7886    cx.assert_editor_state(
 7887        &r#"
 7888            <body><aˇ>
 7889                <script>
 7890                    var x = 1;<aˇ
 7891                </script>
 7892            </body><aˇ>
 7893        "#
 7894        .unindent(),
 7895    );
 7896
 7897    // Curly braces and parens autoclose in both HTML and JavaScript.
 7898    cx.update_editor(|editor, window, cx| {
 7899        editor.handle_input(" b=", window, cx);
 7900        editor.handle_input("{", window, cx);
 7901        editor.handle_input("c", window, cx);
 7902        editor.handle_input("(", window, cx);
 7903    });
 7904    cx.assert_editor_state(
 7905        &r#"
 7906            <body><a b={c(ˇ)}>
 7907                <script>
 7908                    var x = 1;<a b={c(ˇ)}
 7909                </script>
 7910            </body><a b={c(ˇ)}>
 7911        "#
 7912        .unindent(),
 7913    );
 7914
 7915    // Brackets that were already autoclosed are skipped.
 7916    cx.update_editor(|editor, window, cx| {
 7917        editor.handle_input(")", window, cx);
 7918        editor.handle_input("d", window, cx);
 7919        editor.handle_input("}", window, cx);
 7920    });
 7921    cx.assert_editor_state(
 7922        &r#"
 7923            <body><a b={c()d}ˇ>
 7924                <script>
 7925                    var x = 1;<a b={c()d}ˇ
 7926                </script>
 7927            </body><a b={c()d}ˇ>
 7928        "#
 7929        .unindent(),
 7930    );
 7931    cx.update_editor(|editor, window, cx| {
 7932        editor.handle_input(">", window, cx);
 7933    });
 7934    cx.assert_editor_state(
 7935        &r#"
 7936            <body><a b={c()d}>ˇ
 7937                <script>
 7938                    var x = 1;<a b={c()d}>ˇ
 7939                </script>
 7940            </body><a b={c()d}>ˇ
 7941        "#
 7942        .unindent(),
 7943    );
 7944
 7945    // Reset
 7946    cx.set_state(
 7947        &r#"
 7948            <body>ˇ
 7949                <script>
 7950                    var x = 1;ˇ
 7951                </script>
 7952            </body>ˇ
 7953        "#
 7954        .unindent(),
 7955    );
 7956
 7957    cx.update_editor(|editor, window, cx| {
 7958        editor.handle_input("<", window, cx);
 7959    });
 7960    cx.assert_editor_state(
 7961        &r#"
 7962            <body><ˇ>
 7963                <script>
 7964                    var x = 1;<ˇ
 7965                </script>
 7966            </body><ˇ>
 7967        "#
 7968        .unindent(),
 7969    );
 7970
 7971    // When backspacing, the closing angle brackets are removed.
 7972    cx.update_editor(|editor, window, cx| {
 7973        editor.backspace(&Backspace, window, cx);
 7974    });
 7975    cx.assert_editor_state(
 7976        &r#"
 7977            <body>ˇ
 7978                <script>
 7979                    var x = 1;ˇ
 7980                </script>
 7981            </body>ˇ
 7982        "#
 7983        .unindent(),
 7984    );
 7985
 7986    // Block comments autoclose in JavaScript, but not HTML.
 7987    cx.update_editor(|editor, window, cx| {
 7988        editor.handle_input("/", window, cx);
 7989        editor.handle_input("*", window, cx);
 7990    });
 7991    cx.assert_editor_state(
 7992        &r#"
 7993            <body>/*ˇ
 7994                <script>
 7995                    var x = 1;/*ˇ */
 7996                </script>
 7997            </body>/*ˇ
 7998        "#
 7999        .unindent(),
 8000    );
 8001}
 8002
 8003#[gpui::test]
 8004async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8005    init_test(cx, |_| {});
 8006
 8007    let mut cx = EditorTestContext::new(cx).await;
 8008
 8009    let rust_language = Arc::new(
 8010        Language::new(
 8011            LanguageConfig {
 8012                name: "Rust".into(),
 8013                brackets: serde_json::from_value(json!([
 8014                    { "start": "{", "end": "}", "close": true, "newline": true },
 8015                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8016                ]))
 8017                .unwrap(),
 8018                autoclose_before: "})]>".into(),
 8019                ..Default::default()
 8020            },
 8021            Some(tree_sitter_rust::LANGUAGE.into()),
 8022        )
 8023        .with_override_query("(string_literal) @string")
 8024        .unwrap(),
 8025    );
 8026
 8027    cx.language_registry().add(rust_language.clone());
 8028    cx.update_buffer(|buffer, cx| {
 8029        buffer.set_language(Some(rust_language), cx);
 8030    });
 8031
 8032    cx.set_state(
 8033        &r#"
 8034            let x = ˇ
 8035        "#
 8036        .unindent(),
 8037    );
 8038
 8039    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8040    cx.update_editor(|editor, window, cx| {
 8041        editor.handle_input("\"", window, cx);
 8042    });
 8043    cx.assert_editor_state(
 8044        &r#"
 8045            let x = "ˇ"
 8046        "#
 8047        .unindent(),
 8048    );
 8049
 8050    // Inserting another quotation mark. The cursor moves across the existing
 8051    // automatically-inserted quotation mark.
 8052    cx.update_editor(|editor, window, cx| {
 8053        editor.handle_input("\"", window, cx);
 8054    });
 8055    cx.assert_editor_state(
 8056        &r#"
 8057            let x = ""ˇ
 8058        "#
 8059        .unindent(),
 8060    );
 8061
 8062    // Reset
 8063    cx.set_state(
 8064        &r#"
 8065            let x = ˇ
 8066        "#
 8067        .unindent(),
 8068    );
 8069
 8070    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8071    cx.update_editor(|editor, window, cx| {
 8072        editor.handle_input("\"", window, cx);
 8073        editor.handle_input(" ", window, cx);
 8074        editor.move_left(&Default::default(), window, cx);
 8075        editor.handle_input("\\", window, cx);
 8076        editor.handle_input("\"", window, cx);
 8077    });
 8078    cx.assert_editor_state(
 8079        &r#"
 8080            let x = "\"ˇ "
 8081        "#
 8082        .unindent(),
 8083    );
 8084
 8085    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8086    // mark. Nothing is inserted.
 8087    cx.update_editor(|editor, window, cx| {
 8088        editor.move_right(&Default::default(), window, cx);
 8089        editor.handle_input("\"", window, cx);
 8090    });
 8091    cx.assert_editor_state(
 8092        &r#"
 8093            let x = "\" "ˇ
 8094        "#
 8095        .unindent(),
 8096    );
 8097}
 8098
 8099#[gpui::test]
 8100async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8101    init_test(cx, |_| {});
 8102
 8103    let language = Arc::new(Language::new(
 8104        LanguageConfig {
 8105            brackets: BracketPairConfig {
 8106                pairs: vec![
 8107                    BracketPair {
 8108                        start: "{".to_string(),
 8109                        end: "}".to_string(),
 8110                        close: true,
 8111                        surround: true,
 8112                        newline: true,
 8113                    },
 8114                    BracketPair {
 8115                        start: "/* ".to_string(),
 8116                        end: "*/".to_string(),
 8117                        close: true,
 8118                        surround: true,
 8119                        ..Default::default()
 8120                    },
 8121                ],
 8122                ..Default::default()
 8123            },
 8124            ..Default::default()
 8125        },
 8126        Some(tree_sitter_rust::LANGUAGE.into()),
 8127    ));
 8128
 8129    let text = r#"
 8130        a
 8131        b
 8132        c
 8133    "#
 8134    .unindent();
 8135
 8136    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8137    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8138    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8139    editor
 8140        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8141        .await;
 8142
 8143    editor.update_in(cx, |editor, window, cx| {
 8144        editor.change_selections(None, window, cx, |s| {
 8145            s.select_display_ranges([
 8146                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8147                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8148                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8149            ])
 8150        });
 8151
 8152        editor.handle_input("{", window, cx);
 8153        editor.handle_input("{", window, cx);
 8154        editor.handle_input("{", window, cx);
 8155        assert_eq!(
 8156            editor.text(cx),
 8157            "
 8158                {{{a}}}
 8159                {{{b}}}
 8160                {{{c}}}
 8161            "
 8162            .unindent()
 8163        );
 8164        assert_eq!(
 8165            editor.selections.display_ranges(cx),
 8166            [
 8167                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8168                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8169                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8170            ]
 8171        );
 8172
 8173        editor.undo(&Undo, window, cx);
 8174        editor.undo(&Undo, window, cx);
 8175        editor.undo(&Undo, window, cx);
 8176        assert_eq!(
 8177            editor.text(cx),
 8178            "
 8179                a
 8180                b
 8181                c
 8182            "
 8183            .unindent()
 8184        );
 8185        assert_eq!(
 8186            editor.selections.display_ranges(cx),
 8187            [
 8188                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8189                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8190                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8191            ]
 8192        );
 8193
 8194        // Ensure inserting the first character of a multi-byte bracket pair
 8195        // doesn't surround the selections with the bracket.
 8196        editor.handle_input("/", window, cx);
 8197        assert_eq!(
 8198            editor.text(cx),
 8199            "
 8200                /
 8201                /
 8202                /
 8203            "
 8204            .unindent()
 8205        );
 8206        assert_eq!(
 8207            editor.selections.display_ranges(cx),
 8208            [
 8209                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8210                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8211                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8212            ]
 8213        );
 8214
 8215        editor.undo(&Undo, window, cx);
 8216        assert_eq!(
 8217            editor.text(cx),
 8218            "
 8219                a
 8220                b
 8221                c
 8222            "
 8223            .unindent()
 8224        );
 8225        assert_eq!(
 8226            editor.selections.display_ranges(cx),
 8227            [
 8228                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8229                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8230                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8231            ]
 8232        );
 8233
 8234        // Ensure inserting the last character of a multi-byte bracket pair
 8235        // doesn't surround the selections with the bracket.
 8236        editor.handle_input("*", window, cx);
 8237        assert_eq!(
 8238            editor.text(cx),
 8239            "
 8240                *
 8241                *
 8242                *
 8243            "
 8244            .unindent()
 8245        );
 8246        assert_eq!(
 8247            editor.selections.display_ranges(cx),
 8248            [
 8249                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8250                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8251                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8252            ]
 8253        );
 8254    });
 8255}
 8256
 8257#[gpui::test]
 8258async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8259    init_test(cx, |_| {});
 8260
 8261    let language = Arc::new(Language::new(
 8262        LanguageConfig {
 8263            brackets: BracketPairConfig {
 8264                pairs: vec![BracketPair {
 8265                    start: "{".to_string(),
 8266                    end: "}".to_string(),
 8267                    close: true,
 8268                    surround: true,
 8269                    newline: true,
 8270                }],
 8271                ..Default::default()
 8272            },
 8273            autoclose_before: "}".to_string(),
 8274            ..Default::default()
 8275        },
 8276        Some(tree_sitter_rust::LANGUAGE.into()),
 8277    ));
 8278
 8279    let text = r#"
 8280        a
 8281        b
 8282        c
 8283    "#
 8284    .unindent();
 8285
 8286    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8287    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8288    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8289    editor
 8290        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8291        .await;
 8292
 8293    editor.update_in(cx, |editor, window, cx| {
 8294        editor.change_selections(None, window, cx, |s| {
 8295            s.select_ranges([
 8296                Point::new(0, 1)..Point::new(0, 1),
 8297                Point::new(1, 1)..Point::new(1, 1),
 8298                Point::new(2, 1)..Point::new(2, 1),
 8299            ])
 8300        });
 8301
 8302        editor.handle_input("{", window, cx);
 8303        editor.handle_input("{", window, cx);
 8304        editor.handle_input("_", window, cx);
 8305        assert_eq!(
 8306            editor.text(cx),
 8307            "
 8308                a{{_}}
 8309                b{{_}}
 8310                c{{_}}
 8311            "
 8312            .unindent()
 8313        );
 8314        assert_eq!(
 8315            editor.selections.ranges::<Point>(cx),
 8316            [
 8317                Point::new(0, 4)..Point::new(0, 4),
 8318                Point::new(1, 4)..Point::new(1, 4),
 8319                Point::new(2, 4)..Point::new(2, 4)
 8320            ]
 8321        );
 8322
 8323        editor.backspace(&Default::default(), window, cx);
 8324        editor.backspace(&Default::default(), window, cx);
 8325        assert_eq!(
 8326            editor.text(cx),
 8327            "
 8328                a{}
 8329                b{}
 8330                c{}
 8331            "
 8332            .unindent()
 8333        );
 8334        assert_eq!(
 8335            editor.selections.ranges::<Point>(cx),
 8336            [
 8337                Point::new(0, 2)..Point::new(0, 2),
 8338                Point::new(1, 2)..Point::new(1, 2),
 8339                Point::new(2, 2)..Point::new(2, 2)
 8340            ]
 8341        );
 8342
 8343        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8344        assert_eq!(
 8345            editor.text(cx),
 8346            "
 8347                a
 8348                b
 8349                c
 8350            "
 8351            .unindent()
 8352        );
 8353        assert_eq!(
 8354            editor.selections.ranges::<Point>(cx),
 8355            [
 8356                Point::new(0, 1)..Point::new(0, 1),
 8357                Point::new(1, 1)..Point::new(1, 1),
 8358                Point::new(2, 1)..Point::new(2, 1)
 8359            ]
 8360        );
 8361    });
 8362}
 8363
 8364#[gpui::test]
 8365async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8366    init_test(cx, |settings| {
 8367        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8368    });
 8369
 8370    let mut cx = EditorTestContext::new(cx).await;
 8371
 8372    let language = Arc::new(Language::new(
 8373        LanguageConfig {
 8374            brackets: BracketPairConfig {
 8375                pairs: vec![
 8376                    BracketPair {
 8377                        start: "{".to_string(),
 8378                        end: "}".to_string(),
 8379                        close: true,
 8380                        surround: true,
 8381                        newline: true,
 8382                    },
 8383                    BracketPair {
 8384                        start: "(".to_string(),
 8385                        end: ")".to_string(),
 8386                        close: true,
 8387                        surround: true,
 8388                        newline: true,
 8389                    },
 8390                    BracketPair {
 8391                        start: "[".to_string(),
 8392                        end: "]".to_string(),
 8393                        close: false,
 8394                        surround: true,
 8395                        newline: true,
 8396                    },
 8397                ],
 8398                ..Default::default()
 8399            },
 8400            autoclose_before: "})]".to_string(),
 8401            ..Default::default()
 8402        },
 8403        Some(tree_sitter_rust::LANGUAGE.into()),
 8404    ));
 8405
 8406    cx.language_registry().add(language.clone());
 8407    cx.update_buffer(|buffer, cx| {
 8408        buffer.set_language(Some(language), cx);
 8409    });
 8410
 8411    cx.set_state(
 8412        &"
 8413            {(ˇ)}
 8414            [[ˇ]]
 8415            {(ˇ)}
 8416        "
 8417        .unindent(),
 8418    );
 8419
 8420    cx.update_editor(|editor, window, cx| {
 8421        editor.backspace(&Default::default(), window, cx);
 8422        editor.backspace(&Default::default(), window, cx);
 8423    });
 8424
 8425    cx.assert_editor_state(
 8426        &"
 8427            ˇ
 8428            ˇ]]
 8429            ˇ
 8430        "
 8431        .unindent(),
 8432    );
 8433
 8434    cx.update_editor(|editor, window, cx| {
 8435        editor.handle_input("{", window, cx);
 8436        editor.handle_input("{", window, cx);
 8437        editor.move_right(&MoveRight, window, cx);
 8438        editor.move_right(&MoveRight, window, cx);
 8439        editor.move_left(&MoveLeft, window, cx);
 8440        editor.move_left(&MoveLeft, window, cx);
 8441        editor.backspace(&Default::default(), window, cx);
 8442    });
 8443
 8444    cx.assert_editor_state(
 8445        &"
 8446            {ˇ}
 8447            {ˇ}]]
 8448            {ˇ}
 8449        "
 8450        .unindent(),
 8451    );
 8452
 8453    cx.update_editor(|editor, window, cx| {
 8454        editor.backspace(&Default::default(), window, cx);
 8455    });
 8456
 8457    cx.assert_editor_state(
 8458        &"
 8459            ˇ
 8460            ˇ]]
 8461            ˇ
 8462        "
 8463        .unindent(),
 8464    );
 8465}
 8466
 8467#[gpui::test]
 8468async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8469    init_test(cx, |_| {});
 8470
 8471    let language = Arc::new(Language::new(
 8472        LanguageConfig::default(),
 8473        Some(tree_sitter_rust::LANGUAGE.into()),
 8474    ));
 8475
 8476    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8477    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8478    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8479    editor
 8480        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8481        .await;
 8482
 8483    editor.update_in(cx, |editor, window, cx| {
 8484        editor.set_auto_replace_emoji_shortcode(true);
 8485
 8486        editor.handle_input("Hello ", window, cx);
 8487        editor.handle_input(":wave", window, cx);
 8488        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8489
 8490        editor.handle_input(":", window, cx);
 8491        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8492
 8493        editor.handle_input(" :smile", window, cx);
 8494        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8495
 8496        editor.handle_input(":", window, cx);
 8497        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8498
 8499        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8500        editor.handle_input(":wave", window, cx);
 8501        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8502
 8503        editor.handle_input(":", window, cx);
 8504        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8505
 8506        editor.handle_input(":1", window, cx);
 8507        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8508
 8509        editor.handle_input(":", window, cx);
 8510        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8511
 8512        // Ensure shortcode does not get replaced when it is part of a word
 8513        editor.handle_input(" Test:wave", window, cx);
 8514        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8515
 8516        editor.handle_input(":", window, cx);
 8517        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8518
 8519        editor.set_auto_replace_emoji_shortcode(false);
 8520
 8521        // Ensure shortcode does not get replaced when auto replace is off
 8522        editor.handle_input(" :wave", window, cx);
 8523        assert_eq!(
 8524            editor.text(cx),
 8525            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8526        );
 8527
 8528        editor.handle_input(":", window, cx);
 8529        assert_eq!(
 8530            editor.text(cx),
 8531            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8532        );
 8533    });
 8534}
 8535
 8536#[gpui::test]
 8537async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8538    init_test(cx, |_| {});
 8539
 8540    let (text, insertion_ranges) = marked_text_ranges(
 8541        indoc! {"
 8542            ˇ
 8543        "},
 8544        false,
 8545    );
 8546
 8547    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8548    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8549
 8550    _ = editor.update_in(cx, |editor, window, cx| {
 8551        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8552
 8553        editor
 8554            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8555            .unwrap();
 8556
 8557        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8558            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8559            assert_eq!(editor.text(cx), expected_text);
 8560            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8561        }
 8562
 8563        assert(
 8564            editor,
 8565            cx,
 8566            indoc! {"
 8567            type «» =•
 8568            "},
 8569        );
 8570
 8571        assert!(editor.context_menu_visible(), "There should be a matches");
 8572    });
 8573}
 8574
 8575#[gpui::test]
 8576async fn test_snippets(cx: &mut TestAppContext) {
 8577    init_test(cx, |_| {});
 8578
 8579    let mut cx = EditorTestContext::new(cx).await;
 8580
 8581    cx.set_state(indoc! {"
 8582        a.ˇ b
 8583        a.ˇ b
 8584        a.ˇ b
 8585    "});
 8586
 8587    cx.update_editor(|editor, window, cx| {
 8588        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8589        let insertion_ranges = editor
 8590            .selections
 8591            .all(cx)
 8592            .iter()
 8593            .map(|s| s.range().clone())
 8594            .collect::<Vec<_>>();
 8595        editor
 8596            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8597            .unwrap();
 8598    });
 8599
 8600    cx.assert_editor_state(indoc! {"
 8601        a.f(«oneˇ», two, «threeˇ») b
 8602        a.f(«oneˇ», two, «threeˇ») b
 8603        a.f(«oneˇ», two, «threeˇ») b
 8604    "});
 8605
 8606    // Can't move earlier than the first tab stop
 8607    cx.update_editor(|editor, window, cx| {
 8608        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8609    });
 8610    cx.assert_editor_state(indoc! {"
 8611        a.f(«oneˇ», two, «threeˇ») b
 8612        a.f(«oneˇ», two, «threeˇ») b
 8613        a.f(«oneˇ», two, «threeˇ») b
 8614    "});
 8615
 8616    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8617    cx.assert_editor_state(indoc! {"
 8618        a.f(one, «twoˇ», three) b
 8619        a.f(one, «twoˇ», three) b
 8620        a.f(one, «twoˇ», three) b
 8621    "});
 8622
 8623    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 8624    cx.assert_editor_state(indoc! {"
 8625        a.f(«oneˇ», two, «threeˇ») b
 8626        a.f(«oneˇ», two, «threeˇ») b
 8627        a.f(«oneˇ», two, «threeˇ») b
 8628    "});
 8629
 8630    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8631    cx.assert_editor_state(indoc! {"
 8632        a.f(one, «twoˇ», three) b
 8633        a.f(one, «twoˇ», three) b
 8634        a.f(one, «twoˇ», three) b
 8635    "});
 8636    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8637    cx.assert_editor_state(indoc! {"
 8638        a.f(one, two, three)ˇ b
 8639        a.f(one, two, three)ˇ b
 8640        a.f(one, two, three)ˇ b
 8641    "});
 8642
 8643    // As soon as the last tab stop is reached, snippet state is gone
 8644    cx.update_editor(|editor, window, cx| {
 8645        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8646    });
 8647    cx.assert_editor_state(indoc! {"
 8648        a.f(one, two, three)ˇ b
 8649        a.f(one, two, three)ˇ b
 8650        a.f(one, two, three)ˇ b
 8651    "});
 8652}
 8653
 8654#[gpui::test]
 8655async fn test_snippet_indentation(cx: &mut TestAppContext) {
 8656    init_test(cx, |_| {});
 8657
 8658    let mut cx = EditorTestContext::new(cx).await;
 8659
 8660    cx.update_editor(|editor, window, cx| {
 8661        let snippet = Snippet::parse(indoc! {"
 8662            /*
 8663             * Multiline comment with leading indentation
 8664             *
 8665             * $1
 8666             */
 8667            $0"})
 8668        .unwrap();
 8669        let insertion_ranges = editor
 8670            .selections
 8671            .all(cx)
 8672            .iter()
 8673            .map(|s| s.range().clone())
 8674            .collect::<Vec<_>>();
 8675        editor
 8676            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8677            .unwrap();
 8678    });
 8679
 8680    cx.assert_editor_state(indoc! {"
 8681        /*
 8682         * Multiline comment with leading indentation
 8683         *
 8684         * ˇ
 8685         */
 8686    "});
 8687
 8688    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8689    cx.assert_editor_state(indoc! {"
 8690        /*
 8691         * Multiline comment with leading indentation
 8692         *
 8693         *•
 8694         */
 8695        ˇ"});
 8696}
 8697
 8698#[gpui::test]
 8699async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8700    init_test(cx, |_| {});
 8701
 8702    let fs = FakeFs::new(cx.executor());
 8703    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8704
 8705    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8706
 8707    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8708    language_registry.add(rust_lang());
 8709    let mut fake_servers = language_registry.register_fake_lsp(
 8710        "Rust",
 8711        FakeLspAdapter {
 8712            capabilities: lsp::ServerCapabilities {
 8713                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8714                ..Default::default()
 8715            },
 8716            ..Default::default()
 8717        },
 8718    );
 8719
 8720    let buffer = project
 8721        .update(cx, |project, cx| {
 8722            project.open_local_buffer(path!("/file.rs"), cx)
 8723        })
 8724        .await
 8725        .unwrap();
 8726
 8727    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8728    let (editor, cx) = cx.add_window_view(|window, cx| {
 8729        build_editor_with_project(project.clone(), buffer, window, cx)
 8730    });
 8731    editor.update_in(cx, |editor, window, cx| {
 8732        editor.set_text("one\ntwo\nthree\n", window, cx)
 8733    });
 8734    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8735
 8736    cx.executor().start_waiting();
 8737    let fake_server = fake_servers.next().await.unwrap();
 8738
 8739    {
 8740        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8741            move |params, _| async move {
 8742                assert_eq!(
 8743                    params.text_document.uri,
 8744                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8745                );
 8746                assert_eq!(params.options.tab_size, 4);
 8747                Ok(Some(vec![lsp::TextEdit::new(
 8748                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8749                    ", ".to_string(),
 8750                )]))
 8751            },
 8752        );
 8753        let save = editor
 8754            .update_in(cx, |editor, window, cx| {
 8755                editor.save(true, project.clone(), window, cx)
 8756            })
 8757            .unwrap();
 8758        cx.executor().start_waiting();
 8759        save.await;
 8760
 8761        assert_eq!(
 8762            editor.update(cx, |editor, cx| editor.text(cx)),
 8763            "one, two\nthree\n"
 8764        );
 8765        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8766    }
 8767
 8768    {
 8769        editor.update_in(cx, |editor, window, cx| {
 8770            editor.set_text("one\ntwo\nthree\n", window, cx)
 8771        });
 8772        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8773
 8774        // Ensure we can still save even if formatting hangs.
 8775        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8776            move |params, _| async move {
 8777                assert_eq!(
 8778                    params.text_document.uri,
 8779                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8780                );
 8781                futures::future::pending::<()>().await;
 8782                unreachable!()
 8783            },
 8784        );
 8785        let save = editor
 8786            .update_in(cx, |editor, window, cx| {
 8787                editor.save(true, project.clone(), window, cx)
 8788            })
 8789            .unwrap();
 8790        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8791        cx.executor().start_waiting();
 8792        save.await;
 8793        assert_eq!(
 8794            editor.update(cx, |editor, cx| editor.text(cx)),
 8795            "one\ntwo\nthree\n"
 8796        );
 8797    }
 8798
 8799    // For non-dirty buffer, no formatting request should be sent
 8800    {
 8801        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8802
 8803        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8804            panic!("Should not be invoked on non-dirty buffer");
 8805        });
 8806        let save = editor
 8807            .update_in(cx, |editor, window, cx| {
 8808                editor.save(true, project.clone(), window, cx)
 8809            })
 8810            .unwrap();
 8811        cx.executor().start_waiting();
 8812        save.await;
 8813    }
 8814
 8815    // Set rust language override and assert overridden tabsize is sent to language server
 8816    update_test_language_settings(cx, |settings| {
 8817        settings.languages.insert(
 8818            "Rust".into(),
 8819            LanguageSettingsContent {
 8820                tab_size: NonZeroU32::new(8),
 8821                ..Default::default()
 8822            },
 8823        );
 8824    });
 8825
 8826    {
 8827        editor.update_in(cx, |editor, window, cx| {
 8828            editor.set_text("somehting_new\n", window, cx)
 8829        });
 8830        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8831        let _formatting_request_signal = fake_server
 8832            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8833                assert_eq!(
 8834                    params.text_document.uri,
 8835                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8836                );
 8837                assert_eq!(params.options.tab_size, 8);
 8838                Ok(Some(vec![]))
 8839            });
 8840        let save = editor
 8841            .update_in(cx, |editor, window, cx| {
 8842                editor.save(true, project.clone(), window, cx)
 8843            })
 8844            .unwrap();
 8845        cx.executor().start_waiting();
 8846        save.await;
 8847    }
 8848}
 8849
 8850#[gpui::test]
 8851async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8852    init_test(cx, |_| {});
 8853
 8854    let cols = 4;
 8855    let rows = 10;
 8856    let sample_text_1 = sample_text(rows, cols, 'a');
 8857    assert_eq!(
 8858        sample_text_1,
 8859        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8860    );
 8861    let sample_text_2 = sample_text(rows, cols, 'l');
 8862    assert_eq!(
 8863        sample_text_2,
 8864        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8865    );
 8866    let sample_text_3 = sample_text(rows, cols, 'v');
 8867    assert_eq!(
 8868        sample_text_3,
 8869        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8870    );
 8871
 8872    let fs = FakeFs::new(cx.executor());
 8873    fs.insert_tree(
 8874        path!("/a"),
 8875        json!({
 8876            "main.rs": sample_text_1,
 8877            "other.rs": sample_text_2,
 8878            "lib.rs": sample_text_3,
 8879        }),
 8880    )
 8881    .await;
 8882
 8883    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8884    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8885    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8886
 8887    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8888    language_registry.add(rust_lang());
 8889    let mut fake_servers = language_registry.register_fake_lsp(
 8890        "Rust",
 8891        FakeLspAdapter {
 8892            capabilities: lsp::ServerCapabilities {
 8893                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8894                ..Default::default()
 8895            },
 8896            ..Default::default()
 8897        },
 8898    );
 8899
 8900    let worktree = project.update(cx, |project, cx| {
 8901        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8902        assert_eq!(worktrees.len(), 1);
 8903        worktrees.pop().unwrap()
 8904    });
 8905    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8906
 8907    let buffer_1 = project
 8908        .update(cx, |project, cx| {
 8909            project.open_buffer((worktree_id, "main.rs"), cx)
 8910        })
 8911        .await
 8912        .unwrap();
 8913    let buffer_2 = project
 8914        .update(cx, |project, cx| {
 8915            project.open_buffer((worktree_id, "other.rs"), cx)
 8916        })
 8917        .await
 8918        .unwrap();
 8919    let buffer_3 = project
 8920        .update(cx, |project, cx| {
 8921            project.open_buffer((worktree_id, "lib.rs"), cx)
 8922        })
 8923        .await
 8924        .unwrap();
 8925
 8926    let multi_buffer = cx.new(|cx| {
 8927        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8928        multi_buffer.push_excerpts(
 8929            buffer_1.clone(),
 8930            [
 8931                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8932                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8933                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8934            ],
 8935            cx,
 8936        );
 8937        multi_buffer.push_excerpts(
 8938            buffer_2.clone(),
 8939            [
 8940                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8941                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8942                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8943            ],
 8944            cx,
 8945        );
 8946        multi_buffer.push_excerpts(
 8947            buffer_3.clone(),
 8948            [
 8949                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8950                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8951                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8952            ],
 8953            cx,
 8954        );
 8955        multi_buffer
 8956    });
 8957    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8958        Editor::new(
 8959            EditorMode::full(),
 8960            multi_buffer,
 8961            Some(project.clone()),
 8962            window,
 8963            cx,
 8964        )
 8965    });
 8966
 8967    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8968        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8969            s.select_ranges(Some(1..2))
 8970        });
 8971        editor.insert("|one|two|three|", window, cx);
 8972    });
 8973    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8974    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8975        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8976            s.select_ranges(Some(60..70))
 8977        });
 8978        editor.insert("|four|five|six|", window, cx);
 8979    });
 8980    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8981
 8982    // First two buffers should be edited, but not the third one.
 8983    assert_eq!(
 8984        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8985        "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}",
 8986    );
 8987    buffer_1.update(cx, |buffer, _| {
 8988        assert!(buffer.is_dirty());
 8989        assert_eq!(
 8990            buffer.text(),
 8991            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8992        )
 8993    });
 8994    buffer_2.update(cx, |buffer, _| {
 8995        assert!(buffer.is_dirty());
 8996        assert_eq!(
 8997            buffer.text(),
 8998            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8999        )
 9000    });
 9001    buffer_3.update(cx, |buffer, _| {
 9002        assert!(!buffer.is_dirty());
 9003        assert_eq!(buffer.text(), sample_text_3,)
 9004    });
 9005    cx.executor().run_until_parked();
 9006
 9007    cx.executor().start_waiting();
 9008    let save = multi_buffer_editor
 9009        .update_in(cx, |editor, window, cx| {
 9010            editor.save(true, project.clone(), window, cx)
 9011        })
 9012        .unwrap();
 9013
 9014    let fake_server = fake_servers.next().await.unwrap();
 9015    fake_server
 9016        .server
 9017        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9018            Ok(Some(vec![lsp::TextEdit::new(
 9019                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9020                format!("[{} formatted]", params.text_document.uri),
 9021            )]))
 9022        })
 9023        .detach();
 9024    save.await;
 9025
 9026    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9027    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9028    assert_eq!(
 9029        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9030        uri!(
 9031            "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}"
 9032        ),
 9033    );
 9034    buffer_1.update(cx, |buffer, _| {
 9035        assert!(!buffer.is_dirty());
 9036        assert_eq!(
 9037            buffer.text(),
 9038            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9039        )
 9040    });
 9041    buffer_2.update(cx, |buffer, _| {
 9042        assert!(!buffer.is_dirty());
 9043        assert_eq!(
 9044            buffer.text(),
 9045            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9046        )
 9047    });
 9048    buffer_3.update(cx, |buffer, _| {
 9049        assert!(!buffer.is_dirty());
 9050        assert_eq!(buffer.text(), sample_text_3,)
 9051    });
 9052}
 9053
 9054#[gpui::test]
 9055async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9056    init_test(cx, |_| {});
 9057
 9058    let fs = FakeFs::new(cx.executor());
 9059    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9060
 9061    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9062
 9063    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9064    language_registry.add(rust_lang());
 9065    let mut fake_servers = language_registry.register_fake_lsp(
 9066        "Rust",
 9067        FakeLspAdapter {
 9068            capabilities: lsp::ServerCapabilities {
 9069                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9070                ..Default::default()
 9071            },
 9072            ..Default::default()
 9073        },
 9074    );
 9075
 9076    let buffer = project
 9077        .update(cx, |project, cx| {
 9078            project.open_local_buffer(path!("/file.rs"), cx)
 9079        })
 9080        .await
 9081        .unwrap();
 9082
 9083    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9084    let (editor, cx) = cx.add_window_view(|window, cx| {
 9085        build_editor_with_project(project.clone(), buffer, window, cx)
 9086    });
 9087    editor.update_in(cx, |editor, window, cx| {
 9088        editor.set_text("one\ntwo\nthree\n", window, cx)
 9089    });
 9090    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9091
 9092    cx.executor().start_waiting();
 9093    let fake_server = fake_servers.next().await.unwrap();
 9094
 9095    let save = editor
 9096        .update_in(cx, |editor, window, cx| {
 9097            editor.save(true, project.clone(), window, cx)
 9098        })
 9099        .unwrap();
 9100    fake_server
 9101        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9102            assert_eq!(
 9103                params.text_document.uri,
 9104                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9105            );
 9106            assert_eq!(params.options.tab_size, 4);
 9107            Ok(Some(vec![lsp::TextEdit::new(
 9108                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9109                ", ".to_string(),
 9110            )]))
 9111        })
 9112        .next()
 9113        .await;
 9114    cx.executor().start_waiting();
 9115    save.await;
 9116    assert_eq!(
 9117        editor.update(cx, |editor, cx| editor.text(cx)),
 9118        "one, two\nthree\n"
 9119    );
 9120    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9121
 9122    editor.update_in(cx, |editor, window, cx| {
 9123        editor.set_text("one\ntwo\nthree\n", window, cx)
 9124    });
 9125    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9126
 9127    // Ensure we can still save even if formatting hangs.
 9128    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9129        move |params, _| async move {
 9130            assert_eq!(
 9131                params.text_document.uri,
 9132                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9133            );
 9134            futures::future::pending::<()>().await;
 9135            unreachable!()
 9136        },
 9137    );
 9138    let save = editor
 9139        .update_in(cx, |editor, window, cx| {
 9140            editor.save(true, project.clone(), window, cx)
 9141        })
 9142        .unwrap();
 9143    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9144    cx.executor().start_waiting();
 9145    save.await;
 9146    assert_eq!(
 9147        editor.update(cx, |editor, cx| editor.text(cx)),
 9148        "one\ntwo\nthree\n"
 9149    );
 9150    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9151
 9152    // For non-dirty buffer, no formatting request should be sent
 9153    let save = editor
 9154        .update_in(cx, |editor, window, cx| {
 9155            editor.save(true, project.clone(), window, cx)
 9156        })
 9157        .unwrap();
 9158    let _pending_format_request = fake_server
 9159        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9160            panic!("Should not be invoked on non-dirty buffer");
 9161        })
 9162        .next();
 9163    cx.executor().start_waiting();
 9164    save.await;
 9165
 9166    // Set Rust language override and assert overridden tabsize is sent to language server
 9167    update_test_language_settings(cx, |settings| {
 9168        settings.languages.insert(
 9169            "Rust".into(),
 9170            LanguageSettingsContent {
 9171                tab_size: NonZeroU32::new(8),
 9172                ..Default::default()
 9173            },
 9174        );
 9175    });
 9176
 9177    editor.update_in(cx, |editor, window, cx| {
 9178        editor.set_text("somehting_new\n", window, cx)
 9179    });
 9180    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9181    let save = editor
 9182        .update_in(cx, |editor, window, cx| {
 9183            editor.save(true, project.clone(), window, cx)
 9184        })
 9185        .unwrap();
 9186    fake_server
 9187        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9188            assert_eq!(
 9189                params.text_document.uri,
 9190                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9191            );
 9192            assert_eq!(params.options.tab_size, 8);
 9193            Ok(Some(Vec::new()))
 9194        })
 9195        .next()
 9196        .await;
 9197    save.await;
 9198}
 9199
 9200#[gpui::test]
 9201async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9202    init_test(cx, |settings| {
 9203        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9204            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9205        ))
 9206    });
 9207
 9208    let fs = FakeFs::new(cx.executor());
 9209    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9210
 9211    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9212
 9213    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9214    language_registry.add(Arc::new(Language::new(
 9215        LanguageConfig {
 9216            name: "Rust".into(),
 9217            matcher: LanguageMatcher {
 9218                path_suffixes: vec!["rs".to_string()],
 9219                ..Default::default()
 9220            },
 9221            ..LanguageConfig::default()
 9222        },
 9223        Some(tree_sitter_rust::LANGUAGE.into()),
 9224    )));
 9225    update_test_language_settings(cx, |settings| {
 9226        // Enable Prettier formatting for the same buffer, and ensure
 9227        // LSP is called instead of Prettier.
 9228        settings.defaults.prettier = Some(PrettierSettings {
 9229            allowed: true,
 9230            ..PrettierSettings::default()
 9231        });
 9232    });
 9233    let mut fake_servers = language_registry.register_fake_lsp(
 9234        "Rust",
 9235        FakeLspAdapter {
 9236            capabilities: lsp::ServerCapabilities {
 9237                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9238                ..Default::default()
 9239            },
 9240            ..Default::default()
 9241        },
 9242    );
 9243
 9244    let buffer = project
 9245        .update(cx, |project, cx| {
 9246            project.open_local_buffer(path!("/file.rs"), cx)
 9247        })
 9248        .await
 9249        .unwrap();
 9250
 9251    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9252    let (editor, cx) = cx.add_window_view(|window, cx| {
 9253        build_editor_with_project(project.clone(), buffer, window, cx)
 9254    });
 9255    editor.update_in(cx, |editor, window, cx| {
 9256        editor.set_text("one\ntwo\nthree\n", window, cx)
 9257    });
 9258
 9259    cx.executor().start_waiting();
 9260    let fake_server = fake_servers.next().await.unwrap();
 9261
 9262    let format = editor
 9263        .update_in(cx, |editor, window, cx| {
 9264            editor.perform_format(
 9265                project.clone(),
 9266                FormatTrigger::Manual,
 9267                FormatTarget::Buffers,
 9268                window,
 9269                cx,
 9270            )
 9271        })
 9272        .unwrap();
 9273    fake_server
 9274        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9275            assert_eq!(
 9276                params.text_document.uri,
 9277                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9278            );
 9279            assert_eq!(params.options.tab_size, 4);
 9280            Ok(Some(vec![lsp::TextEdit::new(
 9281                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9282                ", ".to_string(),
 9283            )]))
 9284        })
 9285        .next()
 9286        .await;
 9287    cx.executor().start_waiting();
 9288    format.await;
 9289    assert_eq!(
 9290        editor.update(cx, |editor, cx| editor.text(cx)),
 9291        "one, two\nthree\n"
 9292    );
 9293
 9294    editor.update_in(cx, |editor, window, cx| {
 9295        editor.set_text("one\ntwo\nthree\n", window, cx)
 9296    });
 9297    // Ensure we don't lock if formatting hangs.
 9298    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9299        move |params, _| async move {
 9300            assert_eq!(
 9301                params.text_document.uri,
 9302                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9303            );
 9304            futures::future::pending::<()>().await;
 9305            unreachable!()
 9306        },
 9307    );
 9308    let format = editor
 9309        .update_in(cx, |editor, window, cx| {
 9310            editor.perform_format(
 9311                project,
 9312                FormatTrigger::Manual,
 9313                FormatTarget::Buffers,
 9314                window,
 9315                cx,
 9316            )
 9317        })
 9318        .unwrap();
 9319    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9320    cx.executor().start_waiting();
 9321    format.await;
 9322    assert_eq!(
 9323        editor.update(cx, |editor, cx| editor.text(cx)),
 9324        "one\ntwo\nthree\n"
 9325    );
 9326}
 9327
 9328#[gpui::test]
 9329async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9330    init_test(cx, |settings| {
 9331        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9332        settings.defaults.formatter =
 9333            Some(language_settings::SelectedFormatter::List(FormatterList(
 9334                vec![
 9335                    Formatter::LanguageServer { name: None },
 9336                    Formatter::CodeActions(
 9337                        [
 9338                            ("code-action-1".into(), true),
 9339                            ("code-action-2".into(), true),
 9340                        ]
 9341                        .into_iter()
 9342                        .collect(),
 9343                    ),
 9344                ]
 9345                .into(),
 9346            )))
 9347    });
 9348
 9349    let fs = FakeFs::new(cx.executor());
 9350    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9351        .await;
 9352
 9353    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9354    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9355    language_registry.add(rust_lang());
 9356
 9357    let mut fake_servers = language_registry.register_fake_lsp(
 9358        "Rust",
 9359        FakeLspAdapter {
 9360            capabilities: lsp::ServerCapabilities {
 9361                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9362                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9363                    commands: vec!["the-command-for-code-action-1".into()],
 9364                    ..Default::default()
 9365                }),
 9366                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9367                ..Default::default()
 9368            },
 9369            ..Default::default()
 9370        },
 9371    );
 9372
 9373    let buffer = project
 9374        .update(cx, |project, cx| {
 9375            project.open_local_buffer(path!("/file.rs"), cx)
 9376        })
 9377        .await
 9378        .unwrap();
 9379
 9380    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9381    let (editor, cx) = cx.add_window_view(|window, cx| {
 9382        build_editor_with_project(project.clone(), buffer, window, cx)
 9383    });
 9384
 9385    cx.executor().start_waiting();
 9386
 9387    let fake_server = fake_servers.next().await.unwrap();
 9388    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9389        move |_params, _| async move {
 9390            Ok(Some(vec![lsp::TextEdit::new(
 9391                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9392                "applied-formatting\n".to_string(),
 9393            )]))
 9394        },
 9395    );
 9396    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9397        move |params, _| async move {
 9398            assert_eq!(
 9399                params.context.only,
 9400                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9401            );
 9402            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9403            Ok(Some(vec![
 9404                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9405                    kind: Some("code-action-1".into()),
 9406                    edit: Some(lsp::WorkspaceEdit::new(
 9407                        [(
 9408                            uri.clone(),
 9409                            vec![lsp::TextEdit::new(
 9410                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9411                                "applied-code-action-1-edit\n".to_string(),
 9412                            )],
 9413                        )]
 9414                        .into_iter()
 9415                        .collect(),
 9416                    )),
 9417                    command: Some(lsp::Command {
 9418                        command: "the-command-for-code-action-1".into(),
 9419                        ..Default::default()
 9420                    }),
 9421                    ..Default::default()
 9422                }),
 9423                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9424                    kind: Some("code-action-2".into()),
 9425                    edit: Some(lsp::WorkspaceEdit::new(
 9426                        [(
 9427                            uri.clone(),
 9428                            vec![lsp::TextEdit::new(
 9429                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9430                                "applied-code-action-2-edit\n".to_string(),
 9431                            )],
 9432                        )]
 9433                        .into_iter()
 9434                        .collect(),
 9435                    )),
 9436                    ..Default::default()
 9437                }),
 9438            ]))
 9439        },
 9440    );
 9441
 9442    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9443        move |params, _| async move { Ok(params) }
 9444    });
 9445
 9446    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9447    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9448        let fake = fake_server.clone();
 9449        let lock = command_lock.clone();
 9450        move |params, _| {
 9451            assert_eq!(params.command, "the-command-for-code-action-1");
 9452            let fake = fake.clone();
 9453            let lock = lock.clone();
 9454            async move {
 9455                lock.lock().await;
 9456                fake.server
 9457                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9458                        label: None,
 9459                        edit: lsp::WorkspaceEdit {
 9460                            changes: Some(
 9461                                [(
 9462                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9463                                    vec![lsp::TextEdit {
 9464                                        range: lsp::Range::new(
 9465                                            lsp::Position::new(0, 0),
 9466                                            lsp::Position::new(0, 0),
 9467                                        ),
 9468                                        new_text: "applied-code-action-1-command\n".into(),
 9469                                    }],
 9470                                )]
 9471                                .into_iter()
 9472                                .collect(),
 9473                            ),
 9474                            ..Default::default()
 9475                        },
 9476                    })
 9477                    .await
 9478                    .into_response()
 9479                    .unwrap();
 9480                Ok(Some(json!(null)))
 9481            }
 9482        }
 9483    });
 9484
 9485    cx.executor().start_waiting();
 9486    editor
 9487        .update_in(cx, |editor, window, cx| {
 9488            editor.perform_format(
 9489                project.clone(),
 9490                FormatTrigger::Manual,
 9491                FormatTarget::Buffers,
 9492                window,
 9493                cx,
 9494            )
 9495        })
 9496        .unwrap()
 9497        .await;
 9498    editor.update(cx, |editor, cx| {
 9499        assert_eq!(
 9500            editor.text(cx),
 9501            r#"
 9502                applied-code-action-2-edit
 9503                applied-code-action-1-command
 9504                applied-code-action-1-edit
 9505                applied-formatting
 9506                one
 9507                two
 9508                three
 9509            "#
 9510            .unindent()
 9511        );
 9512    });
 9513
 9514    editor.update_in(cx, |editor, window, cx| {
 9515        editor.undo(&Default::default(), window, cx);
 9516        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9517    });
 9518
 9519    // Perform a manual edit while waiting for an LSP command
 9520    // that's being run as part of a formatting code action.
 9521    let lock_guard = command_lock.lock().await;
 9522    let format = editor
 9523        .update_in(cx, |editor, window, cx| {
 9524            editor.perform_format(
 9525                project.clone(),
 9526                FormatTrigger::Manual,
 9527                FormatTarget::Buffers,
 9528                window,
 9529                cx,
 9530            )
 9531        })
 9532        .unwrap();
 9533    cx.run_until_parked();
 9534    editor.update(cx, |editor, cx| {
 9535        assert_eq!(
 9536            editor.text(cx),
 9537            r#"
 9538                applied-code-action-1-edit
 9539                applied-formatting
 9540                one
 9541                two
 9542                three
 9543            "#
 9544            .unindent()
 9545        );
 9546
 9547        editor.buffer.update(cx, |buffer, cx| {
 9548            let ix = buffer.len(cx);
 9549            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9550        });
 9551    });
 9552
 9553    // Allow the LSP command to proceed. Because the buffer was edited,
 9554    // the second code action will not be run.
 9555    drop(lock_guard);
 9556    format.await;
 9557    editor.update_in(cx, |editor, window, cx| {
 9558        assert_eq!(
 9559            editor.text(cx),
 9560            r#"
 9561                applied-code-action-1-command
 9562                applied-code-action-1-edit
 9563                applied-formatting
 9564                one
 9565                two
 9566                three
 9567                edited
 9568            "#
 9569            .unindent()
 9570        );
 9571
 9572        // The manual edit is undone first, because it is the last thing the user did
 9573        // (even though the command completed afterwards).
 9574        editor.undo(&Default::default(), window, cx);
 9575        assert_eq!(
 9576            editor.text(cx),
 9577            r#"
 9578                applied-code-action-1-command
 9579                applied-code-action-1-edit
 9580                applied-formatting
 9581                one
 9582                two
 9583                three
 9584            "#
 9585            .unindent()
 9586        );
 9587
 9588        // All the formatting (including the command, which completed after the manual edit)
 9589        // is undone together.
 9590        editor.undo(&Default::default(), window, cx);
 9591        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9592    });
 9593}
 9594
 9595#[gpui::test]
 9596async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9597    init_test(cx, |settings| {
 9598        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9599            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9600        ))
 9601    });
 9602
 9603    let fs = FakeFs::new(cx.executor());
 9604    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9605
 9606    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9607
 9608    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9609    language_registry.add(Arc::new(Language::new(
 9610        LanguageConfig {
 9611            name: "TypeScript".into(),
 9612            matcher: LanguageMatcher {
 9613                path_suffixes: vec!["ts".to_string()],
 9614                ..Default::default()
 9615            },
 9616            ..LanguageConfig::default()
 9617        },
 9618        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9619    )));
 9620    update_test_language_settings(cx, |settings| {
 9621        settings.defaults.prettier = Some(PrettierSettings {
 9622            allowed: true,
 9623            ..PrettierSettings::default()
 9624        });
 9625    });
 9626    let mut fake_servers = language_registry.register_fake_lsp(
 9627        "TypeScript",
 9628        FakeLspAdapter {
 9629            capabilities: lsp::ServerCapabilities {
 9630                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9631                ..Default::default()
 9632            },
 9633            ..Default::default()
 9634        },
 9635    );
 9636
 9637    let buffer = project
 9638        .update(cx, |project, cx| {
 9639            project.open_local_buffer(path!("/file.ts"), cx)
 9640        })
 9641        .await
 9642        .unwrap();
 9643
 9644    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9645    let (editor, cx) = cx.add_window_view(|window, cx| {
 9646        build_editor_with_project(project.clone(), buffer, window, cx)
 9647    });
 9648    editor.update_in(cx, |editor, window, cx| {
 9649        editor.set_text(
 9650            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9651            window,
 9652            cx,
 9653        )
 9654    });
 9655
 9656    cx.executor().start_waiting();
 9657    let fake_server = fake_servers.next().await.unwrap();
 9658
 9659    let format = editor
 9660        .update_in(cx, |editor, window, cx| {
 9661            editor.perform_code_action_kind(
 9662                project.clone(),
 9663                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9664                window,
 9665                cx,
 9666            )
 9667        })
 9668        .unwrap();
 9669    fake_server
 9670        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9671            assert_eq!(
 9672                params.text_document.uri,
 9673                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9674            );
 9675            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9676                lsp::CodeAction {
 9677                    title: "Organize Imports".to_string(),
 9678                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9679                    edit: Some(lsp::WorkspaceEdit {
 9680                        changes: Some(
 9681                            [(
 9682                                params.text_document.uri.clone(),
 9683                                vec![lsp::TextEdit::new(
 9684                                    lsp::Range::new(
 9685                                        lsp::Position::new(1, 0),
 9686                                        lsp::Position::new(2, 0),
 9687                                    ),
 9688                                    "".to_string(),
 9689                                )],
 9690                            )]
 9691                            .into_iter()
 9692                            .collect(),
 9693                        ),
 9694                        ..Default::default()
 9695                    }),
 9696                    ..Default::default()
 9697                },
 9698            )]))
 9699        })
 9700        .next()
 9701        .await;
 9702    cx.executor().start_waiting();
 9703    format.await;
 9704    assert_eq!(
 9705        editor.update(cx, |editor, cx| editor.text(cx)),
 9706        "import { a } from 'module';\n\nconst x = a;\n"
 9707    );
 9708
 9709    editor.update_in(cx, |editor, window, cx| {
 9710        editor.set_text(
 9711            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9712            window,
 9713            cx,
 9714        )
 9715    });
 9716    // Ensure we don't lock if code action hangs.
 9717    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9718        move |params, _| async move {
 9719            assert_eq!(
 9720                params.text_document.uri,
 9721                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9722            );
 9723            futures::future::pending::<()>().await;
 9724            unreachable!()
 9725        },
 9726    );
 9727    let format = editor
 9728        .update_in(cx, |editor, window, cx| {
 9729            editor.perform_code_action_kind(
 9730                project,
 9731                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9732                window,
 9733                cx,
 9734            )
 9735        })
 9736        .unwrap();
 9737    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9738    cx.executor().start_waiting();
 9739    format.await;
 9740    assert_eq!(
 9741        editor.update(cx, |editor, cx| editor.text(cx)),
 9742        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9743    );
 9744}
 9745
 9746#[gpui::test]
 9747async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9748    init_test(cx, |_| {});
 9749
 9750    let mut cx = EditorLspTestContext::new_rust(
 9751        lsp::ServerCapabilities {
 9752            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9753            ..Default::default()
 9754        },
 9755        cx,
 9756    )
 9757    .await;
 9758
 9759    cx.set_state(indoc! {"
 9760        one.twoˇ
 9761    "});
 9762
 9763    // The format request takes a long time. When it completes, it inserts
 9764    // a newline and an indent before the `.`
 9765    cx.lsp
 9766        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9767            let executor = cx.background_executor().clone();
 9768            async move {
 9769                executor.timer(Duration::from_millis(100)).await;
 9770                Ok(Some(vec![lsp::TextEdit {
 9771                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9772                    new_text: "\n    ".into(),
 9773                }]))
 9774            }
 9775        });
 9776
 9777    // Submit a format request.
 9778    let format_1 = cx
 9779        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9780        .unwrap();
 9781    cx.executor().run_until_parked();
 9782
 9783    // Submit a second format request.
 9784    let format_2 = cx
 9785        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9786        .unwrap();
 9787    cx.executor().run_until_parked();
 9788
 9789    // Wait for both format requests to complete
 9790    cx.executor().advance_clock(Duration::from_millis(200));
 9791    cx.executor().start_waiting();
 9792    format_1.await.unwrap();
 9793    cx.executor().start_waiting();
 9794    format_2.await.unwrap();
 9795
 9796    // The formatting edits only happens once.
 9797    cx.assert_editor_state(indoc! {"
 9798        one
 9799            .twoˇ
 9800    "});
 9801}
 9802
 9803#[gpui::test]
 9804async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9805    init_test(cx, |settings| {
 9806        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9807    });
 9808
 9809    let mut cx = EditorLspTestContext::new_rust(
 9810        lsp::ServerCapabilities {
 9811            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9812            ..Default::default()
 9813        },
 9814        cx,
 9815    )
 9816    .await;
 9817
 9818    // Set up a buffer white some trailing whitespace and no trailing newline.
 9819    cx.set_state(
 9820        &[
 9821            "one ",   //
 9822            "twoˇ",   //
 9823            "three ", //
 9824            "four",   //
 9825        ]
 9826        .join("\n"),
 9827    );
 9828
 9829    // Submit a format request.
 9830    let format = cx
 9831        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9832        .unwrap();
 9833
 9834    // Record which buffer changes have been sent to the language server
 9835    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9836    cx.lsp
 9837        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9838            let buffer_changes = buffer_changes.clone();
 9839            move |params, _| {
 9840                buffer_changes.lock().extend(
 9841                    params
 9842                        .content_changes
 9843                        .into_iter()
 9844                        .map(|e| (e.range.unwrap(), e.text)),
 9845                );
 9846            }
 9847        });
 9848
 9849    // Handle formatting requests to the language server.
 9850    cx.lsp
 9851        .set_request_handler::<lsp::request::Formatting, _, _>({
 9852            let buffer_changes = buffer_changes.clone();
 9853            move |_, _| {
 9854                // When formatting is requested, trailing whitespace has already been stripped,
 9855                // and the trailing newline has already been added.
 9856                assert_eq!(
 9857                    &buffer_changes.lock()[1..],
 9858                    &[
 9859                        (
 9860                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9861                            "".into()
 9862                        ),
 9863                        (
 9864                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9865                            "".into()
 9866                        ),
 9867                        (
 9868                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9869                            "\n".into()
 9870                        ),
 9871                    ]
 9872                );
 9873
 9874                // Insert blank lines between each line of the buffer.
 9875                async move {
 9876                    Ok(Some(vec![
 9877                        lsp::TextEdit {
 9878                            range: lsp::Range::new(
 9879                                lsp::Position::new(1, 0),
 9880                                lsp::Position::new(1, 0),
 9881                            ),
 9882                            new_text: "\n".into(),
 9883                        },
 9884                        lsp::TextEdit {
 9885                            range: lsp::Range::new(
 9886                                lsp::Position::new(2, 0),
 9887                                lsp::Position::new(2, 0),
 9888                            ),
 9889                            new_text: "\n".into(),
 9890                        },
 9891                    ]))
 9892                }
 9893            }
 9894        });
 9895
 9896    // After formatting the buffer, the trailing whitespace is stripped,
 9897    // a newline is appended, and the edits provided by the language server
 9898    // have been applied.
 9899    format.await.unwrap();
 9900    cx.assert_editor_state(
 9901        &[
 9902            "one",   //
 9903            "",      //
 9904            "twoˇ",  //
 9905            "",      //
 9906            "three", //
 9907            "four",  //
 9908            "",      //
 9909        ]
 9910        .join("\n"),
 9911    );
 9912
 9913    // Undoing the formatting undoes the trailing whitespace removal, the
 9914    // trailing newline, and the LSP edits.
 9915    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9916    cx.assert_editor_state(
 9917        &[
 9918            "one ",   //
 9919            "twoˇ",   //
 9920            "three ", //
 9921            "four",   //
 9922        ]
 9923        .join("\n"),
 9924    );
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9929    cx: &mut TestAppContext,
 9930) {
 9931    init_test(cx, |_| {});
 9932
 9933    cx.update(|cx| {
 9934        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9935            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9936                settings.auto_signature_help = Some(true);
 9937            });
 9938        });
 9939    });
 9940
 9941    let mut cx = EditorLspTestContext::new_rust(
 9942        lsp::ServerCapabilities {
 9943            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9944                ..Default::default()
 9945            }),
 9946            ..Default::default()
 9947        },
 9948        cx,
 9949    )
 9950    .await;
 9951
 9952    let language = Language::new(
 9953        LanguageConfig {
 9954            name: "Rust".into(),
 9955            brackets: BracketPairConfig {
 9956                pairs: vec![
 9957                    BracketPair {
 9958                        start: "{".to_string(),
 9959                        end: "}".to_string(),
 9960                        close: true,
 9961                        surround: true,
 9962                        newline: true,
 9963                    },
 9964                    BracketPair {
 9965                        start: "(".to_string(),
 9966                        end: ")".to_string(),
 9967                        close: true,
 9968                        surround: true,
 9969                        newline: true,
 9970                    },
 9971                    BracketPair {
 9972                        start: "/*".to_string(),
 9973                        end: " */".to_string(),
 9974                        close: true,
 9975                        surround: true,
 9976                        newline: true,
 9977                    },
 9978                    BracketPair {
 9979                        start: "[".to_string(),
 9980                        end: "]".to_string(),
 9981                        close: false,
 9982                        surround: false,
 9983                        newline: true,
 9984                    },
 9985                    BracketPair {
 9986                        start: "\"".to_string(),
 9987                        end: "\"".to_string(),
 9988                        close: true,
 9989                        surround: true,
 9990                        newline: false,
 9991                    },
 9992                    BracketPair {
 9993                        start: "<".to_string(),
 9994                        end: ">".to_string(),
 9995                        close: false,
 9996                        surround: true,
 9997                        newline: true,
 9998                    },
 9999                ],
10000                ..Default::default()
10001            },
10002            autoclose_before: "})]".to_string(),
10003            ..Default::default()
10004        },
10005        Some(tree_sitter_rust::LANGUAGE.into()),
10006    );
10007    let language = Arc::new(language);
10008
10009    cx.language_registry().add(language.clone());
10010    cx.update_buffer(|buffer, cx| {
10011        buffer.set_language(Some(language), cx);
10012    });
10013
10014    cx.set_state(
10015        &r#"
10016            fn main() {
10017                sampleˇ
10018            }
10019        "#
10020        .unindent(),
10021    );
10022
10023    cx.update_editor(|editor, window, cx| {
10024        editor.handle_input("(", window, cx);
10025    });
10026    cx.assert_editor_state(
10027        &"
10028            fn main() {
10029                sample(ˇ)
10030            }
10031        "
10032        .unindent(),
10033    );
10034
10035    let mocked_response = lsp::SignatureHelp {
10036        signatures: vec![lsp::SignatureInformation {
10037            label: "fn sample(param1: u8, param2: u8)".to_string(),
10038            documentation: None,
10039            parameters: Some(vec![
10040                lsp::ParameterInformation {
10041                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10042                    documentation: None,
10043                },
10044                lsp::ParameterInformation {
10045                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10046                    documentation: None,
10047                },
10048            ]),
10049            active_parameter: None,
10050        }],
10051        active_signature: Some(0),
10052        active_parameter: Some(0),
10053    };
10054    handle_signature_help_request(&mut cx, mocked_response).await;
10055
10056    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10057        .await;
10058
10059    cx.editor(|editor, _, _| {
10060        let signature_help_state = editor.signature_help_state.popover().cloned();
10061        assert_eq!(
10062            signature_help_state.unwrap().label,
10063            "param1: u8, param2: u8"
10064        );
10065    });
10066}
10067
10068#[gpui::test]
10069async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10070    init_test(cx, |_| {});
10071
10072    cx.update(|cx| {
10073        cx.update_global::<SettingsStore, _>(|settings, cx| {
10074            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10075                settings.auto_signature_help = Some(false);
10076                settings.show_signature_help_after_edits = Some(false);
10077            });
10078        });
10079    });
10080
10081    let mut cx = EditorLspTestContext::new_rust(
10082        lsp::ServerCapabilities {
10083            signature_help_provider: Some(lsp::SignatureHelpOptions {
10084                ..Default::default()
10085            }),
10086            ..Default::default()
10087        },
10088        cx,
10089    )
10090    .await;
10091
10092    let language = Language::new(
10093        LanguageConfig {
10094            name: "Rust".into(),
10095            brackets: BracketPairConfig {
10096                pairs: vec![
10097                    BracketPair {
10098                        start: "{".to_string(),
10099                        end: "}".to_string(),
10100                        close: true,
10101                        surround: true,
10102                        newline: true,
10103                    },
10104                    BracketPair {
10105                        start: "(".to_string(),
10106                        end: ")".to_string(),
10107                        close: true,
10108                        surround: true,
10109                        newline: true,
10110                    },
10111                    BracketPair {
10112                        start: "/*".to_string(),
10113                        end: " */".to_string(),
10114                        close: true,
10115                        surround: true,
10116                        newline: true,
10117                    },
10118                    BracketPair {
10119                        start: "[".to_string(),
10120                        end: "]".to_string(),
10121                        close: false,
10122                        surround: false,
10123                        newline: true,
10124                    },
10125                    BracketPair {
10126                        start: "\"".to_string(),
10127                        end: "\"".to_string(),
10128                        close: true,
10129                        surround: true,
10130                        newline: false,
10131                    },
10132                    BracketPair {
10133                        start: "<".to_string(),
10134                        end: ">".to_string(),
10135                        close: false,
10136                        surround: true,
10137                        newline: true,
10138                    },
10139                ],
10140                ..Default::default()
10141            },
10142            autoclose_before: "})]".to_string(),
10143            ..Default::default()
10144        },
10145        Some(tree_sitter_rust::LANGUAGE.into()),
10146    );
10147    let language = Arc::new(language);
10148
10149    cx.language_registry().add(language.clone());
10150    cx.update_buffer(|buffer, cx| {
10151        buffer.set_language(Some(language), cx);
10152    });
10153
10154    // Ensure that signature_help is not called when no signature help is enabled.
10155    cx.set_state(
10156        &r#"
10157            fn main() {
10158                sampleˇ
10159            }
10160        "#
10161        .unindent(),
10162    );
10163    cx.update_editor(|editor, window, cx| {
10164        editor.handle_input("(", window, cx);
10165    });
10166    cx.assert_editor_state(
10167        &"
10168            fn main() {
10169                sample(ˇ)
10170            }
10171        "
10172        .unindent(),
10173    );
10174    cx.editor(|editor, _, _| {
10175        assert!(editor.signature_help_state.task().is_none());
10176    });
10177
10178    let mocked_response = lsp::SignatureHelp {
10179        signatures: vec![lsp::SignatureInformation {
10180            label: "fn sample(param1: u8, param2: u8)".to_string(),
10181            documentation: None,
10182            parameters: Some(vec![
10183                lsp::ParameterInformation {
10184                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10185                    documentation: None,
10186                },
10187                lsp::ParameterInformation {
10188                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10189                    documentation: None,
10190                },
10191            ]),
10192            active_parameter: None,
10193        }],
10194        active_signature: Some(0),
10195        active_parameter: Some(0),
10196    };
10197
10198    // Ensure that signature_help is called when enabled afte edits
10199    cx.update(|_, cx| {
10200        cx.update_global::<SettingsStore, _>(|settings, cx| {
10201            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10202                settings.auto_signature_help = Some(false);
10203                settings.show_signature_help_after_edits = Some(true);
10204            });
10205        });
10206    });
10207    cx.set_state(
10208        &r#"
10209            fn main() {
10210                sampleˇ
10211            }
10212        "#
10213        .unindent(),
10214    );
10215    cx.update_editor(|editor, window, cx| {
10216        editor.handle_input("(", window, cx);
10217    });
10218    cx.assert_editor_state(
10219        &"
10220            fn main() {
10221                sample(ˇ)
10222            }
10223        "
10224        .unindent(),
10225    );
10226    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10227    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10228        .await;
10229    cx.update_editor(|editor, _, _| {
10230        let signature_help_state = editor.signature_help_state.popover().cloned();
10231        assert!(signature_help_state.is_some());
10232        assert_eq!(
10233            signature_help_state.unwrap().label,
10234            "param1: u8, param2: u8"
10235        );
10236        editor.signature_help_state = SignatureHelpState::default();
10237    });
10238
10239    // Ensure that signature_help is called when auto signature help override is enabled
10240    cx.update(|_, cx| {
10241        cx.update_global::<SettingsStore, _>(|settings, cx| {
10242            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10243                settings.auto_signature_help = Some(true);
10244                settings.show_signature_help_after_edits = Some(false);
10245            });
10246        });
10247    });
10248    cx.set_state(
10249        &r#"
10250            fn main() {
10251                sampleˇ
10252            }
10253        "#
10254        .unindent(),
10255    );
10256    cx.update_editor(|editor, window, cx| {
10257        editor.handle_input("(", window, cx);
10258    });
10259    cx.assert_editor_state(
10260        &"
10261            fn main() {
10262                sample(ˇ)
10263            }
10264        "
10265        .unindent(),
10266    );
10267    handle_signature_help_request(&mut cx, mocked_response).await;
10268    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10269        .await;
10270    cx.editor(|editor, _, _| {
10271        let signature_help_state = editor.signature_help_state.popover().cloned();
10272        assert!(signature_help_state.is_some());
10273        assert_eq!(
10274            signature_help_state.unwrap().label,
10275            "param1: u8, param2: u8"
10276        );
10277    });
10278}
10279
10280#[gpui::test]
10281async fn test_signature_help(cx: &mut TestAppContext) {
10282    init_test(cx, |_| {});
10283    cx.update(|cx| {
10284        cx.update_global::<SettingsStore, _>(|settings, cx| {
10285            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10286                settings.auto_signature_help = Some(true);
10287            });
10288        });
10289    });
10290
10291    let mut cx = EditorLspTestContext::new_rust(
10292        lsp::ServerCapabilities {
10293            signature_help_provider: Some(lsp::SignatureHelpOptions {
10294                ..Default::default()
10295            }),
10296            ..Default::default()
10297        },
10298        cx,
10299    )
10300    .await;
10301
10302    // A test that directly calls `show_signature_help`
10303    cx.update_editor(|editor, window, cx| {
10304        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10305    });
10306
10307    let mocked_response = lsp::SignatureHelp {
10308        signatures: vec![lsp::SignatureInformation {
10309            label: "fn sample(param1: u8, param2: u8)".to_string(),
10310            documentation: None,
10311            parameters: Some(vec![
10312                lsp::ParameterInformation {
10313                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10314                    documentation: None,
10315                },
10316                lsp::ParameterInformation {
10317                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10318                    documentation: None,
10319                },
10320            ]),
10321            active_parameter: None,
10322        }],
10323        active_signature: Some(0),
10324        active_parameter: Some(0),
10325    };
10326    handle_signature_help_request(&mut cx, mocked_response).await;
10327
10328    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10329        .await;
10330
10331    cx.editor(|editor, _, _| {
10332        let signature_help_state = editor.signature_help_state.popover().cloned();
10333        assert!(signature_help_state.is_some());
10334        assert_eq!(
10335            signature_help_state.unwrap().label,
10336            "param1: u8, param2: u8"
10337        );
10338    });
10339
10340    // When exiting outside from inside the brackets, `signature_help` is closed.
10341    cx.set_state(indoc! {"
10342        fn main() {
10343            sample(ˇ);
10344        }
10345
10346        fn sample(param1: u8, param2: u8) {}
10347    "});
10348
10349    cx.update_editor(|editor, window, cx| {
10350        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10351    });
10352
10353    let mocked_response = lsp::SignatureHelp {
10354        signatures: Vec::new(),
10355        active_signature: None,
10356        active_parameter: None,
10357    };
10358    handle_signature_help_request(&mut cx, mocked_response).await;
10359
10360    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10361        .await;
10362
10363    cx.editor(|editor, _, _| {
10364        assert!(!editor.signature_help_state.is_shown());
10365    });
10366
10367    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10368    cx.set_state(indoc! {"
10369        fn main() {
10370            sample(ˇ);
10371        }
10372
10373        fn sample(param1: u8, param2: u8) {}
10374    "});
10375
10376    let mocked_response = lsp::SignatureHelp {
10377        signatures: vec![lsp::SignatureInformation {
10378            label: "fn sample(param1: u8, param2: u8)".to_string(),
10379            documentation: None,
10380            parameters: Some(vec![
10381                lsp::ParameterInformation {
10382                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10383                    documentation: None,
10384                },
10385                lsp::ParameterInformation {
10386                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10387                    documentation: None,
10388                },
10389            ]),
10390            active_parameter: None,
10391        }],
10392        active_signature: Some(0),
10393        active_parameter: Some(0),
10394    };
10395    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10396    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10397        .await;
10398    cx.editor(|editor, _, _| {
10399        assert!(editor.signature_help_state.is_shown());
10400    });
10401
10402    // Restore the popover with more parameter input
10403    cx.set_state(indoc! {"
10404        fn main() {
10405            sample(param1, param2ˇ);
10406        }
10407
10408        fn sample(param1: u8, param2: u8) {}
10409    "});
10410
10411    let mocked_response = lsp::SignatureHelp {
10412        signatures: vec![lsp::SignatureInformation {
10413            label: "fn sample(param1: u8, param2: u8)".to_string(),
10414            documentation: None,
10415            parameters: Some(vec![
10416                lsp::ParameterInformation {
10417                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10418                    documentation: None,
10419                },
10420                lsp::ParameterInformation {
10421                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10422                    documentation: None,
10423                },
10424            ]),
10425            active_parameter: None,
10426        }],
10427        active_signature: Some(0),
10428        active_parameter: Some(1),
10429    };
10430    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10431    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10432        .await;
10433
10434    // When selecting a range, the popover is gone.
10435    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10436    cx.update_editor(|editor, window, cx| {
10437        editor.change_selections(None, window, cx, |s| {
10438            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10439        })
10440    });
10441    cx.assert_editor_state(indoc! {"
10442        fn main() {
10443            sample(param1, «ˇparam2»);
10444        }
10445
10446        fn sample(param1: u8, param2: u8) {}
10447    "});
10448    cx.editor(|editor, _, _| {
10449        assert!(!editor.signature_help_state.is_shown());
10450    });
10451
10452    // When unselecting again, the popover is back if within the brackets.
10453    cx.update_editor(|editor, window, cx| {
10454        editor.change_selections(None, window, cx, |s| {
10455            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10456        })
10457    });
10458    cx.assert_editor_state(indoc! {"
10459        fn main() {
10460            sample(param1, ˇparam2);
10461        }
10462
10463        fn sample(param1: u8, param2: u8) {}
10464    "});
10465    handle_signature_help_request(&mut cx, mocked_response).await;
10466    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10467        .await;
10468    cx.editor(|editor, _, _| {
10469        assert!(editor.signature_help_state.is_shown());
10470    });
10471
10472    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10473    cx.update_editor(|editor, window, cx| {
10474        editor.change_selections(None, window, cx, |s| {
10475            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10476            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10477        })
10478    });
10479    cx.assert_editor_state(indoc! {"
10480        fn main() {
10481            sample(param1, ˇparam2);
10482        }
10483
10484        fn sample(param1: u8, param2: u8) {}
10485    "});
10486
10487    let mocked_response = lsp::SignatureHelp {
10488        signatures: vec![lsp::SignatureInformation {
10489            label: "fn sample(param1: u8, param2: u8)".to_string(),
10490            documentation: None,
10491            parameters: Some(vec![
10492                lsp::ParameterInformation {
10493                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10494                    documentation: None,
10495                },
10496                lsp::ParameterInformation {
10497                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10498                    documentation: None,
10499                },
10500            ]),
10501            active_parameter: None,
10502        }],
10503        active_signature: Some(0),
10504        active_parameter: Some(1),
10505    };
10506    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10507    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10508        .await;
10509    cx.update_editor(|editor, _, cx| {
10510        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10511    });
10512    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10513        .await;
10514    cx.update_editor(|editor, window, cx| {
10515        editor.change_selections(None, window, cx, |s| {
10516            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10517        })
10518    });
10519    cx.assert_editor_state(indoc! {"
10520        fn main() {
10521            sample(param1, «ˇparam2»);
10522        }
10523
10524        fn sample(param1: u8, param2: u8) {}
10525    "});
10526    cx.update_editor(|editor, window, cx| {
10527        editor.change_selections(None, window, cx, |s| {
10528            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10529        })
10530    });
10531    cx.assert_editor_state(indoc! {"
10532        fn main() {
10533            sample(param1, ˇparam2);
10534        }
10535
10536        fn sample(param1: u8, param2: u8) {}
10537    "});
10538    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10539        .await;
10540}
10541
10542#[gpui::test]
10543async fn test_completion_mode(cx: &mut TestAppContext) {
10544    init_test(cx, |_| {});
10545    let mut cx = EditorLspTestContext::new_rust(
10546        lsp::ServerCapabilities {
10547            completion_provider: Some(lsp::CompletionOptions {
10548                resolve_provider: Some(true),
10549                ..Default::default()
10550            }),
10551            ..Default::default()
10552        },
10553        cx,
10554    )
10555    .await;
10556
10557    struct Run {
10558        run_description: &'static str,
10559        initial_state: String,
10560        buffer_marked_text: String,
10561        completion_label: &'static str,
10562        completion_text: &'static str,
10563        expected_with_insert_mode: String,
10564        expected_with_replace_mode: String,
10565        expected_with_replace_subsequence_mode: String,
10566        expected_with_replace_suffix_mode: String,
10567    }
10568
10569    let runs = [
10570        Run {
10571            run_description: "Start of word matches completion text",
10572            initial_state: "before ediˇ after".into(),
10573            buffer_marked_text: "before <edi|> after".into(),
10574            completion_label: "editor",
10575            completion_text: "editor",
10576            expected_with_insert_mode: "before editorˇ after".into(),
10577            expected_with_replace_mode: "before editorˇ after".into(),
10578            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10579            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10580        },
10581        Run {
10582            run_description: "Accept same text at the middle of the word",
10583            initial_state: "before ediˇtor after".into(),
10584            buffer_marked_text: "before <edi|tor> after".into(),
10585            completion_label: "editor",
10586            completion_text: "editor",
10587            expected_with_insert_mode: "before editorˇtor after".into(),
10588            expected_with_replace_mode: "before editorˇ after".into(),
10589            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10590            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10591        },
10592        Run {
10593            run_description: "End of word matches completion text -- cursor at end",
10594            initial_state: "before torˇ after".into(),
10595            buffer_marked_text: "before <tor|> after".into(),
10596            completion_label: "editor",
10597            completion_text: "editor",
10598            expected_with_insert_mode: "before editorˇ after".into(),
10599            expected_with_replace_mode: "before editorˇ after".into(),
10600            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10601            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10602        },
10603        Run {
10604            run_description: "End of word matches completion text -- cursor at start",
10605            initial_state: "before ˇtor after".into(),
10606            buffer_marked_text: "before <|tor> after".into(),
10607            completion_label: "editor",
10608            completion_text: "editor",
10609            expected_with_insert_mode: "before editorˇtor after".into(),
10610            expected_with_replace_mode: "before editorˇ after".into(),
10611            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10612            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10613        },
10614        Run {
10615            run_description: "Prepend text containing whitespace",
10616            initial_state: "pˇfield: bool".into(),
10617            buffer_marked_text: "<p|field>: bool".into(),
10618            completion_label: "pub ",
10619            completion_text: "pub ",
10620            expected_with_insert_mode: "pub ˇfield: bool".into(),
10621            expected_with_replace_mode: "pub ˇ: bool".into(),
10622            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10623            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10624        },
10625        Run {
10626            run_description: "Add element to start of list",
10627            initial_state: "[element_ˇelement_2]".into(),
10628            buffer_marked_text: "[<element_|element_2>]".into(),
10629            completion_label: "element_1",
10630            completion_text: "element_1",
10631            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10632            expected_with_replace_mode: "[element_1ˇ]".into(),
10633            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10634            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10635        },
10636        Run {
10637            run_description: "Add element to start of list -- first and second elements are equal",
10638            initial_state: "[elˇelement]".into(),
10639            buffer_marked_text: "[<el|element>]".into(),
10640            completion_label: "element",
10641            completion_text: "element",
10642            expected_with_insert_mode: "[elementˇelement]".into(),
10643            expected_with_replace_mode: "[elementˇ]".into(),
10644            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10645            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10646        },
10647        Run {
10648            run_description: "Ends with matching suffix",
10649            initial_state: "SubˇError".into(),
10650            buffer_marked_text: "<Sub|Error>".into(),
10651            completion_label: "SubscriptionError",
10652            completion_text: "SubscriptionError",
10653            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10654            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10655            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10656            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10657        },
10658        Run {
10659            run_description: "Suffix is a subsequence -- contiguous",
10660            initial_state: "SubˇErr".into(),
10661            buffer_marked_text: "<Sub|Err>".into(),
10662            completion_label: "SubscriptionError",
10663            completion_text: "SubscriptionError",
10664            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10665            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10666            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10667            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10668        },
10669        Run {
10670            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10671            initial_state: "Suˇscrirr".into(),
10672            buffer_marked_text: "<Su|scrirr>".into(),
10673            completion_label: "SubscriptionError",
10674            completion_text: "SubscriptionError",
10675            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10676            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10677            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10678            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10679        },
10680        Run {
10681            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10682            initial_state: "foo(indˇix)".into(),
10683            buffer_marked_text: "foo(<ind|ix>)".into(),
10684            completion_label: "node_index",
10685            completion_text: "node_index",
10686            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10687            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10688            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10689            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10690        },
10691        Run {
10692            run_description: "Replace range ends before cursor - should extend to cursor",
10693            initial_state: "before editˇo after".into(),
10694            buffer_marked_text: "before <{ed}>it|o after".into(),
10695            completion_label: "editor",
10696            completion_text: "editor",
10697            expected_with_insert_mode: "before editorˇo after".into(),
10698            expected_with_replace_mode: "before editorˇo after".into(),
10699            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
10700            expected_with_replace_suffix_mode: "before editorˇo after".into(),
10701        },
10702        Run {
10703            run_description: "Uses label for suffix matching",
10704            initial_state: "before ediˇtor after".into(),
10705            buffer_marked_text: "before <edi|tor> after".into(),
10706            completion_label: "editor",
10707            completion_text: "editor()",
10708            expected_with_insert_mode: "before editor()ˇtor after".into(),
10709            expected_with_replace_mode: "before editor()ˇ after".into(),
10710            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
10711            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
10712        },
10713        Run {
10714            run_description: "Case insensitive subsequence and suffix matching",
10715            initial_state: "before EDiˇtoR after".into(),
10716            buffer_marked_text: "before <EDi|toR> after".into(),
10717            completion_label: "editor",
10718            completion_text: "editor",
10719            expected_with_insert_mode: "before editorˇtoR after".into(),
10720            expected_with_replace_mode: "before editorˇ after".into(),
10721            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10722            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10723        },
10724    ];
10725
10726    for run in runs {
10727        let run_variations = [
10728            (LspInsertMode::Insert, run.expected_with_insert_mode),
10729            (LspInsertMode::Replace, run.expected_with_replace_mode),
10730            (
10731                LspInsertMode::ReplaceSubsequence,
10732                run.expected_with_replace_subsequence_mode,
10733            ),
10734            (
10735                LspInsertMode::ReplaceSuffix,
10736                run.expected_with_replace_suffix_mode,
10737            ),
10738        ];
10739
10740        for (lsp_insert_mode, expected_text) in run_variations {
10741            eprintln!(
10742                "run = {:?}, mode = {lsp_insert_mode:.?}",
10743                run.run_description,
10744            );
10745
10746            update_test_language_settings(&mut cx, |settings| {
10747                settings.defaults.completions = Some(CompletionSettings {
10748                    lsp_insert_mode,
10749                    words: WordsCompletionMode::Disabled,
10750                    lsp: true,
10751                    lsp_fetch_timeout_ms: 0,
10752                });
10753            });
10754
10755            cx.set_state(&run.initial_state);
10756            cx.update_editor(|editor, window, cx| {
10757                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10758            });
10759
10760            let counter = Arc::new(AtomicUsize::new(0));
10761            handle_completion_request_with_insert_and_replace(
10762                &mut cx,
10763                &run.buffer_marked_text,
10764                vec![(run.completion_label, run.completion_text)],
10765                counter.clone(),
10766            )
10767            .await;
10768            cx.condition(|editor, _| editor.context_menu_visible())
10769                .await;
10770            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10771
10772            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10773                editor
10774                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10775                    .unwrap()
10776            });
10777            cx.assert_editor_state(&expected_text);
10778            handle_resolve_completion_request(&mut cx, None).await;
10779            apply_additional_edits.await.unwrap();
10780        }
10781    }
10782}
10783
10784#[gpui::test]
10785async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10786    init_test(cx, |_| {});
10787    let mut cx = EditorLspTestContext::new_rust(
10788        lsp::ServerCapabilities {
10789            completion_provider: Some(lsp::CompletionOptions {
10790                resolve_provider: Some(true),
10791                ..Default::default()
10792            }),
10793            ..Default::default()
10794        },
10795        cx,
10796    )
10797    .await;
10798
10799    let initial_state = "SubˇError";
10800    let buffer_marked_text = "<Sub|Error>";
10801    let completion_text = "SubscriptionError";
10802    let expected_with_insert_mode = "SubscriptionErrorˇError";
10803    let expected_with_replace_mode = "SubscriptionErrorˇ";
10804
10805    update_test_language_settings(&mut cx, |settings| {
10806        settings.defaults.completions = Some(CompletionSettings {
10807            words: WordsCompletionMode::Disabled,
10808            // set the opposite here to ensure that the action is overriding the default behavior
10809            lsp_insert_mode: LspInsertMode::Insert,
10810            lsp: true,
10811            lsp_fetch_timeout_ms: 0,
10812        });
10813    });
10814
10815    cx.set_state(initial_state);
10816    cx.update_editor(|editor, window, cx| {
10817        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10818    });
10819
10820    let counter = Arc::new(AtomicUsize::new(0));
10821    handle_completion_request_with_insert_and_replace(
10822        &mut cx,
10823        &buffer_marked_text,
10824        vec![(completion_text, completion_text)],
10825        counter.clone(),
10826    )
10827    .await;
10828    cx.condition(|editor, _| editor.context_menu_visible())
10829        .await;
10830    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10831
10832    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10833        editor
10834            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10835            .unwrap()
10836    });
10837    cx.assert_editor_state(&expected_with_replace_mode);
10838    handle_resolve_completion_request(&mut cx, None).await;
10839    apply_additional_edits.await.unwrap();
10840
10841    update_test_language_settings(&mut cx, |settings| {
10842        settings.defaults.completions = Some(CompletionSettings {
10843            words: WordsCompletionMode::Disabled,
10844            // set the opposite here to ensure that the action is overriding the default behavior
10845            lsp_insert_mode: LspInsertMode::Replace,
10846            lsp: true,
10847            lsp_fetch_timeout_ms: 0,
10848        });
10849    });
10850
10851    cx.set_state(initial_state);
10852    cx.update_editor(|editor, window, cx| {
10853        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10854    });
10855    handle_completion_request_with_insert_and_replace(
10856        &mut cx,
10857        &buffer_marked_text,
10858        vec![(completion_text, completion_text)],
10859        counter.clone(),
10860    )
10861    .await;
10862    cx.condition(|editor, _| editor.context_menu_visible())
10863        .await;
10864    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10865
10866    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10867        editor
10868            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10869            .unwrap()
10870    });
10871    cx.assert_editor_state(&expected_with_insert_mode);
10872    handle_resolve_completion_request(&mut cx, None).await;
10873    apply_additional_edits.await.unwrap();
10874}
10875
10876#[gpui::test]
10877async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10878    init_test(cx, |_| {});
10879    let mut cx = EditorLspTestContext::new_rust(
10880        lsp::ServerCapabilities {
10881            completion_provider: Some(lsp::CompletionOptions {
10882                resolve_provider: Some(true),
10883                ..Default::default()
10884            }),
10885            ..Default::default()
10886        },
10887        cx,
10888    )
10889    .await;
10890
10891    // scenario: surrounding text matches completion text
10892    let completion_text = "to_offset";
10893    let initial_state = indoc! {"
10894        1. buf.to_offˇsuffix
10895        2. buf.to_offˇsuf
10896        3. buf.to_offˇfix
10897        4. buf.to_offˇ
10898        5. into_offˇensive
10899        6. ˇsuffix
10900        7. let ˇ //
10901        8. aaˇzz
10902        9. buf.to_off«zzzzzˇ»suffix
10903        10. buf.«ˇzzzzz»suffix
10904        11. to_off«ˇzzzzz»
10905
10906        buf.to_offˇsuffix  // newest cursor
10907    "};
10908    let completion_marked_buffer = indoc! {"
10909        1. buf.to_offsuffix
10910        2. buf.to_offsuf
10911        3. buf.to_offfix
10912        4. buf.to_off
10913        5. into_offensive
10914        6. suffix
10915        7. let  //
10916        8. aazz
10917        9. buf.to_offzzzzzsuffix
10918        10. buf.zzzzzsuffix
10919        11. to_offzzzzz
10920
10921        buf.<to_off|suffix>  // newest cursor
10922    "};
10923    let expected = indoc! {"
10924        1. buf.to_offsetˇ
10925        2. buf.to_offsetˇsuf
10926        3. buf.to_offsetˇfix
10927        4. buf.to_offsetˇ
10928        5. into_offsetˇensive
10929        6. to_offsetˇsuffix
10930        7. let to_offsetˇ //
10931        8. aato_offsetˇzz
10932        9. buf.to_offsetˇ
10933        10. buf.to_offsetˇsuffix
10934        11. to_offsetˇ
10935
10936        buf.to_offsetˇ  // newest cursor
10937    "};
10938    cx.set_state(initial_state);
10939    cx.update_editor(|editor, window, cx| {
10940        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10941    });
10942    handle_completion_request_with_insert_and_replace(
10943        &mut cx,
10944        completion_marked_buffer,
10945        vec![(completion_text, completion_text)],
10946        Arc::new(AtomicUsize::new(0)),
10947    )
10948    .await;
10949    cx.condition(|editor, _| editor.context_menu_visible())
10950        .await;
10951    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10952        editor
10953            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10954            .unwrap()
10955    });
10956    cx.assert_editor_state(expected);
10957    handle_resolve_completion_request(&mut cx, None).await;
10958    apply_additional_edits.await.unwrap();
10959
10960    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10961    let completion_text = "foo_and_bar";
10962    let initial_state = indoc! {"
10963        1. ooanbˇ
10964        2. zooanbˇ
10965        3. ooanbˇz
10966        4. zooanbˇz
10967        5. ooanˇ
10968        6. oanbˇ
10969
10970        ooanbˇ
10971    "};
10972    let completion_marked_buffer = indoc! {"
10973        1. ooanb
10974        2. zooanb
10975        3. ooanbz
10976        4. zooanbz
10977        5. ooan
10978        6. oanb
10979
10980        <ooanb|>
10981    "};
10982    let expected = indoc! {"
10983        1. foo_and_barˇ
10984        2. zfoo_and_barˇ
10985        3. foo_and_barˇz
10986        4. zfoo_and_barˇz
10987        5. ooanfoo_and_barˇ
10988        6. oanbfoo_and_barˇ
10989
10990        foo_and_barˇ
10991    "};
10992    cx.set_state(initial_state);
10993    cx.update_editor(|editor, window, cx| {
10994        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10995    });
10996    handle_completion_request_with_insert_and_replace(
10997        &mut cx,
10998        completion_marked_buffer,
10999        vec![(completion_text, completion_text)],
11000        Arc::new(AtomicUsize::new(0)),
11001    )
11002    .await;
11003    cx.condition(|editor, _| editor.context_menu_visible())
11004        .await;
11005    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11006        editor
11007            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11008            .unwrap()
11009    });
11010    cx.assert_editor_state(expected);
11011    handle_resolve_completion_request(&mut cx, None).await;
11012    apply_additional_edits.await.unwrap();
11013
11014    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11015    // (expects the same as if it was inserted at the end)
11016    let completion_text = "foo_and_bar";
11017    let initial_state = indoc! {"
11018        1. ooˇanb
11019        2. zooˇanb
11020        3. ooˇanbz
11021        4. zooˇanbz
11022
11023        ooˇanb
11024    "};
11025    let completion_marked_buffer = indoc! {"
11026        1. ooanb
11027        2. zooanb
11028        3. ooanbz
11029        4. zooanbz
11030
11031        <oo|anb>
11032    "};
11033    let expected = indoc! {"
11034        1. foo_and_barˇ
11035        2. zfoo_and_barˇ
11036        3. foo_and_barˇz
11037        4. zfoo_and_barˇz
11038
11039        foo_and_barˇ
11040    "};
11041    cx.set_state(initial_state);
11042    cx.update_editor(|editor, window, cx| {
11043        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11044    });
11045    handle_completion_request_with_insert_and_replace(
11046        &mut cx,
11047        completion_marked_buffer,
11048        vec![(completion_text, completion_text)],
11049        Arc::new(AtomicUsize::new(0)),
11050    )
11051    .await;
11052    cx.condition(|editor, _| editor.context_menu_visible())
11053        .await;
11054    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11055        editor
11056            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11057            .unwrap()
11058    });
11059    cx.assert_editor_state(expected);
11060    handle_resolve_completion_request(&mut cx, None).await;
11061    apply_additional_edits.await.unwrap();
11062}
11063
11064// This used to crash
11065#[gpui::test]
11066async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11067    init_test(cx, |_| {});
11068
11069    let buffer_text = indoc! {"
11070        fn main() {
11071            10.satu;
11072
11073            //
11074            // separate cursors so they open in different excerpts (manually reproducible)
11075            //
11076
11077            10.satu20;
11078        }
11079    "};
11080    let multibuffer_text_with_selections = indoc! {"
11081        fn main() {
11082            10.satuˇ;
11083
11084            //
11085
11086            //
11087
11088            10.satuˇ20;
11089        }
11090    "};
11091    let expected_multibuffer = indoc! {"
11092        fn main() {
11093            10.saturating_sub()ˇ;
11094
11095            //
11096
11097            //
11098
11099            10.saturating_sub()ˇ;
11100        }
11101    "};
11102
11103    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11104    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11105
11106    let fs = FakeFs::new(cx.executor());
11107    fs.insert_tree(
11108        path!("/a"),
11109        json!({
11110            "main.rs": buffer_text,
11111        }),
11112    )
11113    .await;
11114
11115    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11116    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11117    language_registry.add(rust_lang());
11118    let mut fake_servers = language_registry.register_fake_lsp(
11119        "Rust",
11120        FakeLspAdapter {
11121            capabilities: lsp::ServerCapabilities {
11122                completion_provider: Some(lsp::CompletionOptions {
11123                    resolve_provider: None,
11124                    ..lsp::CompletionOptions::default()
11125                }),
11126                ..lsp::ServerCapabilities::default()
11127            },
11128            ..FakeLspAdapter::default()
11129        },
11130    );
11131    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11132    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11133    let buffer = project
11134        .update(cx, |project, cx| {
11135            project.open_local_buffer(path!("/a/main.rs"), cx)
11136        })
11137        .await
11138        .unwrap();
11139
11140    let multi_buffer = cx.new(|cx| {
11141        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11142        multi_buffer.push_excerpts(
11143            buffer.clone(),
11144            [ExcerptRange::new(0..first_excerpt_end)],
11145            cx,
11146        );
11147        multi_buffer.push_excerpts(
11148            buffer.clone(),
11149            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11150            cx,
11151        );
11152        multi_buffer
11153    });
11154
11155    let editor = workspace
11156        .update(cx, |_, window, cx| {
11157            cx.new(|cx| {
11158                Editor::new(
11159                    EditorMode::Full {
11160                        scale_ui_elements_with_buffer_font_size: false,
11161                        show_active_line_background: false,
11162                        sized_by_content: false,
11163                    },
11164                    multi_buffer.clone(),
11165                    Some(project.clone()),
11166                    window,
11167                    cx,
11168                )
11169            })
11170        })
11171        .unwrap();
11172
11173    let pane = workspace
11174        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11175        .unwrap();
11176    pane.update_in(cx, |pane, window, cx| {
11177        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11178    });
11179
11180    let fake_server = fake_servers.next().await.unwrap();
11181
11182    editor.update_in(cx, |editor, window, cx| {
11183        editor.change_selections(None, window, cx, |s| {
11184            s.select_ranges([
11185                Point::new(1, 11)..Point::new(1, 11),
11186                Point::new(7, 11)..Point::new(7, 11),
11187            ])
11188        });
11189
11190        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11191    });
11192
11193    editor.update_in(cx, |editor, window, cx| {
11194        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11195    });
11196
11197    fake_server
11198        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11199            let completion_item = lsp::CompletionItem {
11200                label: "saturating_sub()".into(),
11201                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11202                    lsp::InsertReplaceEdit {
11203                        new_text: "saturating_sub()".to_owned(),
11204                        insert: lsp::Range::new(
11205                            lsp::Position::new(7, 7),
11206                            lsp::Position::new(7, 11),
11207                        ),
11208                        replace: lsp::Range::new(
11209                            lsp::Position::new(7, 7),
11210                            lsp::Position::new(7, 13),
11211                        ),
11212                    },
11213                )),
11214                ..lsp::CompletionItem::default()
11215            };
11216
11217            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11218        })
11219        .next()
11220        .await
11221        .unwrap();
11222
11223    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11224        .await;
11225
11226    editor
11227        .update_in(cx, |editor, window, cx| {
11228            editor
11229                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11230                .unwrap()
11231        })
11232        .await
11233        .unwrap();
11234
11235    editor.update(cx, |editor, cx| {
11236        assert_text_with_selections(editor, expected_multibuffer, cx);
11237    })
11238}
11239
11240#[gpui::test]
11241async fn test_completion(cx: &mut TestAppContext) {
11242    init_test(cx, |_| {});
11243
11244    let mut cx = EditorLspTestContext::new_rust(
11245        lsp::ServerCapabilities {
11246            completion_provider: Some(lsp::CompletionOptions {
11247                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11248                resolve_provider: Some(true),
11249                ..Default::default()
11250            }),
11251            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11252            ..Default::default()
11253        },
11254        cx,
11255    )
11256    .await;
11257    let counter = Arc::new(AtomicUsize::new(0));
11258
11259    cx.set_state(indoc! {"
11260        oneˇ
11261        two
11262        three
11263    "});
11264    cx.simulate_keystroke(".");
11265    handle_completion_request(
11266        indoc! {"
11267            one.|<>
11268            two
11269            three
11270        "},
11271        vec!["first_completion", "second_completion"],
11272        true,
11273        counter.clone(),
11274        &mut cx,
11275    )
11276    .await;
11277    cx.condition(|editor, _| editor.context_menu_visible())
11278        .await;
11279    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11280
11281    let _handler = handle_signature_help_request(
11282        &mut cx,
11283        lsp::SignatureHelp {
11284            signatures: vec![lsp::SignatureInformation {
11285                label: "test signature".to_string(),
11286                documentation: None,
11287                parameters: Some(vec![lsp::ParameterInformation {
11288                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11289                    documentation: None,
11290                }]),
11291                active_parameter: None,
11292            }],
11293            active_signature: None,
11294            active_parameter: None,
11295        },
11296    );
11297    cx.update_editor(|editor, window, cx| {
11298        assert!(
11299            !editor.signature_help_state.is_shown(),
11300            "No signature help was called for"
11301        );
11302        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11303    });
11304    cx.run_until_parked();
11305    cx.update_editor(|editor, _, _| {
11306        assert!(
11307            !editor.signature_help_state.is_shown(),
11308            "No signature help should be shown when completions menu is open"
11309        );
11310    });
11311
11312    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11313        editor.context_menu_next(&Default::default(), window, cx);
11314        editor
11315            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11316            .unwrap()
11317    });
11318    cx.assert_editor_state(indoc! {"
11319        one.second_completionˇ
11320        two
11321        three
11322    "});
11323
11324    handle_resolve_completion_request(
11325        &mut cx,
11326        Some(vec![
11327            (
11328                //This overlaps with the primary completion edit which is
11329                //misbehavior from the LSP spec, test that we filter it out
11330                indoc! {"
11331                    one.second_ˇcompletion
11332                    two
11333                    threeˇ
11334                "},
11335                "overlapping additional edit",
11336            ),
11337            (
11338                indoc! {"
11339                    one.second_completion
11340                    two
11341                    threeˇ
11342                "},
11343                "\nadditional edit",
11344            ),
11345        ]),
11346    )
11347    .await;
11348    apply_additional_edits.await.unwrap();
11349    cx.assert_editor_state(indoc! {"
11350        one.second_completionˇ
11351        two
11352        three
11353        additional edit
11354    "});
11355
11356    cx.set_state(indoc! {"
11357        one.second_completion
11358        twoˇ
11359        threeˇ
11360        additional edit
11361    "});
11362    cx.simulate_keystroke(" ");
11363    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11364    cx.simulate_keystroke("s");
11365    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11366
11367    cx.assert_editor_state(indoc! {"
11368        one.second_completion
11369        two sˇ
11370        three sˇ
11371        additional edit
11372    "});
11373    handle_completion_request(
11374        indoc! {"
11375            one.second_completion
11376            two s
11377            three <s|>
11378            additional edit
11379        "},
11380        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11381        true,
11382        counter.clone(),
11383        &mut cx,
11384    )
11385    .await;
11386    cx.condition(|editor, _| editor.context_menu_visible())
11387        .await;
11388    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11389
11390    cx.simulate_keystroke("i");
11391
11392    handle_completion_request(
11393        indoc! {"
11394            one.second_completion
11395            two si
11396            three <si|>
11397            additional edit
11398        "},
11399        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11400        true,
11401        counter.clone(),
11402        &mut cx,
11403    )
11404    .await;
11405    cx.condition(|editor, _| editor.context_menu_visible())
11406        .await;
11407    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11408
11409    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11410        editor
11411            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11412            .unwrap()
11413    });
11414    cx.assert_editor_state(indoc! {"
11415        one.second_completion
11416        two sixth_completionˇ
11417        three sixth_completionˇ
11418        additional edit
11419    "});
11420
11421    apply_additional_edits.await.unwrap();
11422
11423    update_test_language_settings(&mut cx, |settings| {
11424        settings.defaults.show_completions_on_input = Some(false);
11425    });
11426    cx.set_state("editorˇ");
11427    cx.simulate_keystroke(".");
11428    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11429    cx.simulate_keystrokes("c l o");
11430    cx.assert_editor_state("editor.cloˇ");
11431    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11432    cx.update_editor(|editor, window, cx| {
11433        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11434    });
11435    handle_completion_request(
11436        "editor.<clo|>",
11437        vec!["close", "clobber"],
11438        true,
11439        counter.clone(),
11440        &mut cx,
11441    )
11442    .await;
11443    cx.condition(|editor, _| editor.context_menu_visible())
11444        .await;
11445    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11446
11447    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11448        editor
11449            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11450            .unwrap()
11451    });
11452    cx.assert_editor_state("editor.closeˇ");
11453    handle_resolve_completion_request(&mut cx, None).await;
11454    apply_additional_edits.await.unwrap();
11455}
11456
11457#[gpui::test]
11458async fn test_completion_reuse(cx: &mut TestAppContext) {
11459    init_test(cx, |_| {});
11460
11461    let mut cx = EditorLspTestContext::new_rust(
11462        lsp::ServerCapabilities {
11463            completion_provider: Some(lsp::CompletionOptions {
11464                trigger_characters: Some(vec![".".to_string()]),
11465                ..Default::default()
11466            }),
11467            ..Default::default()
11468        },
11469        cx,
11470    )
11471    .await;
11472
11473    let counter = Arc::new(AtomicUsize::new(0));
11474    cx.set_state("objˇ");
11475    cx.simulate_keystroke(".");
11476
11477    // Initial completion request returns complete results
11478    let is_incomplete = false;
11479    handle_completion_request(
11480        "obj.|<>",
11481        vec!["a", "ab", "abc"],
11482        is_incomplete,
11483        counter.clone(),
11484        &mut cx,
11485    )
11486    .await;
11487    cx.run_until_parked();
11488    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11489    cx.assert_editor_state("obj.ˇ");
11490    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11491
11492    // Type "a" - filters existing completions
11493    cx.simulate_keystroke("a");
11494    cx.run_until_parked();
11495    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11496    cx.assert_editor_state("obj.aˇ");
11497    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11498
11499    // Type "b" - filters existing completions
11500    cx.simulate_keystroke("b");
11501    cx.run_until_parked();
11502    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11503    cx.assert_editor_state("obj.abˇ");
11504    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11505
11506    // Type "c" - filters existing completions
11507    cx.simulate_keystroke("c");
11508    cx.run_until_parked();
11509    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11510    cx.assert_editor_state("obj.abcˇ");
11511    check_displayed_completions(vec!["abc"], &mut cx);
11512
11513    // Backspace to delete "c" - filters existing completions
11514    cx.update_editor(|editor, window, cx| {
11515        editor.backspace(&Backspace, window, cx);
11516    });
11517    cx.run_until_parked();
11518    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11519    cx.assert_editor_state("obj.abˇ");
11520    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11521
11522    // Moving cursor to the left dismisses menu.
11523    cx.update_editor(|editor, window, cx| {
11524        editor.move_left(&MoveLeft, window, cx);
11525    });
11526    cx.run_until_parked();
11527    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11528    cx.assert_editor_state("obj.aˇb");
11529    cx.update_editor(|editor, _, _| {
11530        assert_eq!(editor.context_menu_visible(), false);
11531    });
11532
11533    // Type "b" - new request
11534    cx.simulate_keystroke("b");
11535    let is_incomplete = false;
11536    handle_completion_request(
11537        "obj.<ab|>a",
11538        vec!["ab", "abc"],
11539        is_incomplete,
11540        counter.clone(),
11541        &mut cx,
11542    )
11543    .await;
11544    cx.run_until_parked();
11545    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11546    cx.assert_editor_state("obj.abˇb");
11547    check_displayed_completions(vec!["ab", "abc"], &mut cx);
11548
11549    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
11550    cx.update_editor(|editor, window, cx| {
11551        editor.backspace(&Backspace, window, cx);
11552    });
11553    let is_incomplete = false;
11554    handle_completion_request(
11555        "obj.<a|>b",
11556        vec!["a", "ab", "abc"],
11557        is_incomplete,
11558        counter.clone(),
11559        &mut cx,
11560    )
11561    .await;
11562    cx.run_until_parked();
11563    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11564    cx.assert_editor_state("obj.aˇb");
11565    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11566
11567    // Backspace to delete "a" - dismisses menu.
11568    cx.update_editor(|editor, window, cx| {
11569        editor.backspace(&Backspace, window, cx);
11570    });
11571    cx.run_until_parked();
11572    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11573    cx.assert_editor_state("obj.ˇb");
11574    cx.update_editor(|editor, _, _| {
11575        assert_eq!(editor.context_menu_visible(), false);
11576    });
11577}
11578
11579#[gpui::test]
11580async fn test_word_completion(cx: &mut TestAppContext) {
11581    let lsp_fetch_timeout_ms = 10;
11582    init_test(cx, |language_settings| {
11583        language_settings.defaults.completions = Some(CompletionSettings {
11584            words: WordsCompletionMode::Fallback,
11585            lsp: true,
11586            lsp_fetch_timeout_ms: 10,
11587            lsp_insert_mode: LspInsertMode::Insert,
11588        });
11589    });
11590
11591    let mut cx = EditorLspTestContext::new_rust(
11592        lsp::ServerCapabilities {
11593            completion_provider: Some(lsp::CompletionOptions {
11594                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11595                ..lsp::CompletionOptions::default()
11596            }),
11597            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11598            ..lsp::ServerCapabilities::default()
11599        },
11600        cx,
11601    )
11602    .await;
11603
11604    let throttle_completions = Arc::new(AtomicBool::new(false));
11605
11606    let lsp_throttle_completions = throttle_completions.clone();
11607    let _completion_requests_handler =
11608        cx.lsp
11609            .server
11610            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11611                let lsp_throttle_completions = lsp_throttle_completions.clone();
11612                let cx = cx.clone();
11613                async move {
11614                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11615                        cx.background_executor()
11616                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11617                            .await;
11618                    }
11619                    Ok(Some(lsp::CompletionResponse::Array(vec![
11620                        lsp::CompletionItem {
11621                            label: "first".into(),
11622                            ..lsp::CompletionItem::default()
11623                        },
11624                        lsp::CompletionItem {
11625                            label: "last".into(),
11626                            ..lsp::CompletionItem::default()
11627                        },
11628                    ])))
11629                }
11630            });
11631
11632    cx.set_state(indoc! {"
11633        oneˇ
11634        two
11635        three
11636    "});
11637    cx.simulate_keystroke(".");
11638    cx.executor().run_until_parked();
11639    cx.condition(|editor, _| editor.context_menu_visible())
11640        .await;
11641    cx.update_editor(|editor, window, cx| {
11642        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11643        {
11644            assert_eq!(
11645                completion_menu_entries(&menu),
11646                &["first", "last"],
11647                "When LSP server is fast to reply, no fallback word completions are used"
11648            );
11649        } else {
11650            panic!("expected completion menu to be open");
11651        }
11652        editor.cancel(&Cancel, window, cx);
11653    });
11654    cx.executor().run_until_parked();
11655    cx.condition(|editor, _| !editor.context_menu_visible())
11656        .await;
11657
11658    throttle_completions.store(true, atomic::Ordering::Release);
11659    cx.simulate_keystroke(".");
11660    cx.executor()
11661        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11662    cx.executor().run_until_parked();
11663    cx.condition(|editor, _| editor.context_menu_visible())
11664        .await;
11665    cx.update_editor(|editor, _, _| {
11666        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11667        {
11668            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11669                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11670        } else {
11671            panic!("expected completion menu to be open");
11672        }
11673    });
11674}
11675
11676#[gpui::test]
11677async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11678    init_test(cx, |language_settings| {
11679        language_settings.defaults.completions = Some(CompletionSettings {
11680            words: WordsCompletionMode::Enabled,
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                Ok(Some(lsp::CompletionResponse::Array(vec![
11705                    lsp::CompletionItem {
11706                        label: "first".into(),
11707                        ..lsp::CompletionItem::default()
11708                    },
11709                    lsp::CompletionItem {
11710                        label: "last".into(),
11711                        ..lsp::CompletionItem::default()
11712                    },
11713                ])))
11714            });
11715
11716    cx.set_state(indoc! {"ˇ
11717        first
11718        last
11719        second
11720    "});
11721    cx.simulate_keystroke(".");
11722    cx.executor().run_until_parked();
11723    cx.condition(|editor, _| editor.context_menu_visible())
11724        .await;
11725    cx.update_editor(|editor, _, _| {
11726        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11727        {
11728            assert_eq!(
11729                completion_menu_entries(&menu),
11730                &["first", "last", "second"],
11731                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11732            );
11733        } else {
11734            panic!("expected completion menu to be open");
11735        }
11736    });
11737}
11738
11739#[gpui::test]
11740async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11741    init_test(cx, |language_settings| {
11742        language_settings.defaults.completions = Some(CompletionSettings {
11743            words: WordsCompletionMode::Disabled,
11744            lsp: true,
11745            lsp_fetch_timeout_ms: 0,
11746            lsp_insert_mode: LspInsertMode::Insert,
11747        });
11748    });
11749
11750    let mut cx = EditorLspTestContext::new_rust(
11751        lsp::ServerCapabilities {
11752            completion_provider: Some(lsp::CompletionOptions {
11753                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11754                ..lsp::CompletionOptions::default()
11755            }),
11756            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11757            ..lsp::ServerCapabilities::default()
11758        },
11759        cx,
11760    )
11761    .await;
11762
11763    let _completion_requests_handler =
11764        cx.lsp
11765            .server
11766            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11767                panic!("LSP completions should not be queried when dealing with word completions")
11768            });
11769
11770    cx.set_state(indoc! {"ˇ
11771        first
11772        last
11773        second
11774    "});
11775    cx.update_editor(|editor, window, cx| {
11776        editor.show_word_completions(&ShowWordCompletions, window, cx);
11777    });
11778    cx.executor().run_until_parked();
11779    cx.condition(|editor, _| editor.context_menu_visible())
11780        .await;
11781    cx.update_editor(|editor, _, _| {
11782        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11783        {
11784            assert_eq!(
11785                completion_menu_entries(&menu),
11786                &["first", "last", "second"],
11787                "`ShowWordCompletions` action should show word completions"
11788            );
11789        } else {
11790            panic!("expected completion menu to be open");
11791        }
11792    });
11793
11794    cx.simulate_keystroke("l");
11795    cx.executor().run_until_parked();
11796    cx.condition(|editor, _| editor.context_menu_visible())
11797        .await;
11798    cx.update_editor(|editor, _, _| {
11799        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11800        {
11801            assert_eq!(
11802                completion_menu_entries(&menu),
11803                &["last"],
11804                "After showing word completions, further editing should filter them and not query the LSP"
11805            );
11806        } else {
11807            panic!("expected completion menu to be open");
11808        }
11809    });
11810}
11811
11812#[gpui::test]
11813async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11814    init_test(cx, |language_settings| {
11815        language_settings.defaults.completions = Some(CompletionSettings {
11816            words: WordsCompletionMode::Fallback,
11817            lsp: false,
11818            lsp_fetch_timeout_ms: 0,
11819            lsp_insert_mode: LspInsertMode::Insert,
11820        });
11821    });
11822
11823    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11824
11825    cx.set_state(indoc! {"ˇ
11826        0_usize
11827        let
11828        33
11829        4.5f32
11830    "});
11831    cx.update_editor(|editor, window, cx| {
11832        editor.show_completions(&ShowCompletions::default(), window, cx);
11833    });
11834    cx.executor().run_until_parked();
11835    cx.condition(|editor, _| editor.context_menu_visible())
11836        .await;
11837    cx.update_editor(|editor, window, cx| {
11838        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11839        {
11840            assert_eq!(
11841                completion_menu_entries(&menu),
11842                &["let"],
11843                "With no digits in the completion query, no digits should be in the word completions"
11844            );
11845        } else {
11846            panic!("expected completion menu to be open");
11847        }
11848        editor.cancel(&Cancel, window, cx);
11849    });
11850
11851    cx.set_state(indoc! {"11852        0_usize
11853        let
11854        3
11855        33.35f32
11856    "});
11857    cx.update_editor(|editor, window, cx| {
11858        editor.show_completions(&ShowCompletions::default(), window, cx);
11859    });
11860    cx.executor().run_until_parked();
11861    cx.condition(|editor, _| editor.context_menu_visible())
11862        .await;
11863    cx.update_editor(|editor, _, _| {
11864        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11865        {
11866            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11867                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11868        } else {
11869            panic!("expected completion menu to be open");
11870        }
11871    });
11872}
11873
11874fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11875    let position = || lsp::Position {
11876        line: params.text_document_position.position.line,
11877        character: params.text_document_position.position.character,
11878    };
11879    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11880        range: lsp::Range {
11881            start: position(),
11882            end: position(),
11883        },
11884        new_text: text.to_string(),
11885    }))
11886}
11887
11888#[gpui::test]
11889async fn test_multiline_completion(cx: &mut TestAppContext) {
11890    init_test(cx, |_| {});
11891
11892    let fs = FakeFs::new(cx.executor());
11893    fs.insert_tree(
11894        path!("/a"),
11895        json!({
11896            "main.ts": "a",
11897        }),
11898    )
11899    .await;
11900
11901    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11902    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11903    let typescript_language = Arc::new(Language::new(
11904        LanguageConfig {
11905            name: "TypeScript".into(),
11906            matcher: LanguageMatcher {
11907                path_suffixes: vec!["ts".to_string()],
11908                ..LanguageMatcher::default()
11909            },
11910            line_comments: vec!["// ".into()],
11911            ..LanguageConfig::default()
11912        },
11913        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11914    ));
11915    language_registry.add(typescript_language.clone());
11916    let mut fake_servers = language_registry.register_fake_lsp(
11917        "TypeScript",
11918        FakeLspAdapter {
11919            capabilities: lsp::ServerCapabilities {
11920                completion_provider: Some(lsp::CompletionOptions {
11921                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11922                    ..lsp::CompletionOptions::default()
11923                }),
11924                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11925                ..lsp::ServerCapabilities::default()
11926            },
11927            // Emulate vtsls label generation
11928            label_for_completion: Some(Box::new(|item, _| {
11929                let text = if let Some(description) = item
11930                    .label_details
11931                    .as_ref()
11932                    .and_then(|label_details| label_details.description.as_ref())
11933                {
11934                    format!("{} {}", item.label, description)
11935                } else if let Some(detail) = &item.detail {
11936                    format!("{} {}", item.label, detail)
11937                } else {
11938                    item.label.clone()
11939                };
11940                let len = text.len();
11941                Some(language::CodeLabel {
11942                    text,
11943                    runs: Vec::new(),
11944                    filter_range: 0..len,
11945                })
11946            })),
11947            ..FakeLspAdapter::default()
11948        },
11949    );
11950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11951    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11952    let worktree_id = workspace
11953        .update(cx, |workspace, _window, cx| {
11954            workspace.project().update(cx, |project, cx| {
11955                project.worktrees(cx).next().unwrap().read(cx).id()
11956            })
11957        })
11958        .unwrap();
11959    let _buffer = project
11960        .update(cx, |project, cx| {
11961            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11962        })
11963        .await
11964        .unwrap();
11965    let editor = workspace
11966        .update(cx, |workspace, window, cx| {
11967            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11968        })
11969        .unwrap()
11970        .await
11971        .unwrap()
11972        .downcast::<Editor>()
11973        .unwrap();
11974    let fake_server = fake_servers.next().await.unwrap();
11975
11976    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11977    let multiline_label_2 = "a\nb\nc\n";
11978    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11979    let multiline_description = "d\ne\nf\n";
11980    let multiline_detail_2 = "g\nh\ni\n";
11981
11982    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11983        move |params, _| async move {
11984            Ok(Some(lsp::CompletionResponse::Array(vec![
11985                lsp::CompletionItem {
11986                    label: multiline_label.to_string(),
11987                    text_edit: gen_text_edit(&params, "new_text_1"),
11988                    ..lsp::CompletionItem::default()
11989                },
11990                lsp::CompletionItem {
11991                    label: "single line label 1".to_string(),
11992                    detail: Some(multiline_detail.to_string()),
11993                    text_edit: gen_text_edit(&params, "new_text_2"),
11994                    ..lsp::CompletionItem::default()
11995                },
11996                lsp::CompletionItem {
11997                    label: "single line label 2".to_string(),
11998                    label_details: Some(lsp::CompletionItemLabelDetails {
11999                        description: Some(multiline_description.to_string()),
12000                        detail: None,
12001                    }),
12002                    text_edit: gen_text_edit(&params, "new_text_2"),
12003                    ..lsp::CompletionItem::default()
12004                },
12005                lsp::CompletionItem {
12006                    label: multiline_label_2.to_string(),
12007                    detail: Some(multiline_detail_2.to_string()),
12008                    text_edit: gen_text_edit(&params, "new_text_3"),
12009                    ..lsp::CompletionItem::default()
12010                },
12011                lsp::CompletionItem {
12012                    label: "Label with many     spaces and \t but without newlines".to_string(),
12013                    detail: Some(
12014                        "Details with many     spaces and \t but without newlines".to_string(),
12015                    ),
12016                    text_edit: gen_text_edit(&params, "new_text_4"),
12017                    ..lsp::CompletionItem::default()
12018                },
12019            ])))
12020        },
12021    );
12022
12023    editor.update_in(cx, |editor, window, cx| {
12024        cx.focus_self(window);
12025        editor.move_to_end(&MoveToEnd, window, cx);
12026        editor.handle_input(".", window, cx);
12027    });
12028    cx.run_until_parked();
12029    completion_handle.next().await.unwrap();
12030
12031    editor.update(cx, |editor, _| {
12032        assert!(editor.context_menu_visible());
12033        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12034        {
12035            let completion_labels = menu
12036                .completions
12037                .borrow()
12038                .iter()
12039                .map(|c| c.label.text.clone())
12040                .collect::<Vec<_>>();
12041            assert_eq!(
12042                completion_labels,
12043                &[
12044                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12045                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12046                    "single line label 2 d e f ",
12047                    "a b c g h i ",
12048                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12049                ],
12050                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12051            );
12052
12053            for completion in menu
12054                .completions
12055                .borrow()
12056                .iter() {
12057                    assert_eq!(
12058                        completion.label.filter_range,
12059                        0..completion.label.text.len(),
12060                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12061                    );
12062                }
12063        } else {
12064            panic!("expected completion menu to be open");
12065        }
12066    });
12067}
12068
12069#[gpui::test]
12070async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12071    init_test(cx, |_| {});
12072    let mut cx = EditorLspTestContext::new_rust(
12073        lsp::ServerCapabilities {
12074            completion_provider: Some(lsp::CompletionOptions {
12075                trigger_characters: Some(vec![".".to_string()]),
12076                ..Default::default()
12077            }),
12078            ..Default::default()
12079        },
12080        cx,
12081    )
12082    .await;
12083    cx.lsp
12084        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12085            Ok(Some(lsp::CompletionResponse::Array(vec![
12086                lsp::CompletionItem {
12087                    label: "first".into(),
12088                    ..Default::default()
12089                },
12090                lsp::CompletionItem {
12091                    label: "last".into(),
12092                    ..Default::default()
12093                },
12094            ])))
12095        });
12096    cx.set_state("variableˇ");
12097    cx.simulate_keystroke(".");
12098    cx.executor().run_until_parked();
12099
12100    cx.update_editor(|editor, _, _| {
12101        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12102        {
12103            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12104        } else {
12105            panic!("expected completion menu to be open");
12106        }
12107    });
12108
12109    cx.update_editor(|editor, window, cx| {
12110        editor.move_page_down(&MovePageDown::default(), window, cx);
12111        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12112        {
12113            assert!(
12114                menu.selected_item == 1,
12115                "expected PageDown to select the last item from the context menu"
12116            );
12117        } else {
12118            panic!("expected completion menu to stay open after PageDown");
12119        }
12120    });
12121
12122    cx.update_editor(|editor, window, cx| {
12123        editor.move_page_up(&MovePageUp::default(), window, cx);
12124        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12125        {
12126            assert!(
12127                menu.selected_item == 0,
12128                "expected PageUp to select the first item from the context menu"
12129            );
12130        } else {
12131            panic!("expected completion menu to stay open after PageUp");
12132        }
12133    });
12134}
12135
12136#[gpui::test]
12137async fn test_as_is_completions(cx: &mut TestAppContext) {
12138    init_test(cx, |_| {});
12139    let mut cx = EditorLspTestContext::new_rust(
12140        lsp::ServerCapabilities {
12141            completion_provider: Some(lsp::CompletionOptions {
12142                ..Default::default()
12143            }),
12144            ..Default::default()
12145        },
12146        cx,
12147    )
12148    .await;
12149    cx.lsp
12150        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12151            Ok(Some(lsp::CompletionResponse::Array(vec![
12152                lsp::CompletionItem {
12153                    label: "unsafe".into(),
12154                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12155                        range: lsp::Range {
12156                            start: lsp::Position {
12157                                line: 1,
12158                                character: 2,
12159                            },
12160                            end: lsp::Position {
12161                                line: 1,
12162                                character: 3,
12163                            },
12164                        },
12165                        new_text: "unsafe".to_string(),
12166                    })),
12167                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12168                    ..Default::default()
12169                },
12170            ])))
12171        });
12172    cx.set_state("fn a() {}\n");
12173    cx.executor().run_until_parked();
12174    cx.update_editor(|editor, window, cx| {
12175        editor.show_completions(
12176            &ShowCompletions {
12177                trigger: Some("\n".into()),
12178            },
12179            window,
12180            cx,
12181        );
12182    });
12183    cx.executor().run_until_parked();
12184
12185    cx.update_editor(|editor, window, cx| {
12186        editor.confirm_completion(&Default::default(), window, cx)
12187    });
12188    cx.executor().run_until_parked();
12189    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12190}
12191
12192#[gpui::test]
12193async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12194    init_test(cx, |_| {});
12195
12196    let mut cx = EditorLspTestContext::new_rust(
12197        lsp::ServerCapabilities {
12198            completion_provider: Some(lsp::CompletionOptions {
12199                trigger_characters: Some(vec![".".to_string()]),
12200                resolve_provider: Some(true),
12201                ..Default::default()
12202            }),
12203            ..Default::default()
12204        },
12205        cx,
12206    )
12207    .await;
12208
12209    cx.set_state("fn main() { let a = 2ˇ; }");
12210    cx.simulate_keystroke(".");
12211    let completion_item = lsp::CompletionItem {
12212        label: "Some".into(),
12213        kind: Some(lsp::CompletionItemKind::SNIPPET),
12214        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12215        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12216            kind: lsp::MarkupKind::Markdown,
12217            value: "```rust\nSome(2)\n```".to_string(),
12218        })),
12219        deprecated: Some(false),
12220        sort_text: Some("Some".to_string()),
12221        filter_text: Some("Some".to_string()),
12222        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12223        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12224            range: lsp::Range {
12225                start: lsp::Position {
12226                    line: 0,
12227                    character: 22,
12228                },
12229                end: lsp::Position {
12230                    line: 0,
12231                    character: 22,
12232                },
12233            },
12234            new_text: "Some(2)".to_string(),
12235        })),
12236        additional_text_edits: Some(vec![lsp::TextEdit {
12237            range: lsp::Range {
12238                start: lsp::Position {
12239                    line: 0,
12240                    character: 20,
12241                },
12242                end: lsp::Position {
12243                    line: 0,
12244                    character: 22,
12245                },
12246            },
12247            new_text: "".to_string(),
12248        }]),
12249        ..Default::default()
12250    };
12251
12252    let closure_completion_item = completion_item.clone();
12253    let counter = Arc::new(AtomicUsize::new(0));
12254    let counter_clone = counter.clone();
12255    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12256        let task_completion_item = closure_completion_item.clone();
12257        counter_clone.fetch_add(1, atomic::Ordering::Release);
12258        async move {
12259            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12260                is_incomplete: true,
12261                item_defaults: None,
12262                items: vec![task_completion_item],
12263            })))
12264        }
12265    });
12266
12267    cx.condition(|editor, _| editor.context_menu_visible())
12268        .await;
12269    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12270    assert!(request.next().await.is_some());
12271    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12272
12273    cx.simulate_keystrokes("S o m");
12274    cx.condition(|editor, _| editor.context_menu_visible())
12275        .await;
12276    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12277    assert!(request.next().await.is_some());
12278    assert!(request.next().await.is_some());
12279    assert!(request.next().await.is_some());
12280    request.close();
12281    assert!(request.next().await.is_none());
12282    assert_eq!(
12283        counter.load(atomic::Ordering::Acquire),
12284        4,
12285        "With the completions menu open, only one LSP request should happen per input"
12286    );
12287}
12288
12289#[gpui::test]
12290async fn test_toggle_comment(cx: &mut TestAppContext) {
12291    init_test(cx, |_| {});
12292    let mut cx = EditorTestContext::new(cx).await;
12293    let language = Arc::new(Language::new(
12294        LanguageConfig {
12295            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12296            ..Default::default()
12297        },
12298        Some(tree_sitter_rust::LANGUAGE.into()),
12299    ));
12300    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12301
12302    // If multiple selections intersect a line, the line is only toggled once.
12303    cx.set_state(indoc! {"
12304        fn a() {
12305            «//b();
12306            ˇ»// «c();
12307            //ˇ»  d();
12308        }
12309    "});
12310
12311    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12312
12313    cx.assert_editor_state(indoc! {"
12314        fn a() {
12315            «b();
12316            c();
12317            ˇ» d();
12318        }
12319    "});
12320
12321    // The comment prefix is inserted at the same column for every line in a
12322    // selection.
12323    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12324
12325    cx.assert_editor_state(indoc! {"
12326        fn a() {
12327            // «b();
12328            // c();
12329            ˇ»//  d();
12330        }
12331    "});
12332
12333    // If a selection ends at the beginning of a line, that line is not toggled.
12334    cx.set_selections_state(indoc! {"
12335        fn a() {
12336            // b();
12337            «// c();
12338        ˇ»    //  d();
12339        }
12340    "});
12341
12342    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12343
12344    cx.assert_editor_state(indoc! {"
12345        fn a() {
12346            // b();
12347            «c();
12348        ˇ»    //  d();
12349        }
12350    "});
12351
12352    // If a selection span a single line and is empty, the line is toggled.
12353    cx.set_state(indoc! {"
12354        fn a() {
12355            a();
12356            b();
12357        ˇ
12358        }
12359    "});
12360
12361    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12362
12363    cx.assert_editor_state(indoc! {"
12364        fn a() {
12365            a();
12366            b();
12367        //•ˇ
12368        }
12369    "});
12370
12371    // If a selection span multiple lines, empty lines are not toggled.
12372    cx.set_state(indoc! {"
12373        fn a() {
12374            «a();
12375
12376            c();ˇ»
12377        }
12378    "});
12379
12380    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12381
12382    cx.assert_editor_state(indoc! {"
12383        fn a() {
12384            // «a();
12385
12386            // c();ˇ»
12387        }
12388    "});
12389
12390    // If a selection includes multiple comment prefixes, all lines are uncommented.
12391    cx.set_state(indoc! {"
12392        fn a() {
12393            «// a();
12394            /// b();
12395            //! c();ˇ»
12396        }
12397    "});
12398
12399    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12400
12401    cx.assert_editor_state(indoc! {"
12402        fn a() {
12403            «a();
12404            b();
12405            c();ˇ»
12406        }
12407    "});
12408}
12409
12410#[gpui::test]
12411async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12412    init_test(cx, |_| {});
12413    let mut cx = EditorTestContext::new(cx).await;
12414    let language = Arc::new(Language::new(
12415        LanguageConfig {
12416            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12417            ..Default::default()
12418        },
12419        Some(tree_sitter_rust::LANGUAGE.into()),
12420    ));
12421    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12422
12423    let toggle_comments = &ToggleComments {
12424        advance_downwards: false,
12425        ignore_indent: true,
12426    };
12427
12428    // If multiple selections intersect a line, the line is only toggled once.
12429    cx.set_state(indoc! {"
12430        fn a() {
12431        //    «b();
12432        //    c();
12433        //    ˇ» d();
12434        }
12435    "});
12436
12437    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12438
12439    cx.assert_editor_state(indoc! {"
12440        fn a() {
12441            «b();
12442            c();
12443            ˇ» d();
12444        }
12445    "});
12446
12447    // The comment prefix is inserted at the beginning of each line
12448    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12449
12450    cx.assert_editor_state(indoc! {"
12451        fn a() {
12452        //    «b();
12453        //    c();
12454        //    ˇ» d();
12455        }
12456    "});
12457
12458    // If a selection ends at the beginning of a line, that line is not toggled.
12459    cx.set_selections_state(indoc! {"
12460        fn a() {
12461        //    b();
12462        //    «c();
12463        ˇ»//     d();
12464        }
12465    "});
12466
12467    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12468
12469    cx.assert_editor_state(indoc! {"
12470        fn a() {
12471        //    b();
12472            «c();
12473        ˇ»//     d();
12474        }
12475    "});
12476
12477    // If a selection span a single line and is empty, the line is toggled.
12478    cx.set_state(indoc! {"
12479        fn a() {
12480            a();
12481            b();
12482        ˇ
12483        }
12484    "});
12485
12486    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12487
12488    cx.assert_editor_state(indoc! {"
12489        fn a() {
12490            a();
12491            b();
12492        //ˇ
12493        }
12494    "});
12495
12496    // If a selection span multiple lines, empty lines are not toggled.
12497    cx.set_state(indoc! {"
12498        fn a() {
12499            «a();
12500
12501            c();ˇ»
12502        }
12503    "});
12504
12505    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12506
12507    cx.assert_editor_state(indoc! {"
12508        fn a() {
12509        //    «a();
12510
12511        //    c();ˇ»
12512        }
12513    "});
12514
12515    // If a selection includes multiple comment prefixes, all lines are uncommented.
12516    cx.set_state(indoc! {"
12517        fn a() {
12518        //    «a();
12519        ///    b();
12520        //!    c();ˇ»
12521        }
12522    "});
12523
12524    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12525
12526    cx.assert_editor_state(indoc! {"
12527        fn a() {
12528            «a();
12529            b();
12530            c();ˇ»
12531        }
12532    "});
12533}
12534
12535#[gpui::test]
12536async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12537    init_test(cx, |_| {});
12538
12539    let language = Arc::new(Language::new(
12540        LanguageConfig {
12541            line_comments: vec!["// ".into()],
12542            ..Default::default()
12543        },
12544        Some(tree_sitter_rust::LANGUAGE.into()),
12545    ));
12546
12547    let mut cx = EditorTestContext::new(cx).await;
12548
12549    cx.language_registry().add(language.clone());
12550    cx.update_buffer(|buffer, cx| {
12551        buffer.set_language(Some(language), cx);
12552    });
12553
12554    let toggle_comments = &ToggleComments {
12555        advance_downwards: true,
12556        ignore_indent: false,
12557    };
12558
12559    // Single cursor on one line -> advance
12560    // Cursor moves horizontally 3 characters as well on non-blank line
12561    cx.set_state(indoc!(
12562        "fn a() {
12563             ˇdog();
12564             cat();
12565        }"
12566    ));
12567    cx.update_editor(|editor, window, cx| {
12568        editor.toggle_comments(toggle_comments, window, cx);
12569    });
12570    cx.assert_editor_state(indoc!(
12571        "fn a() {
12572             // dog();
12573             catˇ();
12574        }"
12575    ));
12576
12577    // Single selection on one line -> don't advance
12578    cx.set_state(indoc!(
12579        "fn a() {
12580             «dog()ˇ»;
12581             cat();
12582        }"
12583    ));
12584    cx.update_editor(|editor, window, cx| {
12585        editor.toggle_comments(toggle_comments, window, cx);
12586    });
12587    cx.assert_editor_state(indoc!(
12588        "fn a() {
12589             // «dog()ˇ»;
12590             cat();
12591        }"
12592    ));
12593
12594    // Multiple cursors on one line -> advance
12595    cx.set_state(indoc!(
12596        "fn a() {
12597             ˇdˇog();
12598             cat();
12599        }"
12600    ));
12601    cx.update_editor(|editor, window, cx| {
12602        editor.toggle_comments(toggle_comments, window, cx);
12603    });
12604    cx.assert_editor_state(indoc!(
12605        "fn a() {
12606             // dog();
12607             catˇ(ˇ);
12608        }"
12609    ));
12610
12611    // Multiple cursors on one line, with selection -> don't advance
12612    cx.set_state(indoc!(
12613        "fn a() {
12614             ˇdˇog«()ˇ»;
12615             cat();
12616        }"
12617    ));
12618    cx.update_editor(|editor, window, cx| {
12619        editor.toggle_comments(toggle_comments, window, cx);
12620    });
12621    cx.assert_editor_state(indoc!(
12622        "fn a() {
12623             // ˇdˇog«()ˇ»;
12624             cat();
12625        }"
12626    ));
12627
12628    // Single cursor on one line -> advance
12629    // Cursor moves to column 0 on blank line
12630    cx.set_state(indoc!(
12631        "fn a() {
12632             ˇdog();
12633
12634             cat();
12635        }"
12636    ));
12637    cx.update_editor(|editor, window, cx| {
12638        editor.toggle_comments(toggle_comments, window, cx);
12639    });
12640    cx.assert_editor_state(indoc!(
12641        "fn a() {
12642             // dog();
12643        ˇ
12644             cat();
12645        }"
12646    ));
12647
12648    // Single cursor on one line -> advance
12649    // Cursor starts and ends at column 0
12650    cx.set_state(indoc!(
12651        "fn a() {
12652         ˇ    dog();
12653             cat();
12654        }"
12655    ));
12656    cx.update_editor(|editor, window, cx| {
12657        editor.toggle_comments(toggle_comments, window, cx);
12658    });
12659    cx.assert_editor_state(indoc!(
12660        "fn a() {
12661             // dog();
12662         ˇ    cat();
12663        }"
12664    ));
12665}
12666
12667#[gpui::test]
12668async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12669    init_test(cx, |_| {});
12670
12671    let mut cx = EditorTestContext::new(cx).await;
12672
12673    let html_language = Arc::new(
12674        Language::new(
12675            LanguageConfig {
12676                name: "HTML".into(),
12677                block_comment: Some(("<!-- ".into(), " -->".into())),
12678                ..Default::default()
12679            },
12680            Some(tree_sitter_html::LANGUAGE.into()),
12681        )
12682        .with_injection_query(
12683            r#"
12684            (script_element
12685                (raw_text) @injection.content
12686                (#set! injection.language "javascript"))
12687            "#,
12688        )
12689        .unwrap(),
12690    );
12691
12692    let javascript_language = Arc::new(Language::new(
12693        LanguageConfig {
12694            name: "JavaScript".into(),
12695            line_comments: vec!["// ".into()],
12696            ..Default::default()
12697        },
12698        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12699    ));
12700
12701    cx.language_registry().add(html_language.clone());
12702    cx.language_registry().add(javascript_language.clone());
12703    cx.update_buffer(|buffer, cx| {
12704        buffer.set_language(Some(html_language), cx);
12705    });
12706
12707    // Toggle comments for empty selections
12708    cx.set_state(
12709        &r#"
12710            <p>A</p>ˇ
12711            <p>B</p>ˇ
12712            <p>C</p>ˇ
12713        "#
12714        .unindent(),
12715    );
12716    cx.update_editor(|editor, window, cx| {
12717        editor.toggle_comments(&ToggleComments::default(), window, cx)
12718    });
12719    cx.assert_editor_state(
12720        &r#"
12721            <!-- <p>A</p>ˇ -->
12722            <!-- <p>B</p>ˇ -->
12723            <!-- <p>C</p>ˇ -->
12724        "#
12725        .unindent(),
12726    );
12727    cx.update_editor(|editor, window, cx| {
12728        editor.toggle_comments(&ToggleComments::default(), window, cx)
12729    });
12730    cx.assert_editor_state(
12731        &r#"
12732            <p>A</p>ˇ
12733            <p>B</p>ˇ
12734            <p>C</p>ˇ
12735        "#
12736        .unindent(),
12737    );
12738
12739    // Toggle comments for mixture of empty and non-empty selections, where
12740    // multiple selections occupy a given line.
12741    cx.set_state(
12742        &r#"
12743            <p>A«</p>
12744            <p>ˇ»B</p>ˇ
12745            <p>C«</p>
12746            <p>ˇ»D</p>ˇ
12747        "#
12748        .unindent(),
12749    );
12750
12751    cx.update_editor(|editor, window, cx| {
12752        editor.toggle_comments(&ToggleComments::default(), window, cx)
12753    });
12754    cx.assert_editor_state(
12755        &r#"
12756            <!-- <p>A«</p>
12757            <p>ˇ»B</p>ˇ -->
12758            <!-- <p>C«</p>
12759            <p>ˇ»D</p>ˇ -->
12760        "#
12761        .unindent(),
12762    );
12763    cx.update_editor(|editor, window, cx| {
12764        editor.toggle_comments(&ToggleComments::default(), window, cx)
12765    });
12766    cx.assert_editor_state(
12767        &r#"
12768            <p>A«</p>
12769            <p>ˇ»B</p>ˇ
12770            <p>C«</p>
12771            <p>ˇ»D</p>ˇ
12772        "#
12773        .unindent(),
12774    );
12775
12776    // Toggle comments when different languages are active for different
12777    // selections.
12778    cx.set_state(
12779        &r#"
12780            ˇ<script>
12781                ˇvar x = new Y();
12782            ˇ</script>
12783        "#
12784        .unindent(),
12785    );
12786    cx.executor().run_until_parked();
12787    cx.update_editor(|editor, window, cx| {
12788        editor.toggle_comments(&ToggleComments::default(), window, cx)
12789    });
12790    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12791    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12792    cx.assert_editor_state(
12793        &r#"
12794            <!-- ˇ<script> -->
12795                // ˇvar x = new Y();
12796            <!-- ˇ</script> -->
12797        "#
12798        .unindent(),
12799    );
12800}
12801
12802#[gpui::test]
12803fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12804    init_test(cx, |_| {});
12805
12806    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12807    let multibuffer = cx.new(|cx| {
12808        let mut multibuffer = MultiBuffer::new(ReadWrite);
12809        multibuffer.push_excerpts(
12810            buffer.clone(),
12811            [
12812                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12813                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12814            ],
12815            cx,
12816        );
12817        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12818        multibuffer
12819    });
12820
12821    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12822    editor.update_in(cx, |editor, window, cx| {
12823        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12824        editor.change_selections(None, window, cx, |s| {
12825            s.select_ranges([
12826                Point::new(0, 0)..Point::new(0, 0),
12827                Point::new(1, 0)..Point::new(1, 0),
12828            ])
12829        });
12830
12831        editor.handle_input("X", window, cx);
12832        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12833        assert_eq!(
12834            editor.selections.ranges(cx),
12835            [
12836                Point::new(0, 1)..Point::new(0, 1),
12837                Point::new(1, 1)..Point::new(1, 1),
12838            ]
12839        );
12840
12841        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12842        editor.change_selections(None, window, cx, |s| {
12843            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12844        });
12845        editor.backspace(&Default::default(), window, cx);
12846        assert_eq!(editor.text(cx), "Xa\nbbb");
12847        assert_eq!(
12848            editor.selections.ranges(cx),
12849            [Point::new(1, 0)..Point::new(1, 0)]
12850        );
12851
12852        editor.change_selections(None, window, cx, |s| {
12853            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12854        });
12855        editor.backspace(&Default::default(), window, cx);
12856        assert_eq!(editor.text(cx), "X\nbb");
12857        assert_eq!(
12858            editor.selections.ranges(cx),
12859            [Point::new(0, 1)..Point::new(0, 1)]
12860        );
12861    });
12862}
12863
12864#[gpui::test]
12865fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12866    init_test(cx, |_| {});
12867
12868    let markers = vec![('[', ']').into(), ('(', ')').into()];
12869    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12870        indoc! {"
12871            [aaaa
12872            (bbbb]
12873            cccc)",
12874        },
12875        markers.clone(),
12876    );
12877    let excerpt_ranges = markers.into_iter().map(|marker| {
12878        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12879        ExcerptRange::new(context.clone())
12880    });
12881    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12882    let multibuffer = cx.new(|cx| {
12883        let mut multibuffer = MultiBuffer::new(ReadWrite);
12884        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12885        multibuffer
12886    });
12887
12888    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12889    editor.update_in(cx, |editor, window, cx| {
12890        let (expected_text, selection_ranges) = marked_text_ranges(
12891            indoc! {"
12892                aaaa
12893                bˇbbb
12894                bˇbbˇb
12895                cccc"
12896            },
12897            true,
12898        );
12899        assert_eq!(editor.text(cx), expected_text);
12900        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12901
12902        editor.handle_input("X", window, cx);
12903
12904        let (expected_text, expected_selections) = marked_text_ranges(
12905            indoc! {"
12906                aaaa
12907                bXˇbbXb
12908                bXˇbbXˇb
12909                cccc"
12910            },
12911            false,
12912        );
12913        assert_eq!(editor.text(cx), expected_text);
12914        assert_eq!(editor.selections.ranges(cx), expected_selections);
12915
12916        editor.newline(&Newline, window, cx);
12917        let (expected_text, expected_selections) = marked_text_ranges(
12918            indoc! {"
12919                aaaa
12920                bX
12921                ˇbbX
12922                b
12923                bX
12924                ˇbbX
12925                ˇb
12926                cccc"
12927            },
12928            false,
12929        );
12930        assert_eq!(editor.text(cx), expected_text);
12931        assert_eq!(editor.selections.ranges(cx), expected_selections);
12932    });
12933}
12934
12935#[gpui::test]
12936fn test_refresh_selections(cx: &mut TestAppContext) {
12937    init_test(cx, |_| {});
12938
12939    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12940    let mut excerpt1_id = None;
12941    let multibuffer = cx.new(|cx| {
12942        let mut multibuffer = MultiBuffer::new(ReadWrite);
12943        excerpt1_id = multibuffer
12944            .push_excerpts(
12945                buffer.clone(),
12946                [
12947                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12948                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12949                ],
12950                cx,
12951            )
12952            .into_iter()
12953            .next();
12954        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12955        multibuffer
12956    });
12957
12958    let editor = cx.add_window(|window, cx| {
12959        let mut editor = build_editor(multibuffer.clone(), window, cx);
12960        let snapshot = editor.snapshot(window, cx);
12961        editor.change_selections(None, window, cx, |s| {
12962            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12963        });
12964        editor.begin_selection(
12965            Point::new(2, 1).to_display_point(&snapshot),
12966            true,
12967            1,
12968            window,
12969            cx,
12970        );
12971        assert_eq!(
12972            editor.selections.ranges(cx),
12973            [
12974                Point::new(1, 3)..Point::new(1, 3),
12975                Point::new(2, 1)..Point::new(2, 1),
12976            ]
12977        );
12978        editor
12979    });
12980
12981    // Refreshing selections is a no-op when excerpts haven't changed.
12982    _ = editor.update(cx, |editor, window, cx| {
12983        editor.change_selections(None, window, cx, |s| s.refresh());
12984        assert_eq!(
12985            editor.selections.ranges(cx),
12986            [
12987                Point::new(1, 3)..Point::new(1, 3),
12988                Point::new(2, 1)..Point::new(2, 1),
12989            ]
12990        );
12991    });
12992
12993    multibuffer.update(cx, |multibuffer, cx| {
12994        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12995    });
12996    _ = editor.update(cx, |editor, window, cx| {
12997        // Removing an excerpt causes the first selection to become degenerate.
12998        assert_eq!(
12999            editor.selections.ranges(cx),
13000            [
13001                Point::new(0, 0)..Point::new(0, 0),
13002                Point::new(0, 1)..Point::new(0, 1)
13003            ]
13004        );
13005
13006        // Refreshing selections will relocate the first selection to the original buffer
13007        // location.
13008        editor.change_selections(None, window, cx, |s| s.refresh());
13009        assert_eq!(
13010            editor.selections.ranges(cx),
13011            [
13012                Point::new(0, 1)..Point::new(0, 1),
13013                Point::new(0, 3)..Point::new(0, 3)
13014            ]
13015        );
13016        assert!(editor.selections.pending_anchor().is_some());
13017    });
13018}
13019
13020#[gpui::test]
13021fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13022    init_test(cx, |_| {});
13023
13024    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13025    let mut excerpt1_id = None;
13026    let multibuffer = cx.new(|cx| {
13027        let mut multibuffer = MultiBuffer::new(ReadWrite);
13028        excerpt1_id = multibuffer
13029            .push_excerpts(
13030                buffer.clone(),
13031                [
13032                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13033                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13034                ],
13035                cx,
13036            )
13037            .into_iter()
13038            .next();
13039        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13040        multibuffer
13041    });
13042
13043    let editor = cx.add_window(|window, cx| {
13044        let mut editor = build_editor(multibuffer.clone(), window, cx);
13045        let snapshot = editor.snapshot(window, cx);
13046        editor.begin_selection(
13047            Point::new(1, 3).to_display_point(&snapshot),
13048            false,
13049            1,
13050            window,
13051            cx,
13052        );
13053        assert_eq!(
13054            editor.selections.ranges(cx),
13055            [Point::new(1, 3)..Point::new(1, 3)]
13056        );
13057        editor
13058    });
13059
13060    multibuffer.update(cx, |multibuffer, cx| {
13061        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13062    });
13063    _ = editor.update(cx, |editor, window, cx| {
13064        assert_eq!(
13065            editor.selections.ranges(cx),
13066            [Point::new(0, 0)..Point::new(0, 0)]
13067        );
13068
13069        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13070        editor.change_selections(None, window, cx, |s| s.refresh());
13071        assert_eq!(
13072            editor.selections.ranges(cx),
13073            [Point::new(0, 3)..Point::new(0, 3)]
13074        );
13075        assert!(editor.selections.pending_anchor().is_some());
13076    });
13077}
13078
13079#[gpui::test]
13080async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13081    init_test(cx, |_| {});
13082
13083    let language = Arc::new(
13084        Language::new(
13085            LanguageConfig {
13086                brackets: BracketPairConfig {
13087                    pairs: vec![
13088                        BracketPair {
13089                            start: "{".to_string(),
13090                            end: "}".to_string(),
13091                            close: true,
13092                            surround: true,
13093                            newline: true,
13094                        },
13095                        BracketPair {
13096                            start: "/* ".to_string(),
13097                            end: " */".to_string(),
13098                            close: true,
13099                            surround: true,
13100                            newline: true,
13101                        },
13102                    ],
13103                    ..Default::default()
13104                },
13105                ..Default::default()
13106            },
13107            Some(tree_sitter_rust::LANGUAGE.into()),
13108        )
13109        .with_indents_query("")
13110        .unwrap(),
13111    );
13112
13113    let text = concat!(
13114        "{   }\n",     //
13115        "  x\n",       //
13116        "  /*   */\n", //
13117        "x\n",         //
13118        "{{} }\n",     //
13119    );
13120
13121    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13122    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13123    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13124    editor
13125        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13126        .await;
13127
13128    editor.update_in(cx, |editor, window, cx| {
13129        editor.change_selections(None, window, cx, |s| {
13130            s.select_display_ranges([
13131                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13132                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13133                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13134            ])
13135        });
13136        editor.newline(&Newline, window, cx);
13137
13138        assert_eq!(
13139            editor.buffer().read(cx).read(cx).text(),
13140            concat!(
13141                "{ \n",    // Suppress rustfmt
13142                "\n",      //
13143                "}\n",     //
13144                "  x\n",   //
13145                "  /* \n", //
13146                "  \n",    //
13147                "  */\n",  //
13148                "x\n",     //
13149                "{{} \n",  //
13150                "}\n",     //
13151            )
13152        );
13153    });
13154}
13155
13156#[gpui::test]
13157fn test_highlighted_ranges(cx: &mut TestAppContext) {
13158    init_test(cx, |_| {});
13159
13160    let editor = cx.add_window(|window, cx| {
13161        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13162        build_editor(buffer.clone(), window, cx)
13163    });
13164
13165    _ = editor.update(cx, |editor, window, cx| {
13166        struct Type1;
13167        struct Type2;
13168
13169        let buffer = editor.buffer.read(cx).snapshot(cx);
13170
13171        let anchor_range =
13172            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13173
13174        editor.highlight_background::<Type1>(
13175            &[
13176                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13177                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13178                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13179                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13180            ],
13181            |_| Hsla::red(),
13182            cx,
13183        );
13184        editor.highlight_background::<Type2>(
13185            &[
13186                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13187                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13188                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13189                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13190            ],
13191            |_| Hsla::green(),
13192            cx,
13193        );
13194
13195        let snapshot = editor.snapshot(window, cx);
13196        let mut highlighted_ranges = editor.background_highlights_in_range(
13197            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13198            &snapshot,
13199            cx.theme().colors(),
13200        );
13201        // Enforce a consistent ordering based on color without relying on the ordering of the
13202        // highlight's `TypeId` which is non-executor.
13203        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13204        assert_eq!(
13205            highlighted_ranges,
13206            &[
13207                (
13208                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13209                    Hsla::red(),
13210                ),
13211                (
13212                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13213                    Hsla::red(),
13214                ),
13215                (
13216                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13217                    Hsla::green(),
13218                ),
13219                (
13220                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13221                    Hsla::green(),
13222                ),
13223            ]
13224        );
13225        assert_eq!(
13226            editor.background_highlights_in_range(
13227                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13228                &snapshot,
13229                cx.theme().colors(),
13230            ),
13231            &[(
13232                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13233                Hsla::red(),
13234            )]
13235        );
13236    });
13237}
13238
13239#[gpui::test]
13240async fn test_following(cx: &mut TestAppContext) {
13241    init_test(cx, |_| {});
13242
13243    let fs = FakeFs::new(cx.executor());
13244    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13245
13246    let buffer = project.update(cx, |project, cx| {
13247        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13248        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13249    });
13250    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13251    let follower = cx.update(|cx| {
13252        cx.open_window(
13253            WindowOptions {
13254                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13255                    gpui::Point::new(px(0.), px(0.)),
13256                    gpui::Point::new(px(10.), px(80.)),
13257                ))),
13258                ..Default::default()
13259            },
13260            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13261        )
13262        .unwrap()
13263    });
13264
13265    let is_still_following = Rc::new(RefCell::new(true));
13266    let follower_edit_event_count = Rc::new(RefCell::new(0));
13267    let pending_update = Rc::new(RefCell::new(None));
13268    let leader_entity = leader.root(cx).unwrap();
13269    let follower_entity = follower.root(cx).unwrap();
13270    _ = follower.update(cx, {
13271        let update = pending_update.clone();
13272        let is_still_following = is_still_following.clone();
13273        let follower_edit_event_count = follower_edit_event_count.clone();
13274        |_, window, cx| {
13275            cx.subscribe_in(
13276                &leader_entity,
13277                window,
13278                move |_, leader, event, window, cx| {
13279                    leader.read(cx).add_event_to_update_proto(
13280                        event,
13281                        &mut update.borrow_mut(),
13282                        window,
13283                        cx,
13284                    );
13285                },
13286            )
13287            .detach();
13288
13289            cx.subscribe_in(
13290                &follower_entity,
13291                window,
13292                move |_, _, event: &EditorEvent, _window, _cx| {
13293                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13294                        *is_still_following.borrow_mut() = false;
13295                    }
13296
13297                    if let EditorEvent::BufferEdited = event {
13298                        *follower_edit_event_count.borrow_mut() += 1;
13299                    }
13300                },
13301            )
13302            .detach();
13303        }
13304    });
13305
13306    // Update the selections only
13307    _ = leader.update(cx, |leader, window, cx| {
13308        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13309    });
13310    follower
13311        .update(cx, |follower, window, cx| {
13312            follower.apply_update_proto(
13313                &project,
13314                pending_update.borrow_mut().take().unwrap(),
13315                window,
13316                cx,
13317            )
13318        })
13319        .unwrap()
13320        .await
13321        .unwrap();
13322    _ = follower.update(cx, |follower, _, cx| {
13323        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13324    });
13325    assert!(*is_still_following.borrow());
13326    assert_eq!(*follower_edit_event_count.borrow(), 0);
13327
13328    // Update the scroll position only
13329    _ = leader.update(cx, |leader, window, cx| {
13330        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13331    });
13332    follower
13333        .update(cx, |follower, window, cx| {
13334            follower.apply_update_proto(
13335                &project,
13336                pending_update.borrow_mut().take().unwrap(),
13337                window,
13338                cx,
13339            )
13340        })
13341        .unwrap()
13342        .await
13343        .unwrap();
13344    assert_eq!(
13345        follower
13346            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13347            .unwrap(),
13348        gpui::Point::new(1.5, 3.5)
13349    );
13350    assert!(*is_still_following.borrow());
13351    assert_eq!(*follower_edit_event_count.borrow(), 0);
13352
13353    // Update the selections and scroll position. The follower's scroll position is updated
13354    // via autoscroll, not via the leader's exact scroll position.
13355    _ = leader.update(cx, |leader, window, cx| {
13356        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13357        leader.request_autoscroll(Autoscroll::newest(), cx);
13358        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13359    });
13360    follower
13361        .update(cx, |follower, window, cx| {
13362            follower.apply_update_proto(
13363                &project,
13364                pending_update.borrow_mut().take().unwrap(),
13365                window,
13366                cx,
13367            )
13368        })
13369        .unwrap()
13370        .await
13371        .unwrap();
13372    _ = follower.update(cx, |follower, _, cx| {
13373        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13374        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13375    });
13376    assert!(*is_still_following.borrow());
13377
13378    // Creating a pending selection that precedes another selection
13379    _ = leader.update(cx, |leader, window, cx| {
13380        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13381        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13382    });
13383    follower
13384        .update(cx, |follower, window, cx| {
13385            follower.apply_update_proto(
13386                &project,
13387                pending_update.borrow_mut().take().unwrap(),
13388                window,
13389                cx,
13390            )
13391        })
13392        .unwrap()
13393        .await
13394        .unwrap();
13395    _ = follower.update(cx, |follower, _, cx| {
13396        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13397    });
13398    assert!(*is_still_following.borrow());
13399
13400    // Extend the pending selection so that it surrounds another selection
13401    _ = leader.update(cx, |leader, window, cx| {
13402        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13403    });
13404    follower
13405        .update(cx, |follower, window, cx| {
13406            follower.apply_update_proto(
13407                &project,
13408                pending_update.borrow_mut().take().unwrap(),
13409                window,
13410                cx,
13411            )
13412        })
13413        .unwrap()
13414        .await
13415        .unwrap();
13416    _ = follower.update(cx, |follower, _, cx| {
13417        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13418    });
13419
13420    // Scrolling locally breaks the follow
13421    _ = follower.update(cx, |follower, window, cx| {
13422        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13423        follower.set_scroll_anchor(
13424            ScrollAnchor {
13425                anchor: top_anchor,
13426                offset: gpui::Point::new(0.0, 0.5),
13427            },
13428            window,
13429            cx,
13430        );
13431    });
13432    assert!(!(*is_still_following.borrow()));
13433}
13434
13435#[gpui::test]
13436async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13437    init_test(cx, |_| {});
13438
13439    let fs = FakeFs::new(cx.executor());
13440    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13441    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13442    let pane = workspace
13443        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13444        .unwrap();
13445
13446    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13447
13448    let leader = pane.update_in(cx, |_, window, cx| {
13449        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13450        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13451    });
13452
13453    // Start following the editor when it has no excerpts.
13454    let mut state_message =
13455        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13456    let workspace_entity = workspace.root(cx).unwrap();
13457    let follower_1 = cx
13458        .update_window(*workspace.deref(), |_, window, cx| {
13459            Editor::from_state_proto(
13460                workspace_entity,
13461                ViewId {
13462                    creator: CollaboratorId::PeerId(PeerId::default()),
13463                    id: 0,
13464                },
13465                &mut state_message,
13466                window,
13467                cx,
13468            )
13469        })
13470        .unwrap()
13471        .unwrap()
13472        .await
13473        .unwrap();
13474
13475    let update_message = Rc::new(RefCell::new(None));
13476    follower_1.update_in(cx, {
13477        let update = update_message.clone();
13478        |_, window, cx| {
13479            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13480                leader.read(cx).add_event_to_update_proto(
13481                    event,
13482                    &mut update.borrow_mut(),
13483                    window,
13484                    cx,
13485                );
13486            })
13487            .detach();
13488        }
13489    });
13490
13491    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13492        (
13493            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13494            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13495        )
13496    });
13497
13498    // Insert some excerpts.
13499    leader.update(cx, |leader, cx| {
13500        leader.buffer.update(cx, |multibuffer, cx| {
13501            multibuffer.set_excerpts_for_path(
13502                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13503                buffer_1.clone(),
13504                vec![
13505                    Point::row_range(0..3),
13506                    Point::row_range(1..6),
13507                    Point::row_range(12..15),
13508                ],
13509                0,
13510                cx,
13511            );
13512            multibuffer.set_excerpts_for_path(
13513                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13514                buffer_2.clone(),
13515                vec![Point::row_range(0..6), Point::row_range(8..12)],
13516                0,
13517                cx,
13518            );
13519        });
13520    });
13521
13522    // Apply the update of adding the excerpts.
13523    follower_1
13524        .update_in(cx, |follower, window, cx| {
13525            follower.apply_update_proto(
13526                &project,
13527                update_message.borrow().clone().unwrap(),
13528                window,
13529                cx,
13530            )
13531        })
13532        .await
13533        .unwrap();
13534    assert_eq!(
13535        follower_1.update(cx, |editor, cx| editor.text(cx)),
13536        leader.update(cx, |editor, cx| editor.text(cx))
13537    );
13538    update_message.borrow_mut().take();
13539
13540    // Start following separately after it already has excerpts.
13541    let mut state_message =
13542        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13543    let workspace_entity = workspace.root(cx).unwrap();
13544    let follower_2 = cx
13545        .update_window(*workspace.deref(), |_, window, cx| {
13546            Editor::from_state_proto(
13547                workspace_entity,
13548                ViewId {
13549                    creator: CollaboratorId::PeerId(PeerId::default()),
13550                    id: 0,
13551                },
13552                &mut state_message,
13553                window,
13554                cx,
13555            )
13556        })
13557        .unwrap()
13558        .unwrap()
13559        .await
13560        .unwrap();
13561    assert_eq!(
13562        follower_2.update(cx, |editor, cx| editor.text(cx)),
13563        leader.update(cx, |editor, cx| editor.text(cx))
13564    );
13565
13566    // Remove some excerpts.
13567    leader.update(cx, |leader, cx| {
13568        leader.buffer.update(cx, |multibuffer, cx| {
13569            let excerpt_ids = multibuffer.excerpt_ids();
13570            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13571            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13572        });
13573    });
13574
13575    // Apply the update of removing the excerpts.
13576    follower_1
13577        .update_in(cx, |follower, window, cx| {
13578            follower.apply_update_proto(
13579                &project,
13580                update_message.borrow().clone().unwrap(),
13581                window,
13582                cx,
13583            )
13584        })
13585        .await
13586        .unwrap();
13587    follower_2
13588        .update_in(cx, |follower, window, cx| {
13589            follower.apply_update_proto(
13590                &project,
13591                update_message.borrow().clone().unwrap(),
13592                window,
13593                cx,
13594            )
13595        })
13596        .await
13597        .unwrap();
13598    update_message.borrow_mut().take();
13599    assert_eq!(
13600        follower_1.update(cx, |editor, cx| editor.text(cx)),
13601        leader.update(cx, |editor, cx| editor.text(cx))
13602    );
13603}
13604
13605#[gpui::test]
13606async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13607    init_test(cx, |_| {});
13608
13609    let mut cx = EditorTestContext::new(cx).await;
13610    let lsp_store =
13611        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13612
13613    cx.set_state(indoc! {"
13614        ˇfn func(abc def: i32) -> u32 {
13615        }
13616    "});
13617
13618    cx.update(|_, cx| {
13619        lsp_store.update(cx, |lsp_store, cx| {
13620            lsp_store
13621                .update_diagnostics(
13622                    LanguageServerId(0),
13623                    lsp::PublishDiagnosticsParams {
13624                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13625                        version: None,
13626                        diagnostics: vec![
13627                            lsp::Diagnostic {
13628                                range: lsp::Range::new(
13629                                    lsp::Position::new(0, 11),
13630                                    lsp::Position::new(0, 12),
13631                                ),
13632                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13633                                ..Default::default()
13634                            },
13635                            lsp::Diagnostic {
13636                                range: lsp::Range::new(
13637                                    lsp::Position::new(0, 12),
13638                                    lsp::Position::new(0, 15),
13639                                ),
13640                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13641                                ..Default::default()
13642                            },
13643                            lsp::Diagnostic {
13644                                range: lsp::Range::new(
13645                                    lsp::Position::new(0, 25),
13646                                    lsp::Position::new(0, 28),
13647                                ),
13648                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13649                                ..Default::default()
13650                            },
13651                        ],
13652                    },
13653                    DiagnosticSourceKind::Pushed,
13654                    &[],
13655                    cx,
13656                )
13657                .unwrap()
13658        });
13659    });
13660
13661    executor.run_until_parked();
13662
13663    cx.update_editor(|editor, window, cx| {
13664        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13665    });
13666
13667    cx.assert_editor_state(indoc! {"
13668        fn func(abc def: i32) -> ˇu32 {
13669        }
13670    "});
13671
13672    cx.update_editor(|editor, window, cx| {
13673        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13674    });
13675
13676    cx.assert_editor_state(indoc! {"
13677        fn func(abc ˇdef: i32) -> u32 {
13678        }
13679    "});
13680
13681    cx.update_editor(|editor, window, cx| {
13682        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13683    });
13684
13685    cx.assert_editor_state(indoc! {"
13686        fn func(abcˇ def: i32) -> u32 {
13687        }
13688    "});
13689
13690    cx.update_editor(|editor, window, cx| {
13691        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13692    });
13693
13694    cx.assert_editor_state(indoc! {"
13695        fn func(abc def: i32) -> ˇu32 {
13696        }
13697    "});
13698}
13699
13700#[gpui::test]
13701async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13702    init_test(cx, |_| {});
13703
13704    let mut cx = EditorTestContext::new(cx).await;
13705
13706    let diff_base = r#"
13707        use some::mod;
13708
13709        const A: u32 = 42;
13710
13711        fn main() {
13712            println!("hello");
13713
13714            println!("world");
13715        }
13716        "#
13717    .unindent();
13718
13719    // Edits are modified, removed, modified, added
13720    cx.set_state(
13721        &r#"
13722        use some::modified;
13723
13724        ˇ
13725        fn main() {
13726            println!("hello there");
13727
13728            println!("around the");
13729            println!("world");
13730        }
13731        "#
13732        .unindent(),
13733    );
13734
13735    cx.set_head_text(&diff_base);
13736    executor.run_until_parked();
13737
13738    cx.update_editor(|editor, window, cx| {
13739        //Wrap around the bottom of the buffer
13740        for _ in 0..3 {
13741            editor.go_to_next_hunk(&GoToHunk, window, cx);
13742        }
13743    });
13744
13745    cx.assert_editor_state(
13746        &r#"
13747        ˇuse some::modified;
13748
13749
13750        fn main() {
13751            println!("hello there");
13752
13753            println!("around the");
13754            println!("world");
13755        }
13756        "#
13757        .unindent(),
13758    );
13759
13760    cx.update_editor(|editor, window, cx| {
13761        //Wrap around the top of the buffer
13762        for _ in 0..2 {
13763            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13764        }
13765    });
13766
13767    cx.assert_editor_state(
13768        &r#"
13769        use some::modified;
13770
13771
13772        fn main() {
13773        ˇ    println!("hello there");
13774
13775            println!("around the");
13776            println!("world");
13777        }
13778        "#
13779        .unindent(),
13780    );
13781
13782    cx.update_editor(|editor, window, cx| {
13783        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13784    });
13785
13786    cx.assert_editor_state(
13787        &r#"
13788        use some::modified;
13789
13790        ˇ
13791        fn main() {
13792            println!("hello there");
13793
13794            println!("around the");
13795            println!("world");
13796        }
13797        "#
13798        .unindent(),
13799    );
13800
13801    cx.update_editor(|editor, window, cx| {
13802        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13803    });
13804
13805    cx.assert_editor_state(
13806        &r#"
13807        ˇuse some::modified;
13808
13809
13810        fn main() {
13811            println!("hello there");
13812
13813            println!("around the");
13814            println!("world");
13815        }
13816        "#
13817        .unindent(),
13818    );
13819
13820    cx.update_editor(|editor, window, cx| {
13821        for _ in 0..2 {
13822            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13823        }
13824    });
13825
13826    cx.assert_editor_state(
13827        &r#"
13828        use some::modified;
13829
13830
13831        fn main() {
13832        ˇ    println!("hello there");
13833
13834            println!("around the");
13835            println!("world");
13836        }
13837        "#
13838        .unindent(),
13839    );
13840
13841    cx.update_editor(|editor, window, cx| {
13842        editor.fold(&Fold, window, cx);
13843    });
13844
13845    cx.update_editor(|editor, window, cx| {
13846        editor.go_to_next_hunk(&GoToHunk, window, cx);
13847    });
13848
13849    cx.assert_editor_state(
13850        &r#"
13851        ˇuse some::modified;
13852
13853
13854        fn main() {
13855            println!("hello there");
13856
13857            println!("around the");
13858            println!("world");
13859        }
13860        "#
13861        .unindent(),
13862    );
13863}
13864
13865#[test]
13866fn test_split_words() {
13867    fn split(text: &str) -> Vec<&str> {
13868        split_words(text).collect()
13869    }
13870
13871    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13872    assert_eq!(split("hello_world"), &["hello_", "world"]);
13873    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13874    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13875    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13876    assert_eq!(split("helloworld"), &["helloworld"]);
13877
13878    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13879}
13880
13881#[gpui::test]
13882async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13883    init_test(cx, |_| {});
13884
13885    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13886    let mut assert = |before, after| {
13887        let _state_context = cx.set_state(before);
13888        cx.run_until_parked();
13889        cx.update_editor(|editor, window, cx| {
13890            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13891        });
13892        cx.run_until_parked();
13893        cx.assert_editor_state(after);
13894    };
13895
13896    // Outside bracket jumps to outside of matching bracket
13897    assert("console.logˇ(var);", "console.log(var)ˇ;");
13898    assert("console.log(var)ˇ;", "console.logˇ(var);");
13899
13900    // Inside bracket jumps to inside of matching bracket
13901    assert("console.log(ˇvar);", "console.log(varˇ);");
13902    assert("console.log(varˇ);", "console.log(ˇvar);");
13903
13904    // When outside a bracket and inside, favor jumping to the inside bracket
13905    assert(
13906        "console.log('foo', [1, 2, 3]ˇ);",
13907        "console.log(ˇ'foo', [1, 2, 3]);",
13908    );
13909    assert(
13910        "console.log(ˇ'foo', [1, 2, 3]);",
13911        "console.log('foo', [1, 2, 3]ˇ);",
13912    );
13913
13914    // Bias forward if two options are equally likely
13915    assert(
13916        "let result = curried_fun()ˇ();",
13917        "let result = curried_fun()()ˇ;",
13918    );
13919
13920    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13921    assert(
13922        indoc! {"
13923            function test() {
13924                console.log('test')ˇ
13925            }"},
13926        indoc! {"
13927            function test() {
13928                console.logˇ('test')
13929            }"},
13930    );
13931}
13932
13933#[gpui::test]
13934async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13935    init_test(cx, |_| {});
13936
13937    let fs = FakeFs::new(cx.executor());
13938    fs.insert_tree(
13939        path!("/a"),
13940        json!({
13941            "main.rs": "fn main() { let a = 5; }",
13942            "other.rs": "// Test file",
13943        }),
13944    )
13945    .await;
13946    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13947
13948    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13949    language_registry.add(Arc::new(Language::new(
13950        LanguageConfig {
13951            name: "Rust".into(),
13952            matcher: LanguageMatcher {
13953                path_suffixes: vec!["rs".to_string()],
13954                ..Default::default()
13955            },
13956            brackets: BracketPairConfig {
13957                pairs: vec![BracketPair {
13958                    start: "{".to_string(),
13959                    end: "}".to_string(),
13960                    close: true,
13961                    surround: true,
13962                    newline: true,
13963                }],
13964                disabled_scopes_by_bracket_ix: Vec::new(),
13965            },
13966            ..Default::default()
13967        },
13968        Some(tree_sitter_rust::LANGUAGE.into()),
13969    )));
13970    let mut fake_servers = language_registry.register_fake_lsp(
13971        "Rust",
13972        FakeLspAdapter {
13973            capabilities: lsp::ServerCapabilities {
13974                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13975                    first_trigger_character: "{".to_string(),
13976                    more_trigger_character: None,
13977                }),
13978                ..Default::default()
13979            },
13980            ..Default::default()
13981        },
13982    );
13983
13984    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13985
13986    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13987
13988    let worktree_id = workspace
13989        .update(cx, |workspace, _, cx| {
13990            workspace.project().update(cx, |project, cx| {
13991                project.worktrees(cx).next().unwrap().read(cx).id()
13992            })
13993        })
13994        .unwrap();
13995
13996    let buffer = project
13997        .update(cx, |project, cx| {
13998            project.open_local_buffer(path!("/a/main.rs"), cx)
13999        })
14000        .await
14001        .unwrap();
14002    let editor_handle = workspace
14003        .update(cx, |workspace, window, cx| {
14004            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14005        })
14006        .unwrap()
14007        .await
14008        .unwrap()
14009        .downcast::<Editor>()
14010        .unwrap();
14011
14012    cx.executor().start_waiting();
14013    let fake_server = fake_servers.next().await.unwrap();
14014
14015    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14016        |params, _| async move {
14017            assert_eq!(
14018                params.text_document_position.text_document.uri,
14019                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14020            );
14021            assert_eq!(
14022                params.text_document_position.position,
14023                lsp::Position::new(0, 21),
14024            );
14025
14026            Ok(Some(vec![lsp::TextEdit {
14027                new_text: "]".to_string(),
14028                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14029            }]))
14030        },
14031    );
14032
14033    editor_handle.update_in(cx, |editor, window, cx| {
14034        window.focus(&editor.focus_handle(cx));
14035        editor.change_selections(None, window, cx, |s| {
14036            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14037        });
14038        editor.handle_input("{", window, cx);
14039    });
14040
14041    cx.executor().run_until_parked();
14042
14043    buffer.update(cx, |buffer, _| {
14044        assert_eq!(
14045            buffer.text(),
14046            "fn main() { let a = {5}; }",
14047            "No extra braces from on type formatting should appear in the buffer"
14048        )
14049    });
14050}
14051
14052#[gpui::test]
14053async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14054    init_test(cx, |_| {});
14055
14056    let fs = FakeFs::new(cx.executor());
14057    fs.insert_tree(
14058        path!("/a"),
14059        json!({
14060            "main.rs": "fn main() { let a = 5; }",
14061            "other.rs": "// Test file",
14062        }),
14063    )
14064    .await;
14065
14066    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14067
14068    let server_restarts = Arc::new(AtomicUsize::new(0));
14069    let closure_restarts = Arc::clone(&server_restarts);
14070    let language_server_name = "test language server";
14071    let language_name: LanguageName = "Rust".into();
14072
14073    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14074    language_registry.add(Arc::new(Language::new(
14075        LanguageConfig {
14076            name: language_name.clone(),
14077            matcher: LanguageMatcher {
14078                path_suffixes: vec!["rs".to_string()],
14079                ..Default::default()
14080            },
14081            ..Default::default()
14082        },
14083        Some(tree_sitter_rust::LANGUAGE.into()),
14084    )));
14085    let mut fake_servers = language_registry.register_fake_lsp(
14086        "Rust",
14087        FakeLspAdapter {
14088            name: language_server_name,
14089            initialization_options: Some(json!({
14090                "testOptionValue": true
14091            })),
14092            initializer: Some(Box::new(move |fake_server| {
14093                let task_restarts = Arc::clone(&closure_restarts);
14094                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14095                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14096                    futures::future::ready(Ok(()))
14097                });
14098            })),
14099            ..Default::default()
14100        },
14101    );
14102
14103    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14104    let _buffer = project
14105        .update(cx, |project, cx| {
14106            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14107        })
14108        .await
14109        .unwrap();
14110    let _fake_server = fake_servers.next().await.unwrap();
14111    update_test_language_settings(cx, |language_settings| {
14112        language_settings.languages.insert(
14113            language_name.clone(),
14114            LanguageSettingsContent {
14115                tab_size: NonZeroU32::new(8),
14116                ..Default::default()
14117            },
14118        );
14119    });
14120    cx.executor().run_until_parked();
14121    assert_eq!(
14122        server_restarts.load(atomic::Ordering::Acquire),
14123        0,
14124        "Should not restart LSP server on an unrelated change"
14125    );
14126
14127    update_test_project_settings(cx, |project_settings| {
14128        project_settings.lsp.insert(
14129            "Some other server name".into(),
14130            LspSettings {
14131                binary: None,
14132                settings: None,
14133                initialization_options: Some(json!({
14134                    "some other init value": false
14135                })),
14136                enable_lsp_tasks: false,
14137            },
14138        );
14139    });
14140    cx.executor().run_until_parked();
14141    assert_eq!(
14142        server_restarts.load(atomic::Ordering::Acquire),
14143        0,
14144        "Should not restart LSP server on an unrelated LSP settings change"
14145    );
14146
14147    update_test_project_settings(cx, |project_settings| {
14148        project_settings.lsp.insert(
14149            language_server_name.into(),
14150            LspSettings {
14151                binary: None,
14152                settings: None,
14153                initialization_options: Some(json!({
14154                    "anotherInitValue": false
14155                })),
14156                enable_lsp_tasks: false,
14157            },
14158        );
14159    });
14160    cx.executor().run_until_parked();
14161    assert_eq!(
14162        server_restarts.load(atomic::Ordering::Acquire),
14163        1,
14164        "Should restart LSP server on a related LSP settings change"
14165    );
14166
14167    update_test_project_settings(cx, |project_settings| {
14168        project_settings.lsp.insert(
14169            language_server_name.into(),
14170            LspSettings {
14171                binary: None,
14172                settings: None,
14173                initialization_options: Some(json!({
14174                    "anotherInitValue": false
14175                })),
14176                enable_lsp_tasks: false,
14177            },
14178        );
14179    });
14180    cx.executor().run_until_parked();
14181    assert_eq!(
14182        server_restarts.load(atomic::Ordering::Acquire),
14183        1,
14184        "Should not restart LSP server on a related LSP settings change that is the same"
14185    );
14186
14187    update_test_project_settings(cx, |project_settings| {
14188        project_settings.lsp.insert(
14189            language_server_name.into(),
14190            LspSettings {
14191                binary: None,
14192                settings: None,
14193                initialization_options: None,
14194                enable_lsp_tasks: false,
14195            },
14196        );
14197    });
14198    cx.executor().run_until_parked();
14199    assert_eq!(
14200        server_restarts.load(atomic::Ordering::Acquire),
14201        2,
14202        "Should restart LSP server on another related LSP settings change"
14203    );
14204}
14205
14206#[gpui::test]
14207async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14208    init_test(cx, |_| {});
14209
14210    let mut cx = EditorLspTestContext::new_rust(
14211        lsp::ServerCapabilities {
14212            completion_provider: Some(lsp::CompletionOptions {
14213                trigger_characters: Some(vec![".".to_string()]),
14214                resolve_provider: Some(true),
14215                ..Default::default()
14216            }),
14217            ..Default::default()
14218        },
14219        cx,
14220    )
14221    .await;
14222
14223    cx.set_state("fn main() { let a = 2ˇ; }");
14224    cx.simulate_keystroke(".");
14225    let completion_item = lsp::CompletionItem {
14226        label: "some".into(),
14227        kind: Some(lsp::CompletionItemKind::SNIPPET),
14228        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14229        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14230            kind: lsp::MarkupKind::Markdown,
14231            value: "```rust\nSome(2)\n```".to_string(),
14232        })),
14233        deprecated: Some(false),
14234        sort_text: Some("fffffff2".to_string()),
14235        filter_text: Some("some".to_string()),
14236        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14237        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14238            range: lsp::Range {
14239                start: lsp::Position {
14240                    line: 0,
14241                    character: 22,
14242                },
14243                end: lsp::Position {
14244                    line: 0,
14245                    character: 22,
14246                },
14247            },
14248            new_text: "Some(2)".to_string(),
14249        })),
14250        additional_text_edits: Some(vec![lsp::TextEdit {
14251            range: lsp::Range {
14252                start: lsp::Position {
14253                    line: 0,
14254                    character: 20,
14255                },
14256                end: lsp::Position {
14257                    line: 0,
14258                    character: 22,
14259                },
14260            },
14261            new_text: "".to_string(),
14262        }]),
14263        ..Default::default()
14264    };
14265
14266    let closure_completion_item = completion_item.clone();
14267    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14268        let task_completion_item = closure_completion_item.clone();
14269        async move {
14270            Ok(Some(lsp::CompletionResponse::Array(vec![
14271                task_completion_item,
14272            ])))
14273        }
14274    });
14275
14276    request.next().await;
14277
14278    cx.condition(|editor, _| editor.context_menu_visible())
14279        .await;
14280    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14281        editor
14282            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14283            .unwrap()
14284    });
14285    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14286
14287    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14288        let task_completion_item = completion_item.clone();
14289        async move { Ok(task_completion_item) }
14290    })
14291    .next()
14292    .await
14293    .unwrap();
14294    apply_additional_edits.await.unwrap();
14295    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14296}
14297
14298#[gpui::test]
14299async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14300    init_test(cx, |_| {});
14301
14302    let mut cx = EditorLspTestContext::new_rust(
14303        lsp::ServerCapabilities {
14304            completion_provider: Some(lsp::CompletionOptions {
14305                trigger_characters: Some(vec![".".to_string()]),
14306                resolve_provider: Some(true),
14307                ..Default::default()
14308            }),
14309            ..Default::default()
14310        },
14311        cx,
14312    )
14313    .await;
14314
14315    cx.set_state("fn main() { let a = 2ˇ; }");
14316    cx.simulate_keystroke(".");
14317
14318    let item1 = lsp::CompletionItem {
14319        label: "method id()".to_string(),
14320        filter_text: Some("id".to_string()),
14321        detail: None,
14322        documentation: None,
14323        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14324            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14325            new_text: ".id".to_string(),
14326        })),
14327        ..lsp::CompletionItem::default()
14328    };
14329
14330    let item2 = lsp::CompletionItem {
14331        label: "other".to_string(),
14332        filter_text: Some("other".to_string()),
14333        detail: None,
14334        documentation: None,
14335        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14336            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14337            new_text: ".other".to_string(),
14338        })),
14339        ..lsp::CompletionItem::default()
14340    };
14341
14342    let item1 = item1.clone();
14343    cx.set_request_handler::<lsp::request::Completion, _, _>({
14344        let item1 = item1.clone();
14345        move |_, _, _| {
14346            let item1 = item1.clone();
14347            let item2 = item2.clone();
14348            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14349        }
14350    })
14351    .next()
14352    .await;
14353
14354    cx.condition(|editor, _| editor.context_menu_visible())
14355        .await;
14356    cx.update_editor(|editor, _, _| {
14357        let context_menu = editor.context_menu.borrow_mut();
14358        let context_menu = context_menu
14359            .as_ref()
14360            .expect("Should have the context menu deployed");
14361        match context_menu {
14362            CodeContextMenu::Completions(completions_menu) => {
14363                let completions = completions_menu.completions.borrow_mut();
14364                assert_eq!(
14365                    completions
14366                        .iter()
14367                        .map(|completion| &completion.label.text)
14368                        .collect::<Vec<_>>(),
14369                    vec!["method id()", "other"]
14370                )
14371            }
14372            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14373        }
14374    });
14375
14376    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14377        let item1 = item1.clone();
14378        move |_, item_to_resolve, _| {
14379            let item1 = item1.clone();
14380            async move {
14381                if item1 == item_to_resolve {
14382                    Ok(lsp::CompletionItem {
14383                        label: "method id()".to_string(),
14384                        filter_text: Some("id".to_string()),
14385                        detail: Some("Now resolved!".to_string()),
14386                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14387                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14388                            range: lsp::Range::new(
14389                                lsp::Position::new(0, 22),
14390                                lsp::Position::new(0, 22),
14391                            ),
14392                            new_text: ".id".to_string(),
14393                        })),
14394                        ..lsp::CompletionItem::default()
14395                    })
14396                } else {
14397                    Ok(item_to_resolve)
14398                }
14399            }
14400        }
14401    })
14402    .next()
14403    .await
14404    .unwrap();
14405    cx.run_until_parked();
14406
14407    cx.update_editor(|editor, window, cx| {
14408        editor.context_menu_next(&Default::default(), window, cx);
14409    });
14410
14411    cx.update_editor(|editor, _, _| {
14412        let context_menu = editor.context_menu.borrow_mut();
14413        let context_menu = context_menu
14414            .as_ref()
14415            .expect("Should have the context menu deployed");
14416        match context_menu {
14417            CodeContextMenu::Completions(completions_menu) => {
14418                let completions = completions_menu.completions.borrow_mut();
14419                assert_eq!(
14420                    completions
14421                        .iter()
14422                        .map(|completion| &completion.label.text)
14423                        .collect::<Vec<_>>(),
14424                    vec!["method id() Now resolved!", "other"],
14425                    "Should update first completion label, but not second as the filter text did not match."
14426                );
14427            }
14428            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14429        }
14430    });
14431}
14432
14433#[gpui::test]
14434async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14435    init_test(cx, |_| {});
14436    let mut cx = EditorLspTestContext::new_rust(
14437        lsp::ServerCapabilities {
14438            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14439            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14440            completion_provider: Some(lsp::CompletionOptions {
14441                resolve_provider: Some(true),
14442                ..Default::default()
14443            }),
14444            ..Default::default()
14445        },
14446        cx,
14447    )
14448    .await;
14449    cx.set_state(indoc! {"
14450        struct TestStruct {
14451            field: i32
14452        }
14453
14454        fn mainˇ() {
14455            let unused_var = 42;
14456            let test_struct = TestStruct { field: 42 };
14457        }
14458    "});
14459    let symbol_range = cx.lsp_range(indoc! {"
14460        struct TestStruct {
14461            field: i32
14462        }
14463
14464        «fn main»() {
14465            let unused_var = 42;
14466            let test_struct = TestStruct { field: 42 };
14467        }
14468    "});
14469    let mut hover_requests =
14470        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14471            Ok(Some(lsp::Hover {
14472                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14473                    kind: lsp::MarkupKind::Markdown,
14474                    value: "Function documentation".to_string(),
14475                }),
14476                range: Some(symbol_range),
14477            }))
14478        });
14479
14480    // Case 1: Test that code action menu hide hover popover
14481    cx.dispatch_action(Hover);
14482    hover_requests.next().await;
14483    cx.condition(|editor, _| editor.hover_state.visible()).await;
14484    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14485        move |_, _, _| async move {
14486            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14487                lsp::CodeAction {
14488                    title: "Remove unused variable".to_string(),
14489                    kind: Some(CodeActionKind::QUICKFIX),
14490                    edit: Some(lsp::WorkspaceEdit {
14491                        changes: Some(
14492                            [(
14493                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14494                                vec![lsp::TextEdit {
14495                                    range: lsp::Range::new(
14496                                        lsp::Position::new(5, 4),
14497                                        lsp::Position::new(5, 27),
14498                                    ),
14499                                    new_text: "".to_string(),
14500                                }],
14501                            )]
14502                            .into_iter()
14503                            .collect(),
14504                        ),
14505                        ..Default::default()
14506                    }),
14507                    ..Default::default()
14508                },
14509            )]))
14510        },
14511    );
14512    cx.update_editor(|editor, window, cx| {
14513        editor.toggle_code_actions(
14514            &ToggleCodeActions {
14515                deployed_from: None,
14516                quick_launch: false,
14517            },
14518            window,
14519            cx,
14520        );
14521    });
14522    code_action_requests.next().await;
14523    cx.run_until_parked();
14524    cx.condition(|editor, _| editor.context_menu_visible())
14525        .await;
14526    cx.update_editor(|editor, _, _| {
14527        assert!(
14528            !editor.hover_state.visible(),
14529            "Hover popover should be hidden when code action menu is shown"
14530        );
14531        // Hide code actions
14532        editor.context_menu.take();
14533    });
14534
14535    // Case 2: Test that code completions hide hover popover
14536    cx.dispatch_action(Hover);
14537    hover_requests.next().await;
14538    cx.condition(|editor, _| editor.hover_state.visible()).await;
14539    let counter = Arc::new(AtomicUsize::new(0));
14540    let mut completion_requests =
14541        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14542            let counter = counter.clone();
14543            async move {
14544                counter.fetch_add(1, atomic::Ordering::Release);
14545                Ok(Some(lsp::CompletionResponse::Array(vec![
14546                    lsp::CompletionItem {
14547                        label: "main".into(),
14548                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14549                        detail: Some("() -> ()".to_string()),
14550                        ..Default::default()
14551                    },
14552                    lsp::CompletionItem {
14553                        label: "TestStruct".into(),
14554                        kind: Some(lsp::CompletionItemKind::STRUCT),
14555                        detail: Some("struct TestStruct".to_string()),
14556                        ..Default::default()
14557                    },
14558                ])))
14559            }
14560        });
14561    cx.update_editor(|editor, window, cx| {
14562        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14563    });
14564    completion_requests.next().await;
14565    cx.condition(|editor, _| editor.context_menu_visible())
14566        .await;
14567    cx.update_editor(|editor, _, _| {
14568        assert!(
14569            !editor.hover_state.visible(),
14570            "Hover popover should be hidden when completion menu is shown"
14571        );
14572    });
14573}
14574
14575#[gpui::test]
14576async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14577    init_test(cx, |_| {});
14578
14579    let mut cx = EditorLspTestContext::new_rust(
14580        lsp::ServerCapabilities {
14581            completion_provider: Some(lsp::CompletionOptions {
14582                trigger_characters: Some(vec![".".to_string()]),
14583                resolve_provider: Some(true),
14584                ..Default::default()
14585            }),
14586            ..Default::default()
14587        },
14588        cx,
14589    )
14590    .await;
14591
14592    cx.set_state("fn main() { let a = 2ˇ; }");
14593    cx.simulate_keystroke(".");
14594
14595    let unresolved_item_1 = lsp::CompletionItem {
14596        label: "id".to_string(),
14597        filter_text: Some("id".to_string()),
14598        detail: None,
14599        documentation: None,
14600        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14601            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14602            new_text: ".id".to_string(),
14603        })),
14604        ..lsp::CompletionItem::default()
14605    };
14606    let resolved_item_1 = lsp::CompletionItem {
14607        additional_text_edits: Some(vec![lsp::TextEdit {
14608            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14609            new_text: "!!".to_string(),
14610        }]),
14611        ..unresolved_item_1.clone()
14612    };
14613    let unresolved_item_2 = lsp::CompletionItem {
14614        label: "other".to_string(),
14615        filter_text: Some("other".to_string()),
14616        detail: None,
14617        documentation: None,
14618        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14619            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14620            new_text: ".other".to_string(),
14621        })),
14622        ..lsp::CompletionItem::default()
14623    };
14624    let resolved_item_2 = lsp::CompletionItem {
14625        additional_text_edits: Some(vec![lsp::TextEdit {
14626            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14627            new_text: "??".to_string(),
14628        }]),
14629        ..unresolved_item_2.clone()
14630    };
14631
14632    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14633    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14634    cx.lsp
14635        .server
14636        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14637            let unresolved_item_1 = unresolved_item_1.clone();
14638            let resolved_item_1 = resolved_item_1.clone();
14639            let unresolved_item_2 = unresolved_item_2.clone();
14640            let resolved_item_2 = resolved_item_2.clone();
14641            let resolve_requests_1 = resolve_requests_1.clone();
14642            let resolve_requests_2 = resolve_requests_2.clone();
14643            move |unresolved_request, _| {
14644                let unresolved_item_1 = unresolved_item_1.clone();
14645                let resolved_item_1 = resolved_item_1.clone();
14646                let unresolved_item_2 = unresolved_item_2.clone();
14647                let resolved_item_2 = resolved_item_2.clone();
14648                let resolve_requests_1 = resolve_requests_1.clone();
14649                let resolve_requests_2 = resolve_requests_2.clone();
14650                async move {
14651                    if unresolved_request == unresolved_item_1 {
14652                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14653                        Ok(resolved_item_1.clone())
14654                    } else if unresolved_request == unresolved_item_2 {
14655                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14656                        Ok(resolved_item_2.clone())
14657                    } else {
14658                        panic!("Unexpected completion item {unresolved_request:?}")
14659                    }
14660                }
14661            }
14662        })
14663        .detach();
14664
14665    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14666        let unresolved_item_1 = unresolved_item_1.clone();
14667        let unresolved_item_2 = unresolved_item_2.clone();
14668        async move {
14669            Ok(Some(lsp::CompletionResponse::Array(vec![
14670                unresolved_item_1,
14671                unresolved_item_2,
14672            ])))
14673        }
14674    })
14675    .next()
14676    .await;
14677
14678    cx.condition(|editor, _| editor.context_menu_visible())
14679        .await;
14680    cx.update_editor(|editor, _, _| {
14681        let context_menu = editor.context_menu.borrow_mut();
14682        let context_menu = context_menu
14683            .as_ref()
14684            .expect("Should have the context menu deployed");
14685        match context_menu {
14686            CodeContextMenu::Completions(completions_menu) => {
14687                let completions = completions_menu.completions.borrow_mut();
14688                assert_eq!(
14689                    completions
14690                        .iter()
14691                        .map(|completion| &completion.label.text)
14692                        .collect::<Vec<_>>(),
14693                    vec!["id", "other"]
14694                )
14695            }
14696            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14697        }
14698    });
14699    cx.run_until_parked();
14700
14701    cx.update_editor(|editor, window, cx| {
14702        editor.context_menu_next(&ContextMenuNext, window, cx);
14703    });
14704    cx.run_until_parked();
14705    cx.update_editor(|editor, window, cx| {
14706        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14707    });
14708    cx.run_until_parked();
14709    cx.update_editor(|editor, window, cx| {
14710        editor.context_menu_next(&ContextMenuNext, window, cx);
14711    });
14712    cx.run_until_parked();
14713    cx.update_editor(|editor, window, cx| {
14714        editor
14715            .compose_completion(&ComposeCompletion::default(), window, cx)
14716            .expect("No task returned")
14717    })
14718    .await
14719    .expect("Completion failed");
14720    cx.run_until_parked();
14721
14722    cx.update_editor(|editor, _, cx| {
14723        assert_eq!(
14724            resolve_requests_1.load(atomic::Ordering::Acquire),
14725            1,
14726            "Should always resolve once despite multiple selections"
14727        );
14728        assert_eq!(
14729            resolve_requests_2.load(atomic::Ordering::Acquire),
14730            1,
14731            "Should always resolve once after multiple selections and applying the completion"
14732        );
14733        assert_eq!(
14734            editor.text(cx),
14735            "fn main() { let a = ??.other; }",
14736            "Should use resolved data when applying the completion"
14737        );
14738    });
14739}
14740
14741#[gpui::test]
14742async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14743    init_test(cx, |_| {});
14744
14745    let item_0 = lsp::CompletionItem {
14746        label: "abs".into(),
14747        insert_text: Some("abs".into()),
14748        data: Some(json!({ "very": "special"})),
14749        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14750        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14751            lsp::InsertReplaceEdit {
14752                new_text: "abs".to_string(),
14753                insert: lsp::Range::default(),
14754                replace: lsp::Range::default(),
14755            },
14756        )),
14757        ..lsp::CompletionItem::default()
14758    };
14759    let items = iter::once(item_0.clone())
14760        .chain((11..51).map(|i| lsp::CompletionItem {
14761            label: format!("item_{}", i),
14762            insert_text: Some(format!("item_{}", i)),
14763            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14764            ..lsp::CompletionItem::default()
14765        }))
14766        .collect::<Vec<_>>();
14767
14768    let default_commit_characters = vec!["?".to_string()];
14769    let default_data = json!({ "default": "data"});
14770    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14771    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14772    let default_edit_range = lsp::Range {
14773        start: lsp::Position {
14774            line: 0,
14775            character: 5,
14776        },
14777        end: lsp::Position {
14778            line: 0,
14779            character: 5,
14780        },
14781    };
14782
14783    let mut cx = EditorLspTestContext::new_rust(
14784        lsp::ServerCapabilities {
14785            completion_provider: Some(lsp::CompletionOptions {
14786                trigger_characters: Some(vec![".".to_string()]),
14787                resolve_provider: Some(true),
14788                ..Default::default()
14789            }),
14790            ..Default::default()
14791        },
14792        cx,
14793    )
14794    .await;
14795
14796    cx.set_state("fn main() { let a = 2ˇ; }");
14797    cx.simulate_keystroke(".");
14798
14799    let completion_data = default_data.clone();
14800    let completion_characters = default_commit_characters.clone();
14801    let completion_items = items.clone();
14802    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14803        let default_data = completion_data.clone();
14804        let default_commit_characters = completion_characters.clone();
14805        let items = completion_items.clone();
14806        async move {
14807            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14808                items,
14809                item_defaults: Some(lsp::CompletionListItemDefaults {
14810                    data: Some(default_data.clone()),
14811                    commit_characters: Some(default_commit_characters.clone()),
14812                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14813                        default_edit_range,
14814                    )),
14815                    insert_text_format: Some(default_insert_text_format),
14816                    insert_text_mode: Some(default_insert_text_mode),
14817                }),
14818                ..lsp::CompletionList::default()
14819            })))
14820        }
14821    })
14822    .next()
14823    .await;
14824
14825    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14826    cx.lsp
14827        .server
14828        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14829            let closure_resolved_items = resolved_items.clone();
14830            move |item_to_resolve, _| {
14831                let closure_resolved_items = closure_resolved_items.clone();
14832                async move {
14833                    closure_resolved_items.lock().push(item_to_resolve.clone());
14834                    Ok(item_to_resolve)
14835                }
14836            }
14837        })
14838        .detach();
14839
14840    cx.condition(|editor, _| editor.context_menu_visible())
14841        .await;
14842    cx.run_until_parked();
14843    cx.update_editor(|editor, _, _| {
14844        let menu = editor.context_menu.borrow_mut();
14845        match menu.as_ref().expect("should have the completions menu") {
14846            CodeContextMenu::Completions(completions_menu) => {
14847                assert_eq!(
14848                    completions_menu
14849                        .entries
14850                        .borrow()
14851                        .iter()
14852                        .map(|mat| mat.string.clone())
14853                        .collect::<Vec<String>>(),
14854                    items
14855                        .iter()
14856                        .map(|completion| completion.label.clone())
14857                        .collect::<Vec<String>>()
14858                );
14859            }
14860            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14861        }
14862    });
14863    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14864    // with 4 from the end.
14865    assert_eq!(
14866        *resolved_items.lock(),
14867        [&items[0..16], &items[items.len() - 4..items.len()]]
14868            .concat()
14869            .iter()
14870            .cloned()
14871            .map(|mut item| {
14872                if item.data.is_none() {
14873                    item.data = Some(default_data.clone());
14874                }
14875                item
14876            })
14877            .collect::<Vec<lsp::CompletionItem>>(),
14878        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14879    );
14880    resolved_items.lock().clear();
14881
14882    cx.update_editor(|editor, window, cx| {
14883        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14884    });
14885    cx.run_until_parked();
14886    // Completions that have already been resolved are skipped.
14887    assert_eq!(
14888        *resolved_items.lock(),
14889        items[items.len() - 16..items.len() - 4]
14890            .iter()
14891            .cloned()
14892            .map(|mut item| {
14893                if item.data.is_none() {
14894                    item.data = Some(default_data.clone());
14895                }
14896                item
14897            })
14898            .collect::<Vec<lsp::CompletionItem>>()
14899    );
14900    resolved_items.lock().clear();
14901}
14902
14903#[gpui::test]
14904async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14905    init_test(cx, |_| {});
14906
14907    let mut cx = EditorLspTestContext::new(
14908        Language::new(
14909            LanguageConfig {
14910                matcher: LanguageMatcher {
14911                    path_suffixes: vec!["jsx".into()],
14912                    ..Default::default()
14913                },
14914                overrides: [(
14915                    "element".into(),
14916                    LanguageConfigOverride {
14917                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14918                        ..Default::default()
14919                    },
14920                )]
14921                .into_iter()
14922                .collect(),
14923                ..Default::default()
14924            },
14925            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14926        )
14927        .with_override_query("(jsx_self_closing_element) @element")
14928        .unwrap(),
14929        lsp::ServerCapabilities {
14930            completion_provider: Some(lsp::CompletionOptions {
14931                trigger_characters: Some(vec![":".to_string()]),
14932                ..Default::default()
14933            }),
14934            ..Default::default()
14935        },
14936        cx,
14937    )
14938    .await;
14939
14940    cx.lsp
14941        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14942            Ok(Some(lsp::CompletionResponse::Array(vec![
14943                lsp::CompletionItem {
14944                    label: "bg-blue".into(),
14945                    ..Default::default()
14946                },
14947                lsp::CompletionItem {
14948                    label: "bg-red".into(),
14949                    ..Default::default()
14950                },
14951                lsp::CompletionItem {
14952                    label: "bg-yellow".into(),
14953                    ..Default::default()
14954                },
14955            ])))
14956        });
14957
14958    cx.set_state(r#"<p class="bgˇ" />"#);
14959
14960    // Trigger completion when typing a dash, because the dash is an extra
14961    // word character in the 'element' scope, which contains the cursor.
14962    cx.simulate_keystroke("-");
14963    cx.executor().run_until_parked();
14964    cx.update_editor(|editor, _, _| {
14965        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14966        {
14967            assert_eq!(
14968                completion_menu_entries(&menu),
14969                &["bg-red", "bg-blue", "bg-yellow"]
14970            );
14971        } else {
14972            panic!("expected completion menu to be open");
14973        }
14974    });
14975
14976    cx.simulate_keystroke("l");
14977    cx.executor().run_until_parked();
14978    cx.update_editor(|editor, _, _| {
14979        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14980        {
14981            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14982        } else {
14983            panic!("expected completion menu to be open");
14984        }
14985    });
14986
14987    // When filtering completions, consider the character after the '-' to
14988    // be the start of a subword.
14989    cx.set_state(r#"<p class="yelˇ" />"#);
14990    cx.simulate_keystroke("l");
14991    cx.executor().run_until_parked();
14992    cx.update_editor(|editor, _, _| {
14993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14994        {
14995            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14996        } else {
14997            panic!("expected completion menu to be open");
14998        }
14999    });
15000}
15001
15002fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15003    let entries = menu.entries.borrow();
15004    entries.iter().map(|mat| mat.string.clone()).collect()
15005}
15006
15007#[gpui::test]
15008async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15009    init_test(cx, |settings| {
15010        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15011            FormatterList(vec![Formatter::Prettier].into()),
15012        ))
15013    });
15014
15015    let fs = FakeFs::new(cx.executor());
15016    fs.insert_file(path!("/file.ts"), Default::default()).await;
15017
15018    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15019    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15020
15021    language_registry.add(Arc::new(Language::new(
15022        LanguageConfig {
15023            name: "TypeScript".into(),
15024            matcher: LanguageMatcher {
15025                path_suffixes: vec!["ts".to_string()],
15026                ..Default::default()
15027            },
15028            ..Default::default()
15029        },
15030        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15031    )));
15032    update_test_language_settings(cx, |settings| {
15033        settings.defaults.prettier = Some(PrettierSettings {
15034            allowed: true,
15035            ..PrettierSettings::default()
15036        });
15037    });
15038
15039    let test_plugin = "test_plugin";
15040    let _ = language_registry.register_fake_lsp(
15041        "TypeScript",
15042        FakeLspAdapter {
15043            prettier_plugins: vec![test_plugin],
15044            ..Default::default()
15045        },
15046    );
15047
15048    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15049    let buffer = project
15050        .update(cx, |project, cx| {
15051            project.open_local_buffer(path!("/file.ts"), cx)
15052        })
15053        .await
15054        .unwrap();
15055
15056    let buffer_text = "one\ntwo\nthree\n";
15057    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15058    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15059    editor.update_in(cx, |editor, window, cx| {
15060        editor.set_text(buffer_text, window, cx)
15061    });
15062
15063    editor
15064        .update_in(cx, |editor, window, cx| {
15065            editor.perform_format(
15066                project.clone(),
15067                FormatTrigger::Manual,
15068                FormatTarget::Buffers,
15069                window,
15070                cx,
15071            )
15072        })
15073        .unwrap()
15074        .await;
15075    assert_eq!(
15076        editor.update(cx, |editor, cx| editor.text(cx)),
15077        buffer_text.to_string() + prettier_format_suffix,
15078        "Test prettier formatting was not applied to the original buffer text",
15079    );
15080
15081    update_test_language_settings(cx, |settings| {
15082        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15083    });
15084    let format = editor.update_in(cx, |editor, window, cx| {
15085        editor.perform_format(
15086            project.clone(),
15087            FormatTrigger::Manual,
15088            FormatTarget::Buffers,
15089            window,
15090            cx,
15091        )
15092    });
15093    format.await.unwrap();
15094    assert_eq!(
15095        editor.update(cx, |editor, cx| editor.text(cx)),
15096        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15097        "Autoformatting (via test prettier) was not applied to the original buffer text",
15098    );
15099}
15100
15101#[gpui::test]
15102async fn test_addition_reverts(cx: &mut TestAppContext) {
15103    init_test(cx, |_| {});
15104    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15105    let base_text = indoc! {r#"
15106        struct Row;
15107        struct Row1;
15108        struct Row2;
15109
15110        struct Row4;
15111        struct Row5;
15112        struct Row6;
15113
15114        struct Row8;
15115        struct Row9;
15116        struct Row10;"#};
15117
15118    // When addition hunks are not adjacent to carets, no hunk revert is performed
15119    assert_hunk_revert(
15120        indoc! {r#"struct Row;
15121                   struct Row1;
15122                   struct Row1.1;
15123                   struct Row1.2;
15124                   struct Row2;ˇ
15125
15126                   struct Row4;
15127                   struct Row5;
15128                   struct Row6;
15129
15130                   struct Row8;
15131                   ˇstruct Row9;
15132                   struct Row9.1;
15133                   struct Row9.2;
15134                   struct Row9.3;
15135                   struct Row10;"#},
15136        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15137        indoc! {r#"struct Row;
15138                   struct Row1;
15139                   struct Row1.1;
15140                   struct Row1.2;
15141                   struct Row2;ˇ
15142
15143                   struct Row4;
15144                   struct Row5;
15145                   struct Row6;
15146
15147                   struct Row8;
15148                   ˇstruct Row9;
15149                   struct Row9.1;
15150                   struct Row9.2;
15151                   struct Row9.3;
15152                   struct Row10;"#},
15153        base_text,
15154        &mut cx,
15155    );
15156    // Same for selections
15157    assert_hunk_revert(
15158        indoc! {r#"struct Row;
15159                   struct Row1;
15160                   struct Row2;
15161                   struct Row2.1;
15162                   struct Row2.2;
15163                   «ˇ
15164                   struct Row4;
15165                   struct» Row5;
15166                   «struct Row6;
15167                   ˇ»
15168                   struct Row9.1;
15169                   struct Row9.2;
15170                   struct Row9.3;
15171                   struct Row8;
15172                   struct Row9;
15173                   struct Row10;"#},
15174        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15175        indoc! {r#"struct Row;
15176                   struct Row1;
15177                   struct Row2;
15178                   struct Row2.1;
15179                   struct Row2.2;
15180                   «ˇ
15181                   struct Row4;
15182                   struct» Row5;
15183                   «struct Row6;
15184                   ˇ»
15185                   struct Row9.1;
15186                   struct Row9.2;
15187                   struct Row9.3;
15188                   struct Row8;
15189                   struct Row9;
15190                   struct Row10;"#},
15191        base_text,
15192        &mut cx,
15193    );
15194
15195    // When carets and selections intersect the addition hunks, those are reverted.
15196    // Adjacent carets got merged.
15197    assert_hunk_revert(
15198        indoc! {r#"struct Row;
15199                   ˇ// something on the top
15200                   struct Row1;
15201                   struct Row2;
15202                   struct Roˇw3.1;
15203                   struct Row2.2;
15204                   struct Row2.3;ˇ
15205
15206                   struct Row4;
15207                   struct ˇRow5.1;
15208                   struct Row5.2;
15209                   struct «Rowˇ»5.3;
15210                   struct Row5;
15211                   struct Row6;
15212                   ˇ
15213                   struct Row9.1;
15214                   struct «Rowˇ»9.2;
15215                   struct «ˇRow»9.3;
15216                   struct Row8;
15217                   struct Row9;
15218                   «ˇ// something on bottom»
15219                   struct Row10;"#},
15220        vec![
15221            DiffHunkStatusKind::Added,
15222            DiffHunkStatusKind::Added,
15223            DiffHunkStatusKind::Added,
15224            DiffHunkStatusKind::Added,
15225            DiffHunkStatusKind::Added,
15226        ],
15227        indoc! {r#"struct Row;
15228                   ˇstruct Row1;
15229                   struct Row2;
15230                   ˇ
15231                   struct Row4;
15232                   ˇstruct Row5;
15233                   struct Row6;
15234                   ˇ
15235                   ˇstruct Row8;
15236                   struct Row9;
15237                   ˇstruct Row10;"#},
15238        base_text,
15239        &mut cx,
15240    );
15241}
15242
15243#[gpui::test]
15244async fn test_modification_reverts(cx: &mut TestAppContext) {
15245    init_test(cx, |_| {});
15246    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15247    let base_text = indoc! {r#"
15248        struct Row;
15249        struct Row1;
15250        struct Row2;
15251
15252        struct Row4;
15253        struct Row5;
15254        struct Row6;
15255
15256        struct Row8;
15257        struct Row9;
15258        struct Row10;"#};
15259
15260    // Modification hunks behave the same as the addition ones.
15261    assert_hunk_revert(
15262        indoc! {r#"struct Row;
15263                   struct Row1;
15264                   struct Row33;
15265                   ˇ
15266                   struct Row4;
15267                   struct Row5;
15268                   struct Row6;
15269                   ˇ
15270                   struct Row99;
15271                   struct Row9;
15272                   struct Row10;"#},
15273        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15274        indoc! {r#"struct Row;
15275                   struct Row1;
15276                   struct Row33;
15277                   ˇ
15278                   struct Row4;
15279                   struct Row5;
15280                   struct Row6;
15281                   ˇ
15282                   struct Row99;
15283                   struct Row9;
15284                   struct Row10;"#},
15285        base_text,
15286        &mut cx,
15287    );
15288    assert_hunk_revert(
15289        indoc! {r#"struct Row;
15290                   struct Row1;
15291                   struct Row33;
15292                   «ˇ
15293                   struct Row4;
15294                   struct» Row5;
15295                   «struct Row6;
15296                   ˇ»
15297                   struct Row99;
15298                   struct Row9;
15299                   struct Row10;"#},
15300        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15301        indoc! {r#"struct Row;
15302                   struct Row1;
15303                   struct Row33;
15304                   «ˇ
15305                   struct Row4;
15306                   struct» Row5;
15307                   «struct Row6;
15308                   ˇ»
15309                   struct Row99;
15310                   struct Row9;
15311                   struct Row10;"#},
15312        base_text,
15313        &mut cx,
15314    );
15315
15316    assert_hunk_revert(
15317        indoc! {r#"ˇstruct Row1.1;
15318                   struct Row1;
15319                   «ˇstr»uct Row22;
15320
15321                   struct ˇRow44;
15322                   struct Row5;
15323                   struct «Rˇ»ow66;ˇ
15324
15325                   «struˇ»ct Row88;
15326                   struct Row9;
15327                   struct Row1011;ˇ"#},
15328        vec![
15329            DiffHunkStatusKind::Modified,
15330            DiffHunkStatusKind::Modified,
15331            DiffHunkStatusKind::Modified,
15332            DiffHunkStatusKind::Modified,
15333            DiffHunkStatusKind::Modified,
15334            DiffHunkStatusKind::Modified,
15335        ],
15336        indoc! {r#"struct Row;
15337                   ˇstruct Row1;
15338                   struct Row2;
15339                   ˇ
15340                   struct Row4;
15341                   ˇstruct Row5;
15342                   struct Row6;
15343                   ˇ
15344                   struct Row8;
15345                   ˇstruct Row9;
15346                   struct Row10;ˇ"#},
15347        base_text,
15348        &mut cx,
15349    );
15350}
15351
15352#[gpui::test]
15353async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15354    init_test(cx, |_| {});
15355    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15356    let base_text = indoc! {r#"
15357        one
15358
15359        two
15360        three
15361        "#};
15362
15363    cx.set_head_text(base_text);
15364    cx.set_state("\nˇ\n");
15365    cx.executor().run_until_parked();
15366    cx.update_editor(|editor, _window, cx| {
15367        editor.expand_selected_diff_hunks(cx);
15368    });
15369    cx.executor().run_until_parked();
15370    cx.update_editor(|editor, window, cx| {
15371        editor.backspace(&Default::default(), window, cx);
15372    });
15373    cx.run_until_parked();
15374    cx.assert_state_with_diff(
15375        indoc! {r#"
15376
15377        - two
15378        - threeˇ
15379        +
15380        "#}
15381        .to_string(),
15382    );
15383}
15384
15385#[gpui::test]
15386async fn test_deletion_reverts(cx: &mut TestAppContext) {
15387    init_test(cx, |_| {});
15388    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15389    let base_text = indoc! {r#"struct Row;
15390struct Row1;
15391struct Row2;
15392
15393struct Row4;
15394struct Row5;
15395struct Row6;
15396
15397struct Row8;
15398struct Row9;
15399struct Row10;"#};
15400
15401    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15402    assert_hunk_revert(
15403        indoc! {r#"struct Row;
15404                   struct Row2;
15405
15406                   ˇstruct Row4;
15407                   struct Row5;
15408                   struct Row6;
15409                   ˇ
15410                   struct Row8;
15411                   struct Row10;"#},
15412        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15413        indoc! {r#"struct Row;
15414                   struct Row2;
15415
15416                   ˇstruct Row4;
15417                   struct Row5;
15418                   struct Row6;
15419                   ˇ
15420                   struct Row8;
15421                   struct Row10;"#},
15422        base_text,
15423        &mut cx,
15424    );
15425    assert_hunk_revert(
15426        indoc! {r#"struct Row;
15427                   struct Row2;
15428
15429                   «ˇstruct Row4;
15430                   struct» Row5;
15431                   «struct Row6;
15432                   ˇ»
15433                   struct Row8;
15434                   struct Row10;"#},
15435        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15436        indoc! {r#"struct Row;
15437                   struct Row2;
15438
15439                   «ˇstruct Row4;
15440                   struct» Row5;
15441                   «struct Row6;
15442                   ˇ»
15443                   struct Row8;
15444                   struct Row10;"#},
15445        base_text,
15446        &mut cx,
15447    );
15448
15449    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15450    assert_hunk_revert(
15451        indoc! {r#"struct Row;
15452                   ˇstruct Row2;
15453
15454                   struct Row4;
15455                   struct Row5;
15456                   struct Row6;
15457
15458                   struct Row8;ˇ
15459                   struct Row10;"#},
15460        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15461        indoc! {r#"struct Row;
15462                   struct Row1;
15463                   ˇstruct Row2;
15464
15465                   struct Row4;
15466                   struct Row5;
15467                   struct Row6;
15468
15469                   struct Row8;ˇ
15470                   struct Row9;
15471                   struct Row10;"#},
15472        base_text,
15473        &mut cx,
15474    );
15475    assert_hunk_revert(
15476        indoc! {r#"struct Row;
15477                   struct Row2«ˇ;
15478                   struct Row4;
15479                   struct» Row5;
15480                   «struct Row6;
15481
15482                   struct Row8;ˇ»
15483                   struct Row10;"#},
15484        vec![
15485            DiffHunkStatusKind::Deleted,
15486            DiffHunkStatusKind::Deleted,
15487            DiffHunkStatusKind::Deleted,
15488        ],
15489        indoc! {r#"struct Row;
15490                   struct Row1;
15491                   struct Row2«ˇ;
15492
15493                   struct Row4;
15494                   struct» Row5;
15495                   «struct Row6;
15496
15497                   struct Row8;ˇ»
15498                   struct Row9;
15499                   struct Row10;"#},
15500        base_text,
15501        &mut cx,
15502    );
15503}
15504
15505#[gpui::test]
15506async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15507    init_test(cx, |_| {});
15508
15509    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15510    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15511    let base_text_3 =
15512        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15513
15514    let text_1 = edit_first_char_of_every_line(base_text_1);
15515    let text_2 = edit_first_char_of_every_line(base_text_2);
15516    let text_3 = edit_first_char_of_every_line(base_text_3);
15517
15518    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15519    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15520    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15521
15522    let multibuffer = cx.new(|cx| {
15523        let mut multibuffer = MultiBuffer::new(ReadWrite);
15524        multibuffer.push_excerpts(
15525            buffer_1.clone(),
15526            [
15527                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15528                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15529                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15530            ],
15531            cx,
15532        );
15533        multibuffer.push_excerpts(
15534            buffer_2.clone(),
15535            [
15536                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15537                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15538                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15539            ],
15540            cx,
15541        );
15542        multibuffer.push_excerpts(
15543            buffer_3.clone(),
15544            [
15545                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15546                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15547                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15548            ],
15549            cx,
15550        );
15551        multibuffer
15552    });
15553
15554    let fs = FakeFs::new(cx.executor());
15555    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15556    let (editor, cx) = cx
15557        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15558    editor.update_in(cx, |editor, _window, cx| {
15559        for (buffer, diff_base) in [
15560            (buffer_1.clone(), base_text_1),
15561            (buffer_2.clone(), base_text_2),
15562            (buffer_3.clone(), base_text_3),
15563        ] {
15564            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15565            editor
15566                .buffer
15567                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15568        }
15569    });
15570    cx.executor().run_until_parked();
15571
15572    editor.update_in(cx, |editor, window, cx| {
15573        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}");
15574        editor.select_all(&SelectAll, window, cx);
15575        editor.git_restore(&Default::default(), window, cx);
15576    });
15577    cx.executor().run_until_parked();
15578
15579    // When all ranges are selected, all buffer hunks are reverted.
15580    editor.update(cx, |editor, cx| {
15581        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");
15582    });
15583    buffer_1.update(cx, |buffer, _| {
15584        assert_eq!(buffer.text(), base_text_1);
15585    });
15586    buffer_2.update(cx, |buffer, _| {
15587        assert_eq!(buffer.text(), base_text_2);
15588    });
15589    buffer_3.update(cx, |buffer, _| {
15590        assert_eq!(buffer.text(), base_text_3);
15591    });
15592
15593    editor.update_in(cx, |editor, window, cx| {
15594        editor.undo(&Default::default(), window, cx);
15595    });
15596
15597    editor.update_in(cx, |editor, window, cx| {
15598        editor.change_selections(None, window, cx, |s| {
15599            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15600        });
15601        editor.git_restore(&Default::default(), window, cx);
15602    });
15603
15604    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15605    // but not affect buffer_2 and its related excerpts.
15606    editor.update(cx, |editor, cx| {
15607        assert_eq!(
15608            editor.text(cx),
15609            "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}"
15610        );
15611    });
15612    buffer_1.update(cx, |buffer, _| {
15613        assert_eq!(buffer.text(), base_text_1);
15614    });
15615    buffer_2.update(cx, |buffer, _| {
15616        assert_eq!(
15617            buffer.text(),
15618            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15619        );
15620    });
15621    buffer_3.update(cx, |buffer, _| {
15622        assert_eq!(
15623            buffer.text(),
15624            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15625        );
15626    });
15627
15628    fn edit_first_char_of_every_line(text: &str) -> String {
15629        text.split('\n')
15630            .map(|line| format!("X{}", &line[1..]))
15631            .collect::<Vec<_>>()
15632            .join("\n")
15633    }
15634}
15635
15636#[gpui::test]
15637async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15638    init_test(cx, |_| {});
15639
15640    let cols = 4;
15641    let rows = 10;
15642    let sample_text_1 = sample_text(rows, cols, 'a');
15643    assert_eq!(
15644        sample_text_1,
15645        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15646    );
15647    let sample_text_2 = sample_text(rows, cols, 'l');
15648    assert_eq!(
15649        sample_text_2,
15650        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15651    );
15652    let sample_text_3 = sample_text(rows, cols, 'v');
15653    assert_eq!(
15654        sample_text_3,
15655        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15656    );
15657
15658    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15659    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15660    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15661
15662    let multi_buffer = cx.new(|cx| {
15663        let mut multibuffer = MultiBuffer::new(ReadWrite);
15664        multibuffer.push_excerpts(
15665            buffer_1.clone(),
15666            [
15667                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15668                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15669                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15670            ],
15671            cx,
15672        );
15673        multibuffer.push_excerpts(
15674            buffer_2.clone(),
15675            [
15676                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15677                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15678                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15679            ],
15680            cx,
15681        );
15682        multibuffer.push_excerpts(
15683            buffer_3.clone(),
15684            [
15685                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15686                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15687                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15688            ],
15689            cx,
15690        );
15691        multibuffer
15692    });
15693
15694    let fs = FakeFs::new(cx.executor());
15695    fs.insert_tree(
15696        "/a",
15697        json!({
15698            "main.rs": sample_text_1,
15699            "other.rs": sample_text_2,
15700            "lib.rs": sample_text_3,
15701        }),
15702    )
15703    .await;
15704    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15706    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15707    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15708        Editor::new(
15709            EditorMode::full(),
15710            multi_buffer,
15711            Some(project.clone()),
15712            window,
15713            cx,
15714        )
15715    });
15716    let multibuffer_item_id = workspace
15717        .update(cx, |workspace, window, cx| {
15718            assert!(
15719                workspace.active_item(cx).is_none(),
15720                "active item should be None before the first item is added"
15721            );
15722            workspace.add_item_to_active_pane(
15723                Box::new(multi_buffer_editor.clone()),
15724                None,
15725                true,
15726                window,
15727                cx,
15728            );
15729            let active_item = workspace
15730                .active_item(cx)
15731                .expect("should have an active item after adding the multi buffer");
15732            assert!(
15733                !active_item.is_singleton(cx),
15734                "A multi buffer was expected to active after adding"
15735            );
15736            active_item.item_id()
15737        })
15738        .unwrap();
15739    cx.executor().run_until_parked();
15740
15741    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15742        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15743            s.select_ranges(Some(1..2))
15744        });
15745        editor.open_excerpts(&OpenExcerpts, window, cx);
15746    });
15747    cx.executor().run_until_parked();
15748    let first_item_id = workspace
15749        .update(cx, |workspace, window, cx| {
15750            let active_item = workspace
15751                .active_item(cx)
15752                .expect("should have an active item after navigating into the 1st buffer");
15753            let first_item_id = active_item.item_id();
15754            assert_ne!(
15755                first_item_id, multibuffer_item_id,
15756                "Should navigate into the 1st buffer and activate it"
15757            );
15758            assert!(
15759                active_item.is_singleton(cx),
15760                "New active item should be a singleton buffer"
15761            );
15762            assert_eq!(
15763                active_item
15764                    .act_as::<Editor>(cx)
15765                    .expect("should have navigated into an editor for the 1st buffer")
15766                    .read(cx)
15767                    .text(cx),
15768                sample_text_1
15769            );
15770
15771            workspace
15772                .go_back(workspace.active_pane().downgrade(), window, cx)
15773                .detach_and_log_err(cx);
15774
15775            first_item_id
15776        })
15777        .unwrap();
15778    cx.executor().run_until_parked();
15779    workspace
15780        .update(cx, |workspace, _, cx| {
15781            let active_item = workspace
15782                .active_item(cx)
15783                .expect("should have an active item after navigating back");
15784            assert_eq!(
15785                active_item.item_id(),
15786                multibuffer_item_id,
15787                "Should navigate back to the multi buffer"
15788            );
15789            assert!(!active_item.is_singleton(cx));
15790        })
15791        .unwrap();
15792
15793    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15794        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15795            s.select_ranges(Some(39..40))
15796        });
15797        editor.open_excerpts(&OpenExcerpts, window, cx);
15798    });
15799    cx.executor().run_until_parked();
15800    let second_item_id = workspace
15801        .update(cx, |workspace, window, cx| {
15802            let active_item = workspace
15803                .active_item(cx)
15804                .expect("should have an active item after navigating into the 2nd buffer");
15805            let second_item_id = active_item.item_id();
15806            assert_ne!(
15807                second_item_id, multibuffer_item_id,
15808                "Should navigate away from the multibuffer"
15809            );
15810            assert_ne!(
15811                second_item_id, first_item_id,
15812                "Should navigate into the 2nd buffer and activate it"
15813            );
15814            assert!(
15815                active_item.is_singleton(cx),
15816                "New active item should be a singleton buffer"
15817            );
15818            assert_eq!(
15819                active_item
15820                    .act_as::<Editor>(cx)
15821                    .expect("should have navigated into an editor")
15822                    .read(cx)
15823                    .text(cx),
15824                sample_text_2
15825            );
15826
15827            workspace
15828                .go_back(workspace.active_pane().downgrade(), window, cx)
15829                .detach_and_log_err(cx);
15830
15831            second_item_id
15832        })
15833        .unwrap();
15834    cx.executor().run_until_parked();
15835    workspace
15836        .update(cx, |workspace, _, cx| {
15837            let active_item = workspace
15838                .active_item(cx)
15839                .expect("should have an active item after navigating back from the 2nd buffer");
15840            assert_eq!(
15841                active_item.item_id(),
15842                multibuffer_item_id,
15843                "Should navigate back from the 2nd buffer to the multi buffer"
15844            );
15845            assert!(!active_item.is_singleton(cx));
15846        })
15847        .unwrap();
15848
15849    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15850        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15851            s.select_ranges(Some(70..70))
15852        });
15853        editor.open_excerpts(&OpenExcerpts, window, cx);
15854    });
15855    cx.executor().run_until_parked();
15856    workspace
15857        .update(cx, |workspace, window, cx| {
15858            let active_item = workspace
15859                .active_item(cx)
15860                .expect("should have an active item after navigating into the 3rd buffer");
15861            let third_item_id = active_item.item_id();
15862            assert_ne!(
15863                third_item_id, multibuffer_item_id,
15864                "Should navigate into the 3rd buffer and activate it"
15865            );
15866            assert_ne!(third_item_id, first_item_id);
15867            assert_ne!(third_item_id, second_item_id);
15868            assert!(
15869                active_item.is_singleton(cx),
15870                "New active item should be a singleton buffer"
15871            );
15872            assert_eq!(
15873                active_item
15874                    .act_as::<Editor>(cx)
15875                    .expect("should have navigated into an editor")
15876                    .read(cx)
15877                    .text(cx),
15878                sample_text_3
15879            );
15880
15881            workspace
15882                .go_back(workspace.active_pane().downgrade(), window, cx)
15883                .detach_and_log_err(cx);
15884        })
15885        .unwrap();
15886    cx.executor().run_until_parked();
15887    workspace
15888        .update(cx, |workspace, _, cx| {
15889            let active_item = workspace
15890                .active_item(cx)
15891                .expect("should have an active item after navigating back from the 3rd buffer");
15892            assert_eq!(
15893                active_item.item_id(),
15894                multibuffer_item_id,
15895                "Should navigate back from the 3rd buffer to the multi buffer"
15896            );
15897            assert!(!active_item.is_singleton(cx));
15898        })
15899        .unwrap();
15900}
15901
15902#[gpui::test]
15903async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15904    init_test(cx, |_| {});
15905
15906    let mut cx = EditorTestContext::new(cx).await;
15907
15908    let diff_base = r#"
15909        use some::mod;
15910
15911        const A: u32 = 42;
15912
15913        fn main() {
15914            println!("hello");
15915
15916            println!("world");
15917        }
15918        "#
15919    .unindent();
15920
15921    cx.set_state(
15922        &r#"
15923        use some::modified;
15924
15925        ˇ
15926        fn main() {
15927            println!("hello there");
15928
15929            println!("around the");
15930            println!("world");
15931        }
15932        "#
15933        .unindent(),
15934    );
15935
15936    cx.set_head_text(&diff_base);
15937    executor.run_until_parked();
15938
15939    cx.update_editor(|editor, window, cx| {
15940        editor.go_to_next_hunk(&GoToHunk, window, cx);
15941        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15942    });
15943    executor.run_until_parked();
15944    cx.assert_state_with_diff(
15945        r#"
15946          use some::modified;
15947
15948
15949          fn main() {
15950        -     println!("hello");
15951        + ˇ    println!("hello there");
15952
15953              println!("around the");
15954              println!("world");
15955          }
15956        "#
15957        .unindent(),
15958    );
15959
15960    cx.update_editor(|editor, window, cx| {
15961        for _ in 0..2 {
15962            editor.go_to_next_hunk(&GoToHunk, window, cx);
15963            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15964        }
15965    });
15966    executor.run_until_parked();
15967    cx.assert_state_with_diff(
15968        r#"
15969        - use some::mod;
15970        + ˇuse some::modified;
15971
15972
15973          fn main() {
15974        -     println!("hello");
15975        +     println!("hello there");
15976
15977        +     println!("around the");
15978              println!("world");
15979          }
15980        "#
15981        .unindent(),
15982    );
15983
15984    cx.update_editor(|editor, window, cx| {
15985        editor.go_to_next_hunk(&GoToHunk, window, cx);
15986        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15987    });
15988    executor.run_until_parked();
15989    cx.assert_state_with_diff(
15990        r#"
15991        - use some::mod;
15992        + use some::modified;
15993
15994        - const A: u32 = 42;
15995          ˇ
15996          fn main() {
15997        -     println!("hello");
15998        +     println!("hello there");
15999
16000        +     println!("around the");
16001              println!("world");
16002          }
16003        "#
16004        .unindent(),
16005    );
16006
16007    cx.update_editor(|editor, window, cx| {
16008        editor.cancel(&Cancel, window, cx);
16009    });
16010
16011    cx.assert_state_with_diff(
16012        r#"
16013          use some::modified;
16014
16015          ˇ
16016          fn main() {
16017              println!("hello there");
16018
16019              println!("around the");
16020              println!("world");
16021          }
16022        "#
16023        .unindent(),
16024    );
16025}
16026
16027#[gpui::test]
16028async fn test_diff_base_change_with_expanded_diff_hunks(
16029    executor: BackgroundExecutor,
16030    cx: &mut TestAppContext,
16031) {
16032    init_test(cx, |_| {});
16033
16034    let mut cx = EditorTestContext::new(cx).await;
16035
16036    let diff_base = r#"
16037        use some::mod1;
16038        use some::mod2;
16039
16040        const A: u32 = 42;
16041        const B: u32 = 42;
16042        const C: u32 = 42;
16043
16044        fn main() {
16045            println!("hello");
16046
16047            println!("world");
16048        }
16049        "#
16050    .unindent();
16051
16052    cx.set_state(
16053        &r#"
16054        use some::mod2;
16055
16056        const A: u32 = 42;
16057        const C: u32 = 42;
16058
16059        fn main(ˇ) {
16060            //println!("hello");
16061
16062            println!("world");
16063            //
16064            //
16065        }
16066        "#
16067        .unindent(),
16068    );
16069
16070    cx.set_head_text(&diff_base);
16071    executor.run_until_parked();
16072
16073    cx.update_editor(|editor, window, cx| {
16074        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16075    });
16076    executor.run_until_parked();
16077    cx.assert_state_with_diff(
16078        r#"
16079        - use some::mod1;
16080          use some::mod2;
16081
16082          const A: u32 = 42;
16083        - const B: u32 = 42;
16084          const C: u32 = 42;
16085
16086          fn main(ˇ) {
16087        -     println!("hello");
16088        +     //println!("hello");
16089
16090              println!("world");
16091        +     //
16092        +     //
16093          }
16094        "#
16095        .unindent(),
16096    );
16097
16098    cx.set_head_text("new diff base!");
16099    executor.run_until_parked();
16100    cx.assert_state_with_diff(
16101        r#"
16102        - new diff base!
16103        + use some::mod2;
16104        +
16105        + const A: u32 = 42;
16106        + const C: u32 = 42;
16107        +
16108        + fn main(ˇ) {
16109        +     //println!("hello");
16110        +
16111        +     println!("world");
16112        +     //
16113        +     //
16114        + }
16115        "#
16116        .unindent(),
16117    );
16118}
16119
16120#[gpui::test]
16121async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16122    init_test(cx, |_| {});
16123
16124    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16125    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16126    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16127    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16128    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16129    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16130
16131    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16132    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16133    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16134
16135    let multi_buffer = cx.new(|cx| {
16136        let mut multibuffer = MultiBuffer::new(ReadWrite);
16137        multibuffer.push_excerpts(
16138            buffer_1.clone(),
16139            [
16140                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16141                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16142                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16143            ],
16144            cx,
16145        );
16146        multibuffer.push_excerpts(
16147            buffer_2.clone(),
16148            [
16149                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16150                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16151                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16152            ],
16153            cx,
16154        );
16155        multibuffer.push_excerpts(
16156            buffer_3.clone(),
16157            [
16158                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16159                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16160                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16161            ],
16162            cx,
16163        );
16164        multibuffer
16165    });
16166
16167    let editor =
16168        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16169    editor
16170        .update(cx, |editor, _window, cx| {
16171            for (buffer, diff_base) in [
16172                (buffer_1.clone(), file_1_old),
16173                (buffer_2.clone(), file_2_old),
16174                (buffer_3.clone(), file_3_old),
16175            ] {
16176                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16177                editor
16178                    .buffer
16179                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16180            }
16181        })
16182        .unwrap();
16183
16184    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16185    cx.run_until_parked();
16186
16187    cx.assert_editor_state(
16188        &"
16189            ˇaaa
16190            ccc
16191            ddd
16192
16193            ggg
16194            hhh
16195
16196
16197            lll
16198            mmm
16199            NNN
16200
16201            qqq
16202            rrr
16203
16204            uuu
16205            111
16206            222
16207            333
16208
16209            666
16210            777
16211
16212            000
16213            !!!"
16214        .unindent(),
16215    );
16216
16217    cx.update_editor(|editor, window, cx| {
16218        editor.select_all(&SelectAll, window, cx);
16219        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16220    });
16221    cx.executor().run_until_parked();
16222
16223    cx.assert_state_with_diff(
16224        "
16225            «aaa
16226          - bbb
16227            ccc
16228            ddd
16229
16230            ggg
16231            hhh
16232
16233
16234            lll
16235            mmm
16236          - nnn
16237          + NNN
16238
16239            qqq
16240            rrr
16241
16242            uuu
16243            111
16244            222
16245            333
16246
16247          + 666
16248            777
16249
16250            000
16251            !!!ˇ»"
16252            .unindent(),
16253    );
16254}
16255
16256#[gpui::test]
16257async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16258    init_test(cx, |_| {});
16259
16260    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16261    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16262
16263    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16264    let multi_buffer = cx.new(|cx| {
16265        let mut multibuffer = MultiBuffer::new(ReadWrite);
16266        multibuffer.push_excerpts(
16267            buffer.clone(),
16268            [
16269                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16270                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16271                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16272            ],
16273            cx,
16274        );
16275        multibuffer
16276    });
16277
16278    let editor =
16279        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16280    editor
16281        .update(cx, |editor, _window, cx| {
16282            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16283            editor
16284                .buffer
16285                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16286        })
16287        .unwrap();
16288
16289    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16290    cx.run_until_parked();
16291
16292    cx.update_editor(|editor, window, cx| {
16293        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16294    });
16295    cx.executor().run_until_parked();
16296
16297    // When the start of a hunk coincides with the start of its excerpt,
16298    // the hunk is expanded. When the start of a a hunk is earlier than
16299    // the start of its excerpt, the hunk is not expanded.
16300    cx.assert_state_with_diff(
16301        "
16302            ˇaaa
16303          - bbb
16304          + BBB
16305
16306          - ddd
16307          - eee
16308          + DDD
16309          + EEE
16310            fff
16311
16312            iii
16313        "
16314        .unindent(),
16315    );
16316}
16317
16318#[gpui::test]
16319async fn test_edits_around_expanded_insertion_hunks(
16320    executor: BackgroundExecutor,
16321    cx: &mut TestAppContext,
16322) {
16323    init_test(cx, |_| {});
16324
16325    let mut cx = EditorTestContext::new(cx).await;
16326
16327    let diff_base = r#"
16328        use some::mod1;
16329        use some::mod2;
16330
16331        const A: u32 = 42;
16332
16333        fn main() {
16334            println!("hello");
16335
16336            println!("world");
16337        }
16338        "#
16339    .unindent();
16340    executor.run_until_parked();
16341    cx.set_state(
16342        &r#"
16343        use some::mod1;
16344        use some::mod2;
16345
16346        const A: u32 = 42;
16347        const B: u32 = 42;
16348        const C: u32 = 42;
16349        ˇ
16350
16351        fn main() {
16352            println!("hello");
16353
16354            println!("world");
16355        }
16356        "#
16357        .unindent(),
16358    );
16359
16360    cx.set_head_text(&diff_base);
16361    executor.run_until_parked();
16362
16363    cx.update_editor(|editor, window, cx| {
16364        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16365    });
16366    executor.run_until_parked();
16367
16368    cx.assert_state_with_diff(
16369        r#"
16370        use some::mod1;
16371        use some::mod2;
16372
16373        const A: u32 = 42;
16374      + const B: u32 = 42;
16375      + const C: u32 = 42;
16376      + ˇ
16377
16378        fn main() {
16379            println!("hello");
16380
16381            println!("world");
16382        }
16383      "#
16384        .unindent(),
16385    );
16386
16387    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16388    executor.run_until_parked();
16389
16390    cx.assert_state_with_diff(
16391        r#"
16392        use some::mod1;
16393        use some::mod2;
16394
16395        const A: u32 = 42;
16396      + const B: u32 = 42;
16397      + const C: u32 = 42;
16398      + const D: u32 = 42;
16399      + ˇ
16400
16401        fn main() {
16402            println!("hello");
16403
16404            println!("world");
16405        }
16406      "#
16407        .unindent(),
16408    );
16409
16410    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16411    executor.run_until_parked();
16412
16413    cx.assert_state_with_diff(
16414        r#"
16415        use some::mod1;
16416        use some::mod2;
16417
16418        const A: u32 = 42;
16419      + const B: u32 = 42;
16420      + const C: u32 = 42;
16421      + const D: u32 = 42;
16422      + const E: u32 = 42;
16423      + ˇ
16424
16425        fn main() {
16426            println!("hello");
16427
16428            println!("world");
16429        }
16430      "#
16431        .unindent(),
16432    );
16433
16434    cx.update_editor(|editor, window, cx| {
16435        editor.delete_line(&DeleteLine, window, cx);
16436    });
16437    executor.run_until_parked();
16438
16439    cx.assert_state_with_diff(
16440        r#"
16441        use some::mod1;
16442        use some::mod2;
16443
16444        const A: u32 = 42;
16445      + const B: u32 = 42;
16446      + const C: u32 = 42;
16447      + const D: u32 = 42;
16448      + const E: u32 = 42;
16449        ˇ
16450        fn main() {
16451            println!("hello");
16452
16453            println!("world");
16454        }
16455      "#
16456        .unindent(),
16457    );
16458
16459    cx.update_editor(|editor, window, cx| {
16460        editor.move_up(&MoveUp, window, cx);
16461        editor.delete_line(&DeleteLine, window, cx);
16462        editor.move_up(&MoveUp, window, cx);
16463        editor.delete_line(&DeleteLine, window, cx);
16464        editor.move_up(&MoveUp, window, cx);
16465        editor.delete_line(&DeleteLine, window, cx);
16466    });
16467    executor.run_until_parked();
16468    cx.assert_state_with_diff(
16469        r#"
16470        use some::mod1;
16471        use some::mod2;
16472
16473        const A: u32 = 42;
16474      + const B: u32 = 42;
16475        ˇ
16476        fn main() {
16477            println!("hello");
16478
16479            println!("world");
16480        }
16481      "#
16482        .unindent(),
16483    );
16484
16485    cx.update_editor(|editor, window, cx| {
16486        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16487        editor.delete_line(&DeleteLine, window, cx);
16488    });
16489    executor.run_until_parked();
16490    cx.assert_state_with_diff(
16491        r#"
16492        ˇ
16493        fn main() {
16494            println!("hello");
16495
16496            println!("world");
16497        }
16498      "#
16499        .unindent(),
16500    );
16501}
16502
16503#[gpui::test]
16504async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16505    init_test(cx, |_| {});
16506
16507    let mut cx = EditorTestContext::new(cx).await;
16508    cx.set_head_text(indoc! { "
16509        one
16510        two
16511        three
16512        four
16513        five
16514        "
16515    });
16516    cx.set_state(indoc! { "
16517        one
16518        ˇthree
16519        five
16520    "});
16521    cx.run_until_parked();
16522    cx.update_editor(|editor, window, cx| {
16523        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16524    });
16525    cx.assert_state_with_diff(
16526        indoc! { "
16527        one
16528      - two
16529        ˇthree
16530      - four
16531        five
16532    "}
16533        .to_string(),
16534    );
16535    cx.update_editor(|editor, window, cx| {
16536        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16537    });
16538
16539    cx.assert_state_with_diff(
16540        indoc! { "
16541        one
16542        ˇthree
16543        five
16544    "}
16545        .to_string(),
16546    );
16547
16548    cx.set_state(indoc! { "
16549        one
16550        ˇTWO
16551        three
16552        four
16553        five
16554    "});
16555    cx.run_until_parked();
16556    cx.update_editor(|editor, window, cx| {
16557        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16558    });
16559
16560    cx.assert_state_with_diff(
16561        indoc! { "
16562            one
16563          - two
16564          + ˇTWO
16565            three
16566            four
16567            five
16568        "}
16569        .to_string(),
16570    );
16571    cx.update_editor(|editor, window, cx| {
16572        editor.move_up(&Default::default(), window, cx);
16573        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16574    });
16575    cx.assert_state_with_diff(
16576        indoc! { "
16577            one
16578            ˇTWO
16579            three
16580            four
16581            five
16582        "}
16583        .to_string(),
16584    );
16585}
16586
16587#[gpui::test]
16588async fn test_edits_around_expanded_deletion_hunks(
16589    executor: BackgroundExecutor,
16590    cx: &mut TestAppContext,
16591) {
16592    init_test(cx, |_| {});
16593
16594    let mut cx = EditorTestContext::new(cx).await;
16595
16596    let diff_base = r#"
16597        use some::mod1;
16598        use some::mod2;
16599
16600        const A: u32 = 42;
16601        const B: u32 = 42;
16602        const C: u32 = 42;
16603
16604
16605        fn main() {
16606            println!("hello");
16607
16608            println!("world");
16609        }
16610    "#
16611    .unindent();
16612    executor.run_until_parked();
16613    cx.set_state(
16614        &r#"
16615        use some::mod1;
16616        use some::mod2;
16617
16618        ˇconst B: u32 = 42;
16619        const C: u32 = 42;
16620
16621
16622        fn main() {
16623            println!("hello");
16624
16625            println!("world");
16626        }
16627        "#
16628        .unindent(),
16629    );
16630
16631    cx.set_head_text(&diff_base);
16632    executor.run_until_parked();
16633
16634    cx.update_editor(|editor, window, cx| {
16635        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16636    });
16637    executor.run_until_parked();
16638
16639    cx.assert_state_with_diff(
16640        r#"
16641        use some::mod1;
16642        use some::mod2;
16643
16644      - const A: u32 = 42;
16645        ˇconst B: u32 = 42;
16646        const C: u32 = 42;
16647
16648
16649        fn main() {
16650            println!("hello");
16651
16652            println!("world");
16653        }
16654      "#
16655        .unindent(),
16656    );
16657
16658    cx.update_editor(|editor, window, cx| {
16659        editor.delete_line(&DeleteLine, window, cx);
16660    });
16661    executor.run_until_parked();
16662    cx.assert_state_with_diff(
16663        r#"
16664        use some::mod1;
16665        use some::mod2;
16666
16667      - const A: u32 = 42;
16668      - const B: u32 = 42;
16669        ˇconst C: u32 = 42;
16670
16671
16672        fn main() {
16673            println!("hello");
16674
16675            println!("world");
16676        }
16677      "#
16678        .unindent(),
16679    );
16680
16681    cx.update_editor(|editor, window, cx| {
16682        editor.delete_line(&DeleteLine, window, cx);
16683    });
16684    executor.run_until_parked();
16685    cx.assert_state_with_diff(
16686        r#"
16687        use some::mod1;
16688        use some::mod2;
16689
16690      - const A: u32 = 42;
16691      - const B: u32 = 42;
16692      - const C: u32 = 42;
16693        ˇ
16694
16695        fn main() {
16696            println!("hello");
16697
16698            println!("world");
16699        }
16700      "#
16701        .unindent(),
16702    );
16703
16704    cx.update_editor(|editor, window, cx| {
16705        editor.handle_input("replacement", window, cx);
16706    });
16707    executor.run_until_parked();
16708    cx.assert_state_with_diff(
16709        r#"
16710        use some::mod1;
16711        use some::mod2;
16712
16713      - const A: u32 = 42;
16714      - const B: u32 = 42;
16715      - const C: u32 = 42;
16716      -
16717      + replacementˇ
16718
16719        fn main() {
16720            println!("hello");
16721
16722            println!("world");
16723        }
16724      "#
16725        .unindent(),
16726    );
16727}
16728
16729#[gpui::test]
16730async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16731    init_test(cx, |_| {});
16732
16733    let mut cx = EditorTestContext::new(cx).await;
16734
16735    let base_text = r#"
16736        one
16737        two
16738        three
16739        four
16740        five
16741    "#
16742    .unindent();
16743    executor.run_until_parked();
16744    cx.set_state(
16745        &r#"
16746        one
16747        two
16748        fˇour
16749        five
16750        "#
16751        .unindent(),
16752    );
16753
16754    cx.set_head_text(&base_text);
16755    executor.run_until_parked();
16756
16757    cx.update_editor(|editor, window, cx| {
16758        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16759    });
16760    executor.run_until_parked();
16761
16762    cx.assert_state_with_diff(
16763        r#"
16764          one
16765          two
16766        - three
16767          fˇour
16768          five
16769        "#
16770        .unindent(),
16771    );
16772
16773    cx.update_editor(|editor, window, cx| {
16774        editor.backspace(&Backspace, window, cx);
16775        editor.backspace(&Backspace, window, cx);
16776    });
16777    executor.run_until_parked();
16778    cx.assert_state_with_diff(
16779        r#"
16780          one
16781          two
16782        - threeˇ
16783        - four
16784        + our
16785          five
16786        "#
16787        .unindent(),
16788    );
16789}
16790
16791#[gpui::test]
16792async fn test_edit_after_expanded_modification_hunk(
16793    executor: BackgroundExecutor,
16794    cx: &mut TestAppContext,
16795) {
16796    init_test(cx, |_| {});
16797
16798    let mut cx = EditorTestContext::new(cx).await;
16799
16800    let diff_base = r#"
16801        use some::mod1;
16802        use some::mod2;
16803
16804        const A: u32 = 42;
16805        const B: u32 = 42;
16806        const C: u32 = 42;
16807        const D: u32 = 42;
16808
16809
16810        fn main() {
16811            println!("hello");
16812
16813            println!("world");
16814        }"#
16815    .unindent();
16816
16817    cx.set_state(
16818        &r#"
16819        use some::mod1;
16820        use some::mod2;
16821
16822        const A: u32 = 42;
16823        const B: u32 = 42;
16824        const C: u32 = 43ˇ
16825        const D: u32 = 42;
16826
16827
16828        fn main() {
16829            println!("hello");
16830
16831            println!("world");
16832        }"#
16833        .unindent(),
16834    );
16835
16836    cx.set_head_text(&diff_base);
16837    executor.run_until_parked();
16838    cx.update_editor(|editor, window, cx| {
16839        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16840    });
16841    executor.run_until_parked();
16842
16843    cx.assert_state_with_diff(
16844        r#"
16845        use some::mod1;
16846        use some::mod2;
16847
16848        const A: u32 = 42;
16849        const B: u32 = 42;
16850      - const C: u32 = 42;
16851      + const C: u32 = 43ˇ
16852        const D: u32 = 42;
16853
16854
16855        fn main() {
16856            println!("hello");
16857
16858            println!("world");
16859        }"#
16860        .unindent(),
16861    );
16862
16863    cx.update_editor(|editor, window, cx| {
16864        editor.handle_input("\nnew_line\n", window, cx);
16865    });
16866    executor.run_until_parked();
16867
16868    cx.assert_state_with_diff(
16869        r#"
16870        use some::mod1;
16871        use some::mod2;
16872
16873        const A: u32 = 42;
16874        const B: u32 = 42;
16875      - const C: u32 = 42;
16876      + const C: u32 = 43
16877      + new_line
16878      + ˇ
16879        const D: u32 = 42;
16880
16881
16882        fn main() {
16883            println!("hello");
16884
16885            println!("world");
16886        }"#
16887        .unindent(),
16888    );
16889}
16890
16891#[gpui::test]
16892async fn test_stage_and_unstage_added_file_hunk(
16893    executor: BackgroundExecutor,
16894    cx: &mut TestAppContext,
16895) {
16896    init_test(cx, |_| {});
16897
16898    let mut cx = EditorTestContext::new(cx).await;
16899    cx.update_editor(|editor, _, cx| {
16900        editor.set_expand_all_diff_hunks(cx);
16901    });
16902
16903    let working_copy = r#"
16904            ˇfn main() {
16905                println!("hello, world!");
16906            }
16907        "#
16908    .unindent();
16909
16910    cx.set_state(&working_copy);
16911    executor.run_until_parked();
16912
16913    cx.assert_state_with_diff(
16914        r#"
16915            + ˇfn main() {
16916            +     println!("hello, world!");
16917            + }
16918        "#
16919        .unindent(),
16920    );
16921    cx.assert_index_text(None);
16922
16923    cx.update_editor(|editor, window, cx| {
16924        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16925    });
16926    executor.run_until_parked();
16927    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16928    cx.assert_state_with_diff(
16929        r#"
16930            + ˇfn main() {
16931            +     println!("hello, world!");
16932            + }
16933        "#
16934        .unindent(),
16935    );
16936
16937    cx.update_editor(|editor, window, cx| {
16938        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16939    });
16940    executor.run_until_parked();
16941    cx.assert_index_text(None);
16942}
16943
16944async fn setup_indent_guides_editor(
16945    text: &str,
16946    cx: &mut TestAppContext,
16947) -> (BufferId, EditorTestContext) {
16948    init_test(cx, |_| {});
16949
16950    let mut cx = EditorTestContext::new(cx).await;
16951
16952    let buffer_id = cx.update_editor(|editor, window, cx| {
16953        editor.set_text(text, window, cx);
16954        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16955
16956        buffer_ids[0]
16957    });
16958
16959    (buffer_id, cx)
16960}
16961
16962fn assert_indent_guides(
16963    range: Range<u32>,
16964    expected: Vec<IndentGuide>,
16965    active_indices: Option<Vec<usize>>,
16966    cx: &mut EditorTestContext,
16967) {
16968    let indent_guides = cx.update_editor(|editor, window, cx| {
16969        let snapshot = editor.snapshot(window, cx).display_snapshot;
16970        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16971            editor,
16972            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16973            true,
16974            &snapshot,
16975            cx,
16976        );
16977
16978        indent_guides.sort_by(|a, b| {
16979            a.depth.cmp(&b.depth).then(
16980                a.start_row
16981                    .cmp(&b.start_row)
16982                    .then(a.end_row.cmp(&b.end_row)),
16983            )
16984        });
16985        indent_guides
16986    });
16987
16988    if let Some(expected) = active_indices {
16989        let active_indices = cx.update_editor(|editor, window, cx| {
16990            let snapshot = editor.snapshot(window, cx).display_snapshot;
16991            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16992        });
16993
16994        assert_eq!(
16995            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16996            expected,
16997            "Active indent guide indices do not match"
16998        );
16999    }
17000
17001    assert_eq!(indent_guides, expected, "Indent guides do not match");
17002}
17003
17004fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17005    IndentGuide {
17006        buffer_id,
17007        start_row: MultiBufferRow(start_row),
17008        end_row: MultiBufferRow(end_row),
17009        depth,
17010        tab_size: 4,
17011        settings: IndentGuideSettings {
17012            enabled: true,
17013            line_width: 1,
17014            active_line_width: 1,
17015            ..Default::default()
17016        },
17017    }
17018}
17019
17020#[gpui::test]
17021async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17022    let (buffer_id, mut cx) = setup_indent_guides_editor(
17023        &"
17024        fn main() {
17025            let a = 1;
17026        }"
17027        .unindent(),
17028        cx,
17029    )
17030    .await;
17031
17032    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17033}
17034
17035#[gpui::test]
17036async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17037    let (buffer_id, mut cx) = setup_indent_guides_editor(
17038        &"
17039        fn main() {
17040            let a = 1;
17041            let b = 2;
17042        }"
17043        .unindent(),
17044        cx,
17045    )
17046    .await;
17047
17048    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17049}
17050
17051#[gpui::test]
17052async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17053    let (buffer_id, mut cx) = setup_indent_guides_editor(
17054        &"
17055        fn main() {
17056            let a = 1;
17057            if a == 3 {
17058                let b = 2;
17059            } else {
17060                let c = 3;
17061            }
17062        }"
17063        .unindent(),
17064        cx,
17065    )
17066    .await;
17067
17068    assert_indent_guides(
17069        0..8,
17070        vec![
17071            indent_guide(buffer_id, 1, 6, 0),
17072            indent_guide(buffer_id, 3, 3, 1),
17073            indent_guide(buffer_id, 5, 5, 1),
17074        ],
17075        None,
17076        &mut cx,
17077    );
17078}
17079
17080#[gpui::test]
17081async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17082    let (buffer_id, mut cx) = setup_indent_guides_editor(
17083        &"
17084        fn main() {
17085            let a = 1;
17086                let b = 2;
17087            let c = 3;
17088        }"
17089        .unindent(),
17090        cx,
17091    )
17092    .await;
17093
17094    assert_indent_guides(
17095        0..5,
17096        vec![
17097            indent_guide(buffer_id, 1, 3, 0),
17098            indent_guide(buffer_id, 2, 2, 1),
17099        ],
17100        None,
17101        &mut cx,
17102    );
17103}
17104
17105#[gpui::test]
17106async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17107    let (buffer_id, mut cx) = setup_indent_guides_editor(
17108        &"
17109        fn main() {
17110            let a = 1;
17111
17112            let c = 3;
17113        }"
17114        .unindent(),
17115        cx,
17116    )
17117    .await;
17118
17119    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17120}
17121
17122#[gpui::test]
17123async fn test_indent_guide_complex(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        0..11,
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_starts_off_screen(cx: &mut TestAppContext) {
17156    let (buffer_id, mut cx) = setup_indent_guides_editor(
17157        &"
17158        fn main() {
17159            let a = 1;
17160
17161            let c = 3;
17162
17163            if a == 3 {
17164                let b = 2;
17165            } else {
17166                let c = 3;
17167            }
17168        }"
17169        .unindent(),
17170        cx,
17171    )
17172    .await;
17173
17174    assert_indent_guides(
17175        1..11,
17176        vec![
17177            indent_guide(buffer_id, 1, 9, 0),
17178            indent_guide(buffer_id, 6, 6, 1),
17179            indent_guide(buffer_id, 8, 8, 1),
17180        ],
17181        None,
17182        &mut cx,
17183    );
17184}
17185
17186#[gpui::test]
17187async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17188    let (buffer_id, mut cx) = setup_indent_guides_editor(
17189        &"
17190        fn main() {
17191            let a = 1;
17192
17193            let c = 3;
17194
17195            if a == 3 {
17196                let b = 2;
17197            } else {
17198                let c = 3;
17199            }
17200        }"
17201        .unindent(),
17202        cx,
17203    )
17204    .await;
17205
17206    assert_indent_guides(
17207        1..10,
17208        vec![
17209            indent_guide(buffer_id, 1, 9, 0),
17210            indent_guide(buffer_id, 6, 6, 1),
17211            indent_guide(buffer_id, 8, 8, 1),
17212        ],
17213        None,
17214        &mut cx,
17215    );
17216}
17217
17218#[gpui::test]
17219async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17220    let (buffer_id, mut cx) = setup_indent_guides_editor(
17221        &"
17222        fn main() {
17223            if a {
17224                b(
17225                    c,
17226                    d,
17227                )
17228            } else {
17229                e(
17230                    f
17231                )
17232            }
17233        }"
17234        .unindent(),
17235        cx,
17236    )
17237    .await;
17238
17239    assert_indent_guides(
17240        0..11,
17241        vec![
17242            indent_guide(buffer_id, 1, 10, 0),
17243            indent_guide(buffer_id, 2, 5, 1),
17244            indent_guide(buffer_id, 7, 9, 1),
17245            indent_guide(buffer_id, 3, 4, 2),
17246            indent_guide(buffer_id, 8, 8, 2),
17247        ],
17248        None,
17249        &mut cx,
17250    );
17251
17252    cx.update_editor(|editor, window, cx| {
17253        editor.fold_at(MultiBufferRow(2), window, cx);
17254        assert_eq!(
17255            editor.display_text(cx),
17256            "
17257            fn main() {
17258                if a {
17259                    b(⋯
17260                    )
17261                } else {
17262                    e(
17263                        f
17264                    )
17265                }
17266            }"
17267            .unindent()
17268        );
17269    });
17270
17271    assert_indent_guides(
17272        0..11,
17273        vec![
17274            indent_guide(buffer_id, 1, 10, 0),
17275            indent_guide(buffer_id, 2, 5, 1),
17276            indent_guide(buffer_id, 7, 9, 1),
17277            indent_guide(buffer_id, 8, 8, 2),
17278        ],
17279        None,
17280        &mut cx,
17281    );
17282}
17283
17284#[gpui::test]
17285async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17286    let (buffer_id, mut cx) = setup_indent_guides_editor(
17287        &"
17288        block1
17289            block2
17290                block3
17291                    block4
17292            block2
17293        block1
17294        block1"
17295            .unindent(),
17296        cx,
17297    )
17298    .await;
17299
17300    assert_indent_guides(
17301        1..10,
17302        vec![
17303            indent_guide(buffer_id, 1, 4, 0),
17304            indent_guide(buffer_id, 2, 3, 1),
17305            indent_guide(buffer_id, 3, 3, 2),
17306        ],
17307        None,
17308        &mut cx,
17309    );
17310}
17311
17312#[gpui::test]
17313async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17314    let (buffer_id, mut cx) = setup_indent_guides_editor(
17315        &"
17316        block1
17317            block2
17318                block3
17319
17320        block1
17321        block1"
17322            .unindent(),
17323        cx,
17324    )
17325    .await;
17326
17327    assert_indent_guides(
17328        0..6,
17329        vec![
17330            indent_guide(buffer_id, 1, 2, 0),
17331            indent_guide(buffer_id, 2, 2, 1),
17332        ],
17333        None,
17334        &mut cx,
17335    );
17336}
17337
17338#[gpui::test]
17339async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17340    let (buffer_id, mut cx) = setup_indent_guides_editor(
17341        &"
17342        function component() {
17343        \treturn (
17344        \t\t\t
17345        \t\t<div>
17346        \t\t\t<abc></abc>
17347        \t\t</div>
17348        \t)
17349        }"
17350        .unindent(),
17351        cx,
17352    )
17353    .await;
17354
17355    assert_indent_guides(
17356        0..8,
17357        vec![
17358            indent_guide(buffer_id, 1, 6, 0),
17359            indent_guide(buffer_id, 2, 5, 1),
17360            indent_guide(buffer_id, 4, 4, 2),
17361        ],
17362        None,
17363        &mut cx,
17364    );
17365}
17366
17367#[gpui::test]
17368async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17369    let (buffer_id, mut cx) = setup_indent_guides_editor(
17370        &"
17371        function component() {
17372        \treturn (
17373        \t
17374        \t\t<div>
17375        \t\t\t<abc></abc>
17376        \t\t</div>
17377        \t)
17378        }"
17379        .unindent(),
17380        cx,
17381    )
17382    .await;
17383
17384    assert_indent_guides(
17385        0..8,
17386        vec![
17387            indent_guide(buffer_id, 1, 6, 0),
17388            indent_guide(buffer_id, 2, 5, 1),
17389            indent_guide(buffer_id, 4, 4, 2),
17390        ],
17391        None,
17392        &mut cx,
17393    );
17394}
17395
17396#[gpui::test]
17397async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17398    let (buffer_id, mut cx) = setup_indent_guides_editor(
17399        &"
17400        block1
17401
17402
17403
17404            block2
17405        "
17406        .unindent(),
17407        cx,
17408    )
17409    .await;
17410
17411    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17412}
17413
17414#[gpui::test]
17415async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17416    let (buffer_id, mut cx) = setup_indent_guides_editor(
17417        &"
17418        def a:
17419        \tb = 3
17420        \tif True:
17421        \t\tc = 4
17422        \t\td = 5
17423        \tprint(b)
17424        "
17425        .unindent(),
17426        cx,
17427    )
17428    .await;
17429
17430    assert_indent_guides(
17431        0..6,
17432        vec![
17433            indent_guide(buffer_id, 1, 5, 0),
17434            indent_guide(buffer_id, 3, 4, 1),
17435        ],
17436        None,
17437        &mut cx,
17438    );
17439}
17440
17441#[gpui::test]
17442async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17443    let (buffer_id, mut cx) = setup_indent_guides_editor(
17444        &"
17445    fn main() {
17446        let a = 1;
17447    }"
17448        .unindent(),
17449        cx,
17450    )
17451    .await;
17452
17453    cx.update_editor(|editor, window, cx| {
17454        editor.change_selections(None, window, cx, |s| {
17455            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17456        });
17457    });
17458
17459    assert_indent_guides(
17460        0..3,
17461        vec![indent_guide(buffer_id, 1, 1, 0)],
17462        Some(vec![0]),
17463        &mut cx,
17464    );
17465}
17466
17467#[gpui::test]
17468async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17469    let (buffer_id, mut cx) = setup_indent_guides_editor(
17470        &"
17471    fn main() {
17472        if 1 == 2 {
17473            let a = 1;
17474        }
17475    }"
17476        .unindent(),
17477        cx,
17478    )
17479    .await;
17480
17481    cx.update_editor(|editor, window, cx| {
17482        editor.change_selections(None, window, cx, |s| {
17483            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17484        });
17485    });
17486
17487    assert_indent_guides(
17488        0..4,
17489        vec![
17490            indent_guide(buffer_id, 1, 3, 0),
17491            indent_guide(buffer_id, 2, 2, 1),
17492        ],
17493        Some(vec![1]),
17494        &mut cx,
17495    );
17496
17497    cx.update_editor(|editor, window, cx| {
17498        editor.change_selections(None, window, cx, |s| {
17499            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17500        });
17501    });
17502
17503    assert_indent_guides(
17504        0..4,
17505        vec![
17506            indent_guide(buffer_id, 1, 3, 0),
17507            indent_guide(buffer_id, 2, 2, 1),
17508        ],
17509        Some(vec![1]),
17510        &mut cx,
17511    );
17512
17513    cx.update_editor(|editor, window, cx| {
17514        editor.change_selections(None, window, cx, |s| {
17515            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17516        });
17517    });
17518
17519    assert_indent_guides(
17520        0..4,
17521        vec![
17522            indent_guide(buffer_id, 1, 3, 0),
17523            indent_guide(buffer_id, 2, 2, 1),
17524        ],
17525        Some(vec![0]),
17526        &mut cx,
17527    );
17528}
17529
17530#[gpui::test]
17531async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17532    let (buffer_id, mut cx) = setup_indent_guides_editor(
17533        &"
17534    fn main() {
17535        let a = 1;
17536
17537        let b = 2;
17538    }"
17539        .unindent(),
17540        cx,
17541    )
17542    .await;
17543
17544    cx.update_editor(|editor, window, cx| {
17545        editor.change_selections(None, window, cx, |s| {
17546            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17547        });
17548    });
17549
17550    assert_indent_guides(
17551        0..5,
17552        vec![indent_guide(buffer_id, 1, 3, 0)],
17553        Some(vec![0]),
17554        &mut cx,
17555    );
17556}
17557
17558#[gpui::test]
17559async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17560    let (buffer_id, mut cx) = setup_indent_guides_editor(
17561        &"
17562    def m:
17563        a = 1
17564        pass"
17565            .unindent(),
17566        cx,
17567    )
17568    .await;
17569
17570    cx.update_editor(|editor, window, cx| {
17571        editor.change_selections(None, window, cx, |s| {
17572            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17573        });
17574    });
17575
17576    assert_indent_guides(
17577        0..3,
17578        vec![indent_guide(buffer_id, 1, 2, 0)],
17579        Some(vec![0]),
17580        &mut cx,
17581    );
17582}
17583
17584#[gpui::test]
17585async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17586    init_test(cx, |_| {});
17587    let mut cx = EditorTestContext::new(cx).await;
17588    let text = indoc! {
17589        "
17590        impl A {
17591            fn b() {
17592                0;
17593                3;
17594                5;
17595                6;
17596                7;
17597            }
17598        }
17599        "
17600    };
17601    let base_text = indoc! {
17602        "
17603        impl A {
17604            fn b() {
17605                0;
17606                1;
17607                2;
17608                3;
17609                4;
17610            }
17611            fn c() {
17612                5;
17613                6;
17614                7;
17615            }
17616        }
17617        "
17618    };
17619
17620    cx.update_editor(|editor, window, cx| {
17621        editor.set_text(text, window, cx);
17622
17623        editor.buffer().update(cx, |multibuffer, cx| {
17624            let buffer = multibuffer.as_singleton().unwrap();
17625            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17626
17627            multibuffer.set_all_diff_hunks_expanded(cx);
17628            multibuffer.add_diff(diff, cx);
17629
17630            buffer.read(cx).remote_id()
17631        })
17632    });
17633    cx.run_until_parked();
17634
17635    cx.assert_state_with_diff(
17636        indoc! { "
17637          impl A {
17638              fn b() {
17639                  0;
17640        -         1;
17641        -         2;
17642                  3;
17643        -         4;
17644        -     }
17645        -     fn c() {
17646                  5;
17647                  6;
17648                  7;
17649              }
17650          }
17651          ˇ"
17652        }
17653        .to_string(),
17654    );
17655
17656    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17657        editor
17658            .snapshot(window, cx)
17659            .buffer_snapshot
17660            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17661            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17662            .collect::<Vec<_>>()
17663    });
17664    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17665    assert_eq!(
17666        actual_guides,
17667        vec![
17668            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17669            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17670            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17671        ]
17672    );
17673}
17674
17675#[gpui::test]
17676async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17677    init_test(cx, |_| {});
17678    let mut cx = EditorTestContext::new(cx).await;
17679
17680    let diff_base = r#"
17681        a
17682        b
17683        c
17684        "#
17685    .unindent();
17686
17687    cx.set_state(
17688        &r#"
17689        ˇA
17690        b
17691        C
17692        "#
17693        .unindent(),
17694    );
17695    cx.set_head_text(&diff_base);
17696    cx.update_editor(|editor, window, cx| {
17697        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17698    });
17699    executor.run_until_parked();
17700
17701    let both_hunks_expanded = r#"
17702        - a
17703        + ˇA
17704          b
17705        - c
17706        + C
17707        "#
17708    .unindent();
17709
17710    cx.assert_state_with_diff(both_hunks_expanded.clone());
17711
17712    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17713        let snapshot = editor.snapshot(window, cx);
17714        let hunks = editor
17715            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17716            .collect::<Vec<_>>();
17717        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17718        let buffer_id = hunks[0].buffer_id;
17719        hunks
17720            .into_iter()
17721            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17722            .collect::<Vec<_>>()
17723    });
17724    assert_eq!(hunk_ranges.len(), 2);
17725
17726    cx.update_editor(|editor, _, cx| {
17727        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17728    });
17729    executor.run_until_parked();
17730
17731    let second_hunk_expanded = r#"
17732          ˇA
17733          b
17734        - c
17735        + C
17736        "#
17737    .unindent();
17738
17739    cx.assert_state_with_diff(second_hunk_expanded);
17740
17741    cx.update_editor(|editor, _, cx| {
17742        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17743    });
17744    executor.run_until_parked();
17745
17746    cx.assert_state_with_diff(both_hunks_expanded.clone());
17747
17748    cx.update_editor(|editor, _, cx| {
17749        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17750    });
17751    executor.run_until_parked();
17752
17753    let first_hunk_expanded = r#"
17754        - a
17755        + ˇA
17756          b
17757          C
17758        "#
17759    .unindent();
17760
17761    cx.assert_state_with_diff(first_hunk_expanded);
17762
17763    cx.update_editor(|editor, _, cx| {
17764        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17765    });
17766    executor.run_until_parked();
17767
17768    cx.assert_state_with_diff(both_hunks_expanded);
17769
17770    cx.set_state(
17771        &r#"
17772        ˇA
17773        b
17774        "#
17775        .unindent(),
17776    );
17777    cx.run_until_parked();
17778
17779    // TODO this cursor position seems bad
17780    cx.assert_state_with_diff(
17781        r#"
17782        - ˇa
17783        + A
17784          b
17785        "#
17786        .unindent(),
17787    );
17788
17789    cx.update_editor(|editor, window, cx| {
17790        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17791    });
17792
17793    cx.assert_state_with_diff(
17794        r#"
17795            - ˇa
17796            + A
17797              b
17798            - c
17799            "#
17800        .unindent(),
17801    );
17802
17803    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17804        let snapshot = editor.snapshot(window, cx);
17805        let hunks = editor
17806            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17807            .collect::<Vec<_>>();
17808        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17809        let buffer_id = hunks[0].buffer_id;
17810        hunks
17811            .into_iter()
17812            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17813            .collect::<Vec<_>>()
17814    });
17815    assert_eq!(hunk_ranges.len(), 2);
17816
17817    cx.update_editor(|editor, _, cx| {
17818        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17819    });
17820    executor.run_until_parked();
17821
17822    cx.assert_state_with_diff(
17823        r#"
17824        - ˇa
17825        + A
17826          b
17827        "#
17828        .unindent(),
17829    );
17830}
17831
17832#[gpui::test]
17833async fn test_toggle_deletion_hunk_at_start_of_file(
17834    executor: BackgroundExecutor,
17835    cx: &mut TestAppContext,
17836) {
17837    init_test(cx, |_| {});
17838    let mut cx = EditorTestContext::new(cx).await;
17839
17840    let diff_base = r#"
17841        a
17842        b
17843        c
17844        "#
17845    .unindent();
17846
17847    cx.set_state(
17848        &r#"
17849        ˇb
17850        c
17851        "#
17852        .unindent(),
17853    );
17854    cx.set_head_text(&diff_base);
17855    cx.update_editor(|editor, window, cx| {
17856        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17857    });
17858    executor.run_until_parked();
17859
17860    let hunk_expanded = r#"
17861        - a
17862          ˇb
17863          c
17864        "#
17865    .unindent();
17866
17867    cx.assert_state_with_diff(hunk_expanded.clone());
17868
17869    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17870        let snapshot = editor.snapshot(window, cx);
17871        let hunks = editor
17872            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17873            .collect::<Vec<_>>();
17874        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17875        let buffer_id = hunks[0].buffer_id;
17876        hunks
17877            .into_iter()
17878            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17879            .collect::<Vec<_>>()
17880    });
17881    assert_eq!(hunk_ranges.len(), 1);
17882
17883    cx.update_editor(|editor, _, cx| {
17884        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17885    });
17886    executor.run_until_parked();
17887
17888    let hunk_collapsed = r#"
17889          ˇb
17890          c
17891        "#
17892    .unindent();
17893
17894    cx.assert_state_with_diff(hunk_collapsed);
17895
17896    cx.update_editor(|editor, _, cx| {
17897        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17898    });
17899    executor.run_until_parked();
17900
17901    cx.assert_state_with_diff(hunk_expanded.clone());
17902}
17903
17904#[gpui::test]
17905async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17906    init_test(cx, |_| {});
17907
17908    let fs = FakeFs::new(cx.executor());
17909    fs.insert_tree(
17910        path!("/test"),
17911        json!({
17912            ".git": {},
17913            "file-1": "ONE\n",
17914            "file-2": "TWO\n",
17915            "file-3": "THREE\n",
17916        }),
17917    )
17918    .await;
17919
17920    fs.set_head_for_repo(
17921        path!("/test/.git").as_ref(),
17922        &[
17923            ("file-1".into(), "one\n".into()),
17924            ("file-2".into(), "two\n".into()),
17925            ("file-3".into(), "three\n".into()),
17926        ],
17927        "deadbeef",
17928    );
17929
17930    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17931    let mut buffers = vec![];
17932    for i in 1..=3 {
17933        let buffer = project
17934            .update(cx, |project, cx| {
17935                let path = format!(path!("/test/file-{}"), i);
17936                project.open_local_buffer(path, cx)
17937            })
17938            .await
17939            .unwrap();
17940        buffers.push(buffer);
17941    }
17942
17943    let multibuffer = cx.new(|cx| {
17944        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17945        multibuffer.set_all_diff_hunks_expanded(cx);
17946        for buffer in &buffers {
17947            let snapshot = buffer.read(cx).snapshot();
17948            multibuffer.set_excerpts_for_path(
17949                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17950                buffer.clone(),
17951                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17952                DEFAULT_MULTIBUFFER_CONTEXT,
17953                cx,
17954            );
17955        }
17956        multibuffer
17957    });
17958
17959    let editor = cx.add_window(|window, cx| {
17960        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17961    });
17962    cx.run_until_parked();
17963
17964    let snapshot = editor
17965        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17966        .unwrap();
17967    let hunks = snapshot
17968        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17969        .map(|hunk| match hunk {
17970            DisplayDiffHunk::Unfolded {
17971                display_row_range, ..
17972            } => display_row_range,
17973            DisplayDiffHunk::Folded { .. } => unreachable!(),
17974        })
17975        .collect::<Vec<_>>();
17976    assert_eq!(
17977        hunks,
17978        [
17979            DisplayRow(2)..DisplayRow(4),
17980            DisplayRow(7)..DisplayRow(9),
17981            DisplayRow(12)..DisplayRow(14),
17982        ]
17983    );
17984}
17985
17986#[gpui::test]
17987async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17988    init_test(cx, |_| {});
17989
17990    let mut cx = EditorTestContext::new(cx).await;
17991    cx.set_head_text(indoc! { "
17992        one
17993        two
17994        three
17995        four
17996        five
17997        "
17998    });
17999    cx.set_index_text(indoc! { "
18000        one
18001        two
18002        three
18003        four
18004        five
18005        "
18006    });
18007    cx.set_state(indoc! {"
18008        one
18009        TWO
18010        ˇTHREE
18011        FOUR
18012        five
18013    "});
18014    cx.run_until_parked();
18015    cx.update_editor(|editor, window, cx| {
18016        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18017    });
18018    cx.run_until_parked();
18019    cx.assert_index_text(Some(indoc! {"
18020        one
18021        TWO
18022        THREE
18023        FOUR
18024        five
18025    "}));
18026    cx.set_state(indoc! { "
18027        one
18028        TWO
18029        ˇTHREE-HUNDRED
18030        FOUR
18031        five
18032    "});
18033    cx.run_until_parked();
18034    cx.update_editor(|editor, window, cx| {
18035        let snapshot = editor.snapshot(window, cx);
18036        let hunks = editor
18037            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18038            .collect::<Vec<_>>();
18039        assert_eq!(hunks.len(), 1);
18040        assert_eq!(
18041            hunks[0].status(),
18042            DiffHunkStatus {
18043                kind: DiffHunkStatusKind::Modified,
18044                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18045            }
18046        );
18047
18048        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18049    });
18050    cx.run_until_parked();
18051    cx.assert_index_text(Some(indoc! {"
18052        one
18053        TWO
18054        THREE-HUNDRED
18055        FOUR
18056        five
18057    "}));
18058}
18059
18060#[gpui::test]
18061fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18062    init_test(cx, |_| {});
18063
18064    let editor = cx.add_window(|window, cx| {
18065        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18066        build_editor(buffer, window, cx)
18067    });
18068
18069    let render_args = Arc::new(Mutex::new(None));
18070    let snapshot = editor
18071        .update(cx, |editor, window, cx| {
18072            let snapshot = editor.buffer().read(cx).snapshot(cx);
18073            let range =
18074                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18075
18076            struct RenderArgs {
18077                row: MultiBufferRow,
18078                folded: bool,
18079                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18080            }
18081
18082            let crease = Crease::inline(
18083                range,
18084                FoldPlaceholder::test(),
18085                {
18086                    let toggle_callback = render_args.clone();
18087                    move |row, folded, callback, _window, _cx| {
18088                        *toggle_callback.lock() = Some(RenderArgs {
18089                            row,
18090                            folded,
18091                            callback,
18092                        });
18093                        div()
18094                    }
18095                },
18096                |_row, _folded, _window, _cx| div(),
18097            );
18098
18099            editor.insert_creases(Some(crease), cx);
18100            let snapshot = editor.snapshot(window, cx);
18101            let _div = snapshot.render_crease_toggle(
18102                MultiBufferRow(1),
18103                false,
18104                cx.entity().clone(),
18105                window,
18106                cx,
18107            );
18108            snapshot
18109        })
18110        .unwrap();
18111
18112    let render_args = render_args.lock().take().unwrap();
18113    assert_eq!(render_args.row, MultiBufferRow(1));
18114    assert!(!render_args.folded);
18115    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18116
18117    cx.update_window(*editor, |_, window, cx| {
18118        (render_args.callback)(true, window, cx)
18119    })
18120    .unwrap();
18121    let snapshot = editor
18122        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18123        .unwrap();
18124    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18125
18126    cx.update_window(*editor, |_, window, cx| {
18127        (render_args.callback)(false, window, cx)
18128    })
18129    .unwrap();
18130    let snapshot = editor
18131        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18132        .unwrap();
18133    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18134}
18135
18136#[gpui::test]
18137async fn test_input_text(cx: &mut TestAppContext) {
18138    init_test(cx, |_| {});
18139    let mut cx = EditorTestContext::new(cx).await;
18140
18141    cx.set_state(
18142        &r#"ˇone
18143        two
18144
18145        three
18146        fourˇ
18147        five
18148
18149        siˇx"#
18150            .unindent(),
18151    );
18152
18153    cx.dispatch_action(HandleInput(String::new()));
18154    cx.assert_editor_state(
18155        &r#"ˇone
18156        two
18157
18158        three
18159        fourˇ
18160        five
18161
18162        siˇx"#
18163            .unindent(),
18164    );
18165
18166    cx.dispatch_action(HandleInput("AAAA".to_string()));
18167    cx.assert_editor_state(
18168        &r#"AAAAˇone
18169        two
18170
18171        three
18172        fourAAAAˇ
18173        five
18174
18175        siAAAAˇx"#
18176            .unindent(),
18177    );
18178}
18179
18180#[gpui::test]
18181async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18182    init_test(cx, |_| {});
18183
18184    let mut cx = EditorTestContext::new(cx).await;
18185    cx.set_state(
18186        r#"let foo = 1;
18187let foo = 2;
18188let foo = 3;
18189let fooˇ = 4;
18190let foo = 5;
18191let foo = 6;
18192let foo = 7;
18193let foo = 8;
18194let foo = 9;
18195let foo = 10;
18196let foo = 11;
18197let foo = 12;
18198let foo = 13;
18199let foo = 14;
18200let foo = 15;"#,
18201    );
18202
18203    cx.update_editor(|e, window, cx| {
18204        assert_eq!(
18205            e.next_scroll_position,
18206            NextScrollCursorCenterTopBottom::Center,
18207            "Default next scroll direction is center",
18208        );
18209
18210        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18211        assert_eq!(
18212            e.next_scroll_position,
18213            NextScrollCursorCenterTopBottom::Top,
18214            "After center, next scroll direction should be top",
18215        );
18216
18217        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18218        assert_eq!(
18219            e.next_scroll_position,
18220            NextScrollCursorCenterTopBottom::Bottom,
18221            "After top, next scroll direction should be bottom",
18222        );
18223
18224        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18225        assert_eq!(
18226            e.next_scroll_position,
18227            NextScrollCursorCenterTopBottom::Center,
18228            "After bottom, scrolling should start over",
18229        );
18230
18231        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18232        assert_eq!(
18233            e.next_scroll_position,
18234            NextScrollCursorCenterTopBottom::Top,
18235            "Scrolling continues if retriggered fast enough"
18236        );
18237    });
18238
18239    cx.executor()
18240        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18241    cx.executor().run_until_parked();
18242    cx.update_editor(|e, _, _| {
18243        assert_eq!(
18244            e.next_scroll_position,
18245            NextScrollCursorCenterTopBottom::Center,
18246            "If scrolling is not triggered fast enough, it should reset"
18247        );
18248    });
18249}
18250
18251#[gpui::test]
18252async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18253    init_test(cx, |_| {});
18254    let mut cx = EditorLspTestContext::new_rust(
18255        lsp::ServerCapabilities {
18256            definition_provider: Some(lsp::OneOf::Left(true)),
18257            references_provider: Some(lsp::OneOf::Left(true)),
18258            ..lsp::ServerCapabilities::default()
18259        },
18260        cx,
18261    )
18262    .await;
18263
18264    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18265        let go_to_definition = cx
18266            .lsp
18267            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18268                move |params, _| async move {
18269                    if empty_go_to_definition {
18270                        Ok(None)
18271                    } else {
18272                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18273                            uri: params.text_document_position_params.text_document.uri,
18274                            range: lsp::Range::new(
18275                                lsp::Position::new(4, 3),
18276                                lsp::Position::new(4, 6),
18277                            ),
18278                        })))
18279                    }
18280                },
18281            );
18282        let references = cx
18283            .lsp
18284            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18285                Ok(Some(vec![lsp::Location {
18286                    uri: params.text_document_position.text_document.uri,
18287                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18288                }]))
18289            });
18290        (go_to_definition, references)
18291    };
18292
18293    cx.set_state(
18294        &r#"fn one() {
18295            let mut a = ˇtwo();
18296        }
18297
18298        fn two() {}"#
18299            .unindent(),
18300    );
18301    set_up_lsp_handlers(false, &mut cx);
18302    let navigated = cx
18303        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18304        .await
18305        .expect("Failed to navigate to definition");
18306    assert_eq!(
18307        navigated,
18308        Navigated::Yes,
18309        "Should have navigated to definition from the GetDefinition response"
18310    );
18311    cx.assert_editor_state(
18312        &r#"fn one() {
18313            let mut a = two();
18314        }
18315
18316        fn «twoˇ»() {}"#
18317            .unindent(),
18318    );
18319
18320    let editors = cx.update_workspace(|workspace, _, cx| {
18321        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18322    });
18323    cx.update_editor(|_, _, test_editor_cx| {
18324        assert_eq!(
18325            editors.len(),
18326            1,
18327            "Initially, only one, test, editor should be open in the workspace"
18328        );
18329        assert_eq!(
18330            test_editor_cx.entity(),
18331            editors.last().expect("Asserted len is 1").clone()
18332        );
18333    });
18334
18335    set_up_lsp_handlers(true, &mut cx);
18336    let navigated = cx
18337        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18338        .await
18339        .expect("Failed to navigate to lookup references");
18340    assert_eq!(
18341        navigated,
18342        Navigated::Yes,
18343        "Should have navigated to references as a fallback after empty GoToDefinition response"
18344    );
18345    // We should not change the selections in the existing file,
18346    // if opening another milti buffer with the references
18347    cx.assert_editor_state(
18348        &r#"fn one() {
18349            let mut a = two();
18350        }
18351
18352        fn «twoˇ»() {}"#
18353            .unindent(),
18354    );
18355    let editors = cx.update_workspace(|workspace, _, cx| {
18356        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18357    });
18358    cx.update_editor(|_, _, test_editor_cx| {
18359        assert_eq!(
18360            editors.len(),
18361            2,
18362            "After falling back to references search, we open a new editor with the results"
18363        );
18364        let references_fallback_text = editors
18365            .into_iter()
18366            .find(|new_editor| *new_editor != test_editor_cx.entity())
18367            .expect("Should have one non-test editor now")
18368            .read(test_editor_cx)
18369            .text(test_editor_cx);
18370        assert_eq!(
18371            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18372            "Should use the range from the references response and not the GoToDefinition one"
18373        );
18374    });
18375}
18376
18377#[gpui::test]
18378async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18379    init_test(cx, |_| {});
18380    cx.update(|cx| {
18381        let mut editor_settings = EditorSettings::get_global(cx).clone();
18382        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18383        EditorSettings::override_global(editor_settings, cx);
18384    });
18385    let mut cx = EditorLspTestContext::new_rust(
18386        lsp::ServerCapabilities {
18387            definition_provider: Some(lsp::OneOf::Left(true)),
18388            references_provider: Some(lsp::OneOf::Left(true)),
18389            ..lsp::ServerCapabilities::default()
18390        },
18391        cx,
18392    )
18393    .await;
18394    let original_state = r#"fn one() {
18395        let mut a = ˇtwo();
18396    }
18397
18398    fn two() {}"#
18399        .unindent();
18400    cx.set_state(&original_state);
18401
18402    let mut go_to_definition = cx
18403        .lsp
18404        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18405            move |_, _| async move { Ok(None) },
18406        );
18407    let _references = cx
18408        .lsp
18409        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18410            panic!("Should not call for references with no go to definition fallback")
18411        });
18412
18413    let navigated = cx
18414        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18415        .await
18416        .expect("Failed to navigate to lookup references");
18417    go_to_definition
18418        .next()
18419        .await
18420        .expect("Should have called the go_to_definition handler");
18421
18422    assert_eq!(
18423        navigated,
18424        Navigated::No,
18425        "Should have navigated to references as a fallback after empty GoToDefinition response"
18426    );
18427    cx.assert_editor_state(&original_state);
18428    let editors = cx.update_workspace(|workspace, _, cx| {
18429        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18430    });
18431    cx.update_editor(|_, _, _| {
18432        assert_eq!(
18433            editors.len(),
18434            1,
18435            "After unsuccessful fallback, no other editor should have been opened"
18436        );
18437    });
18438}
18439
18440#[gpui::test]
18441async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18442    init_test(cx, |_| {});
18443
18444    let language = Arc::new(Language::new(
18445        LanguageConfig::default(),
18446        Some(tree_sitter_rust::LANGUAGE.into()),
18447    ));
18448
18449    let text = r#"
18450        #[cfg(test)]
18451        mod tests() {
18452            #[test]
18453            fn runnable_1() {
18454                let a = 1;
18455            }
18456
18457            #[test]
18458            fn runnable_2() {
18459                let a = 1;
18460                let b = 2;
18461            }
18462        }
18463    "#
18464    .unindent();
18465
18466    let fs = FakeFs::new(cx.executor());
18467    fs.insert_file("/file.rs", Default::default()).await;
18468
18469    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18470    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18471    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18472    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18473    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18474
18475    let editor = cx.new_window_entity(|window, cx| {
18476        Editor::new(
18477            EditorMode::full(),
18478            multi_buffer,
18479            Some(project.clone()),
18480            window,
18481            cx,
18482        )
18483    });
18484
18485    editor.update_in(cx, |editor, window, cx| {
18486        let snapshot = editor.buffer().read(cx).snapshot(cx);
18487        editor.tasks.insert(
18488            (buffer.read(cx).remote_id(), 3),
18489            RunnableTasks {
18490                templates: vec![],
18491                offset: snapshot.anchor_before(43),
18492                column: 0,
18493                extra_variables: HashMap::default(),
18494                context_range: BufferOffset(43)..BufferOffset(85),
18495            },
18496        );
18497        editor.tasks.insert(
18498            (buffer.read(cx).remote_id(), 8),
18499            RunnableTasks {
18500                templates: vec![],
18501                offset: snapshot.anchor_before(86),
18502                column: 0,
18503                extra_variables: HashMap::default(),
18504                context_range: BufferOffset(86)..BufferOffset(191),
18505            },
18506        );
18507
18508        // Test finding task when cursor is inside function body
18509        editor.change_selections(None, window, cx, |s| {
18510            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18511        });
18512        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18513        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18514
18515        // Test finding task when cursor is on function name
18516        editor.change_selections(None, window, cx, |s| {
18517            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18518        });
18519        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18520        assert_eq!(row, 8, "Should find task when cursor is on function name");
18521    });
18522}
18523
18524#[gpui::test]
18525async fn test_folding_buffers(cx: &mut TestAppContext) {
18526    init_test(cx, |_| {});
18527
18528    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18529    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18530    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18531
18532    let fs = FakeFs::new(cx.executor());
18533    fs.insert_tree(
18534        path!("/a"),
18535        json!({
18536            "first.rs": sample_text_1,
18537            "second.rs": sample_text_2,
18538            "third.rs": sample_text_3,
18539        }),
18540    )
18541    .await;
18542    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18543    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18544    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18545    let worktree = project.update(cx, |project, cx| {
18546        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18547        assert_eq!(worktrees.len(), 1);
18548        worktrees.pop().unwrap()
18549    });
18550    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18551
18552    let buffer_1 = project
18553        .update(cx, |project, cx| {
18554            project.open_buffer((worktree_id, "first.rs"), cx)
18555        })
18556        .await
18557        .unwrap();
18558    let buffer_2 = project
18559        .update(cx, |project, cx| {
18560            project.open_buffer((worktree_id, "second.rs"), cx)
18561        })
18562        .await
18563        .unwrap();
18564    let buffer_3 = project
18565        .update(cx, |project, cx| {
18566            project.open_buffer((worktree_id, "third.rs"), cx)
18567        })
18568        .await
18569        .unwrap();
18570
18571    let multi_buffer = cx.new(|cx| {
18572        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18573        multi_buffer.push_excerpts(
18574            buffer_1.clone(),
18575            [
18576                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18577                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18578                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18579            ],
18580            cx,
18581        );
18582        multi_buffer.push_excerpts(
18583            buffer_2.clone(),
18584            [
18585                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18586                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18587                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18588            ],
18589            cx,
18590        );
18591        multi_buffer.push_excerpts(
18592            buffer_3.clone(),
18593            [
18594                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18595                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18596                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18597            ],
18598            cx,
18599        );
18600        multi_buffer
18601    });
18602    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18603        Editor::new(
18604            EditorMode::full(),
18605            multi_buffer.clone(),
18606            Some(project.clone()),
18607            window,
18608            cx,
18609        )
18610    });
18611
18612    assert_eq!(
18613        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18614        "\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",
18615    );
18616
18617    multi_buffer_editor.update(cx, |editor, cx| {
18618        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18619    });
18620    assert_eq!(
18621        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18622        "\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 folding the first buffer, its text should not be displayed"
18624    );
18625
18626    multi_buffer_editor.update(cx, |editor, cx| {
18627        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18628    });
18629    assert_eq!(
18630        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18631        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18632        "After folding the second buffer, its text should not be displayed"
18633    );
18634
18635    multi_buffer_editor.update(cx, |editor, cx| {
18636        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18637    });
18638    assert_eq!(
18639        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18640        "\n\n\n\n\n",
18641        "After folding the third buffer, its text should not be displayed"
18642    );
18643
18644    // Emulate selection inside the fold logic, that should work
18645    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18646        editor
18647            .snapshot(window, cx)
18648            .next_line_boundary(Point::new(0, 4));
18649    });
18650
18651    multi_buffer_editor.update(cx, |editor, cx| {
18652        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18653    });
18654    assert_eq!(
18655        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18656        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18657        "After unfolding the second buffer, its text should be displayed"
18658    );
18659
18660    // Typing inside of buffer 1 causes that buffer to be unfolded.
18661    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18662        assert_eq!(
18663            multi_buffer
18664                .read(cx)
18665                .snapshot(cx)
18666                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18667                .collect::<String>(),
18668            "bbbb"
18669        );
18670        editor.change_selections(None, window, cx, |selections| {
18671            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18672        });
18673        editor.handle_input("B", window, cx);
18674    });
18675
18676    assert_eq!(
18677        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18678        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18679        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18680    );
18681
18682    multi_buffer_editor.update(cx, |editor, cx| {
18683        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18684    });
18685    assert_eq!(
18686        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18687        "\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",
18688        "After unfolding the all buffers, all original text should be displayed"
18689    );
18690}
18691
18692#[gpui::test]
18693async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18694    init_test(cx, |_| {});
18695
18696    let sample_text_1 = "1111\n2222\n3333".to_string();
18697    let sample_text_2 = "4444\n5555\n6666".to_string();
18698    let sample_text_3 = "7777\n8888\n9999".to_string();
18699
18700    let fs = FakeFs::new(cx.executor());
18701    fs.insert_tree(
18702        path!("/a"),
18703        json!({
18704            "first.rs": sample_text_1,
18705            "second.rs": sample_text_2,
18706            "third.rs": sample_text_3,
18707        }),
18708    )
18709    .await;
18710    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18713    let worktree = project.update(cx, |project, cx| {
18714        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18715        assert_eq!(worktrees.len(), 1);
18716        worktrees.pop().unwrap()
18717    });
18718    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18719
18720    let buffer_1 = project
18721        .update(cx, |project, cx| {
18722            project.open_buffer((worktree_id, "first.rs"), cx)
18723        })
18724        .await
18725        .unwrap();
18726    let buffer_2 = project
18727        .update(cx, |project, cx| {
18728            project.open_buffer((worktree_id, "second.rs"), cx)
18729        })
18730        .await
18731        .unwrap();
18732    let buffer_3 = project
18733        .update(cx, |project, cx| {
18734            project.open_buffer((worktree_id, "third.rs"), cx)
18735        })
18736        .await
18737        .unwrap();
18738
18739    let multi_buffer = cx.new(|cx| {
18740        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18741        multi_buffer.push_excerpts(
18742            buffer_1.clone(),
18743            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18744            cx,
18745        );
18746        multi_buffer.push_excerpts(
18747            buffer_2.clone(),
18748            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18749            cx,
18750        );
18751        multi_buffer.push_excerpts(
18752            buffer_3.clone(),
18753            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18754            cx,
18755        );
18756        multi_buffer
18757    });
18758
18759    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18760        Editor::new(
18761            EditorMode::full(),
18762            multi_buffer,
18763            Some(project.clone()),
18764            window,
18765            cx,
18766        )
18767    });
18768
18769    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18770    assert_eq!(
18771        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18772        full_text,
18773    );
18774
18775    multi_buffer_editor.update(cx, |editor, cx| {
18776        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18777    });
18778    assert_eq!(
18779        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18780        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18781        "After folding the first buffer, its text should not be displayed"
18782    );
18783
18784    multi_buffer_editor.update(cx, |editor, cx| {
18785        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18786    });
18787
18788    assert_eq!(
18789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18790        "\n\n\n\n\n\n7777\n8888\n9999",
18791        "After folding the second buffer, its text should not be displayed"
18792    );
18793
18794    multi_buffer_editor.update(cx, |editor, cx| {
18795        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18796    });
18797    assert_eq!(
18798        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18799        "\n\n\n\n\n",
18800        "After folding the third buffer, its text should not be displayed"
18801    );
18802
18803    multi_buffer_editor.update(cx, |editor, cx| {
18804        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18805    });
18806    assert_eq!(
18807        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18808        "\n\n\n\n4444\n5555\n6666\n\n",
18809        "After unfolding the second buffer, its text should be displayed"
18810    );
18811
18812    multi_buffer_editor.update(cx, |editor, cx| {
18813        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18814    });
18815    assert_eq!(
18816        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18817        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18818        "After unfolding the first buffer, its text should be displayed"
18819    );
18820
18821    multi_buffer_editor.update(cx, |editor, cx| {
18822        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18823    });
18824    assert_eq!(
18825        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18826        full_text,
18827        "After unfolding all buffers, all original text should be displayed"
18828    );
18829}
18830
18831#[gpui::test]
18832async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18833    init_test(cx, |_| {});
18834
18835    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18836
18837    let fs = FakeFs::new(cx.executor());
18838    fs.insert_tree(
18839        path!("/a"),
18840        json!({
18841            "main.rs": sample_text,
18842        }),
18843    )
18844    .await;
18845    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18846    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18847    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18848    let worktree = project.update(cx, |project, cx| {
18849        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18850        assert_eq!(worktrees.len(), 1);
18851        worktrees.pop().unwrap()
18852    });
18853    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18854
18855    let buffer_1 = project
18856        .update(cx, |project, cx| {
18857            project.open_buffer((worktree_id, "main.rs"), cx)
18858        })
18859        .await
18860        .unwrap();
18861
18862    let multi_buffer = cx.new(|cx| {
18863        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18864        multi_buffer.push_excerpts(
18865            buffer_1.clone(),
18866            [ExcerptRange::new(
18867                Point::new(0, 0)
18868                    ..Point::new(
18869                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18870                        0,
18871                    ),
18872            )],
18873            cx,
18874        );
18875        multi_buffer
18876    });
18877    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18878        Editor::new(
18879            EditorMode::full(),
18880            multi_buffer,
18881            Some(project.clone()),
18882            window,
18883            cx,
18884        )
18885    });
18886
18887    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18888    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18889        enum TestHighlight {}
18890        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18891        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18892        editor.highlight_text::<TestHighlight>(
18893            vec![highlight_range.clone()],
18894            HighlightStyle::color(Hsla::green()),
18895            cx,
18896        );
18897        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18898    });
18899
18900    let full_text = format!("\n\n{sample_text}");
18901    assert_eq!(
18902        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18903        full_text,
18904    );
18905}
18906
18907#[gpui::test]
18908async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18909    init_test(cx, |_| {});
18910    cx.update(|cx| {
18911        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18912            "keymaps/default-linux.json",
18913            cx,
18914        )
18915        .unwrap();
18916        cx.bind_keys(default_key_bindings);
18917    });
18918
18919    let (editor, cx) = cx.add_window_view(|window, cx| {
18920        let multi_buffer = MultiBuffer::build_multi(
18921            [
18922                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18923                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18924                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18925                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18926            ],
18927            cx,
18928        );
18929        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18930
18931        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18932        // fold all but the second buffer, so that we test navigating between two
18933        // adjacent folded buffers, as well as folded buffers at the start and
18934        // end the multibuffer
18935        editor.fold_buffer(buffer_ids[0], cx);
18936        editor.fold_buffer(buffer_ids[2], cx);
18937        editor.fold_buffer(buffer_ids[3], cx);
18938
18939        editor
18940    });
18941    cx.simulate_resize(size(px(1000.), px(1000.)));
18942
18943    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18944    cx.assert_excerpts_with_selections(indoc! {"
18945        [EXCERPT]
18946        ˇ[FOLDED]
18947        [EXCERPT]
18948        a1
18949        b1
18950        [EXCERPT]
18951        [FOLDED]
18952        [EXCERPT]
18953        [FOLDED]
18954        "
18955    });
18956    cx.simulate_keystroke("down");
18957    cx.assert_excerpts_with_selections(indoc! {"
18958        [EXCERPT]
18959        [FOLDED]
18960        [EXCERPT]
18961        ˇa1
18962        b1
18963        [EXCERPT]
18964        [FOLDED]
18965        [EXCERPT]
18966        [FOLDED]
18967        "
18968    });
18969    cx.simulate_keystroke("down");
18970    cx.assert_excerpts_with_selections(indoc! {"
18971        [EXCERPT]
18972        [FOLDED]
18973        [EXCERPT]
18974        a1
18975        ˇb1
18976        [EXCERPT]
18977        [FOLDED]
18978        [EXCERPT]
18979        [FOLDED]
18980        "
18981    });
18982    cx.simulate_keystroke("down");
18983    cx.assert_excerpts_with_selections(indoc! {"
18984        [EXCERPT]
18985        [FOLDED]
18986        [EXCERPT]
18987        a1
18988        b1
18989        ˇ[EXCERPT]
18990        [FOLDED]
18991        [EXCERPT]
18992        [FOLDED]
18993        "
18994    });
18995    cx.simulate_keystroke("down");
18996    cx.assert_excerpts_with_selections(indoc! {"
18997        [EXCERPT]
18998        [FOLDED]
18999        [EXCERPT]
19000        a1
19001        b1
19002        [EXCERPT]
19003        ˇ[FOLDED]
19004        [EXCERPT]
19005        [FOLDED]
19006        "
19007    });
19008    for _ in 0..5 {
19009        cx.simulate_keystroke("down");
19010        cx.assert_excerpts_with_selections(indoc! {"
19011            [EXCERPT]
19012            [FOLDED]
19013            [EXCERPT]
19014            a1
19015            b1
19016            [EXCERPT]
19017            [FOLDED]
19018            [EXCERPT]
19019            ˇ[FOLDED]
19020            "
19021        });
19022    }
19023
19024    cx.simulate_keystroke("up");
19025    cx.assert_excerpts_with_selections(indoc! {"
19026        [EXCERPT]
19027        [FOLDED]
19028        [EXCERPT]
19029        a1
19030        b1
19031        [EXCERPT]
19032        ˇ[FOLDED]
19033        [EXCERPT]
19034        [FOLDED]
19035        "
19036    });
19037    cx.simulate_keystroke("up");
19038    cx.assert_excerpts_with_selections(indoc! {"
19039        [EXCERPT]
19040        [FOLDED]
19041        [EXCERPT]
19042        a1
19043        b1
19044        ˇ[EXCERPT]
19045        [FOLDED]
19046        [EXCERPT]
19047        [FOLDED]
19048        "
19049    });
19050    cx.simulate_keystroke("up");
19051    cx.assert_excerpts_with_selections(indoc! {"
19052        [EXCERPT]
19053        [FOLDED]
19054        [EXCERPT]
19055        a1
19056        ˇb1
19057        [EXCERPT]
19058        [FOLDED]
19059        [EXCERPT]
19060        [FOLDED]
19061        "
19062    });
19063    cx.simulate_keystroke("up");
19064    cx.assert_excerpts_with_selections(indoc! {"
19065        [EXCERPT]
19066        [FOLDED]
19067        [EXCERPT]
19068        ˇa1
19069        b1
19070        [EXCERPT]
19071        [FOLDED]
19072        [EXCERPT]
19073        [FOLDED]
19074        "
19075    });
19076    for _ in 0..5 {
19077        cx.simulate_keystroke("up");
19078        cx.assert_excerpts_with_selections(indoc! {"
19079            [EXCERPT]
19080            ˇ[FOLDED]
19081            [EXCERPT]
19082            a1
19083            b1
19084            [EXCERPT]
19085            [FOLDED]
19086            [EXCERPT]
19087            [FOLDED]
19088            "
19089        });
19090    }
19091}
19092
19093#[gpui::test]
19094async fn test_inline_completion_text(cx: &mut TestAppContext) {
19095    init_test(cx, |_| {});
19096
19097    // Simple insertion
19098    assert_highlighted_edits(
19099        "Hello, world!",
19100        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19101        true,
19102        cx,
19103        |highlighted_edits, cx| {
19104            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19105            assert_eq!(highlighted_edits.highlights.len(), 1);
19106            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19107            assert_eq!(
19108                highlighted_edits.highlights[0].1.background_color,
19109                Some(cx.theme().status().created_background)
19110            );
19111        },
19112    )
19113    .await;
19114
19115    // Replacement
19116    assert_highlighted_edits(
19117        "This is a test.",
19118        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19119        false,
19120        cx,
19121        |highlighted_edits, cx| {
19122            assert_eq!(highlighted_edits.text, "That is a test.");
19123            assert_eq!(highlighted_edits.highlights.len(), 1);
19124            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19125            assert_eq!(
19126                highlighted_edits.highlights[0].1.background_color,
19127                Some(cx.theme().status().created_background)
19128            );
19129        },
19130    )
19131    .await;
19132
19133    // Multiple edits
19134    assert_highlighted_edits(
19135        "Hello, world!",
19136        vec![
19137            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19138            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19139        ],
19140        false,
19141        cx,
19142        |highlighted_edits, cx| {
19143            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19144            assert_eq!(highlighted_edits.highlights.len(), 2);
19145            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19146            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19147            assert_eq!(
19148                highlighted_edits.highlights[0].1.background_color,
19149                Some(cx.theme().status().created_background)
19150            );
19151            assert_eq!(
19152                highlighted_edits.highlights[1].1.background_color,
19153                Some(cx.theme().status().created_background)
19154            );
19155        },
19156    )
19157    .await;
19158
19159    // Multiple lines with edits
19160    assert_highlighted_edits(
19161        "First line\nSecond line\nThird line\nFourth line",
19162        vec![
19163            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19164            (
19165                Point::new(2, 0)..Point::new(2, 10),
19166                "New third line".to_string(),
19167            ),
19168            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19169        ],
19170        false,
19171        cx,
19172        |highlighted_edits, cx| {
19173            assert_eq!(
19174                highlighted_edits.text,
19175                "Second modified\nNew third line\nFourth updated line"
19176            );
19177            assert_eq!(highlighted_edits.highlights.len(), 3);
19178            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19179            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19180            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19181            for highlight in &highlighted_edits.highlights {
19182                assert_eq!(
19183                    highlight.1.background_color,
19184                    Some(cx.theme().status().created_background)
19185                );
19186            }
19187        },
19188    )
19189    .await;
19190}
19191
19192#[gpui::test]
19193async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19194    init_test(cx, |_| {});
19195
19196    // Deletion
19197    assert_highlighted_edits(
19198        "Hello, world!",
19199        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19200        true,
19201        cx,
19202        |highlighted_edits, cx| {
19203            assert_eq!(highlighted_edits.text, "Hello, world!");
19204            assert_eq!(highlighted_edits.highlights.len(), 1);
19205            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19206            assert_eq!(
19207                highlighted_edits.highlights[0].1.background_color,
19208                Some(cx.theme().status().deleted_background)
19209            );
19210        },
19211    )
19212    .await;
19213
19214    // Insertion
19215    assert_highlighted_edits(
19216        "Hello, world!",
19217        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19218        true,
19219        cx,
19220        |highlighted_edits, cx| {
19221            assert_eq!(highlighted_edits.highlights.len(), 1);
19222            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19223            assert_eq!(
19224                highlighted_edits.highlights[0].1.background_color,
19225                Some(cx.theme().status().created_background)
19226            );
19227        },
19228    )
19229    .await;
19230}
19231
19232async fn assert_highlighted_edits(
19233    text: &str,
19234    edits: Vec<(Range<Point>, String)>,
19235    include_deletions: bool,
19236    cx: &mut TestAppContext,
19237    assertion_fn: impl Fn(HighlightedText, &App),
19238) {
19239    let window = cx.add_window(|window, cx| {
19240        let buffer = MultiBuffer::build_simple(text, cx);
19241        Editor::new(EditorMode::full(), buffer, None, window, cx)
19242    });
19243    let cx = &mut VisualTestContext::from_window(*window, cx);
19244
19245    let (buffer, snapshot) = window
19246        .update(cx, |editor, _window, cx| {
19247            (
19248                editor.buffer().clone(),
19249                editor.buffer().read(cx).snapshot(cx),
19250            )
19251        })
19252        .unwrap();
19253
19254    let edits = edits
19255        .into_iter()
19256        .map(|(range, edit)| {
19257            (
19258                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19259                edit,
19260            )
19261        })
19262        .collect::<Vec<_>>();
19263
19264    let text_anchor_edits = edits
19265        .clone()
19266        .into_iter()
19267        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19268        .collect::<Vec<_>>();
19269
19270    let edit_preview = window
19271        .update(cx, |_, _window, cx| {
19272            buffer
19273                .read(cx)
19274                .as_singleton()
19275                .unwrap()
19276                .read(cx)
19277                .preview_edits(text_anchor_edits.into(), cx)
19278        })
19279        .unwrap()
19280        .await;
19281
19282    cx.update(|_window, cx| {
19283        let highlighted_edits = inline_completion_edit_text(
19284            &snapshot.as_singleton().unwrap().2,
19285            &edits,
19286            &edit_preview,
19287            include_deletions,
19288            cx,
19289        );
19290        assertion_fn(highlighted_edits, cx)
19291    });
19292}
19293
19294#[track_caller]
19295fn assert_breakpoint(
19296    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19297    path: &Arc<Path>,
19298    expected: Vec<(u32, Breakpoint)>,
19299) {
19300    if expected.len() == 0usize {
19301        assert!(!breakpoints.contains_key(path), "{}", path.display());
19302    } else {
19303        let mut breakpoint = breakpoints
19304            .get(path)
19305            .unwrap()
19306            .into_iter()
19307            .map(|breakpoint| {
19308                (
19309                    breakpoint.row,
19310                    Breakpoint {
19311                        message: breakpoint.message.clone(),
19312                        state: breakpoint.state,
19313                        condition: breakpoint.condition.clone(),
19314                        hit_condition: breakpoint.hit_condition.clone(),
19315                    },
19316                )
19317            })
19318            .collect::<Vec<_>>();
19319
19320        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19321
19322        assert_eq!(expected, breakpoint);
19323    }
19324}
19325
19326fn add_log_breakpoint_at_cursor(
19327    editor: &mut Editor,
19328    log_message: &str,
19329    window: &mut Window,
19330    cx: &mut Context<Editor>,
19331) {
19332    let (anchor, bp) = editor
19333        .breakpoints_at_cursors(window, cx)
19334        .first()
19335        .and_then(|(anchor, bp)| {
19336            if let Some(bp) = bp {
19337                Some((*anchor, bp.clone()))
19338            } else {
19339                None
19340            }
19341        })
19342        .unwrap_or_else(|| {
19343            let cursor_position: Point = editor.selections.newest(cx).head();
19344
19345            let breakpoint_position = editor
19346                .snapshot(window, cx)
19347                .display_snapshot
19348                .buffer_snapshot
19349                .anchor_before(Point::new(cursor_position.row, 0));
19350
19351            (breakpoint_position, Breakpoint::new_log(&log_message))
19352        });
19353
19354    editor.edit_breakpoint_at_anchor(
19355        anchor,
19356        bp,
19357        BreakpointEditAction::EditLogMessage(log_message.into()),
19358        cx,
19359    );
19360}
19361
19362#[gpui::test]
19363async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19364    init_test(cx, |_| {});
19365
19366    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19367    let fs = FakeFs::new(cx.executor());
19368    fs.insert_tree(
19369        path!("/a"),
19370        json!({
19371            "main.rs": sample_text,
19372        }),
19373    )
19374    .await;
19375    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19376    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19377    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19378
19379    let fs = FakeFs::new(cx.executor());
19380    fs.insert_tree(
19381        path!("/a"),
19382        json!({
19383            "main.rs": sample_text,
19384        }),
19385    )
19386    .await;
19387    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19389    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19390    let worktree_id = workspace
19391        .update(cx, |workspace, _window, cx| {
19392            workspace.project().update(cx, |project, cx| {
19393                project.worktrees(cx).next().unwrap().read(cx).id()
19394            })
19395        })
19396        .unwrap();
19397
19398    let buffer = project
19399        .update(cx, |project, cx| {
19400            project.open_buffer((worktree_id, "main.rs"), cx)
19401        })
19402        .await
19403        .unwrap();
19404
19405    let (editor, cx) = cx.add_window_view(|window, cx| {
19406        Editor::new(
19407            EditorMode::full(),
19408            MultiBuffer::build_from_buffer(buffer, cx),
19409            Some(project.clone()),
19410            window,
19411            cx,
19412        )
19413    });
19414
19415    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19416    let abs_path = project.read_with(cx, |project, cx| {
19417        project
19418            .absolute_path(&project_path, cx)
19419            .map(|path_buf| Arc::from(path_buf.to_owned()))
19420            .unwrap()
19421    });
19422
19423    // assert we can add breakpoint on the first line
19424    editor.update_in(cx, |editor, window, cx| {
19425        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19426        editor.move_to_end(&MoveToEnd, window, cx);
19427        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19428    });
19429
19430    let breakpoints = editor.update(cx, |editor, cx| {
19431        editor
19432            .breakpoint_store()
19433            .as_ref()
19434            .unwrap()
19435            .read(cx)
19436            .all_source_breakpoints(cx)
19437            .clone()
19438    });
19439
19440    assert_eq!(1, breakpoints.len());
19441    assert_breakpoint(
19442        &breakpoints,
19443        &abs_path,
19444        vec![
19445            (0, Breakpoint::new_standard()),
19446            (3, Breakpoint::new_standard()),
19447        ],
19448    );
19449
19450    editor.update_in(cx, |editor, window, cx| {
19451        editor.move_to_beginning(&MoveToBeginning, window, cx);
19452        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19453    });
19454
19455    let breakpoints = editor.update(cx, |editor, cx| {
19456        editor
19457            .breakpoint_store()
19458            .as_ref()
19459            .unwrap()
19460            .read(cx)
19461            .all_source_breakpoints(cx)
19462            .clone()
19463    });
19464
19465    assert_eq!(1, breakpoints.len());
19466    assert_breakpoint(
19467        &breakpoints,
19468        &abs_path,
19469        vec![(3, Breakpoint::new_standard())],
19470    );
19471
19472    editor.update_in(cx, |editor, window, cx| {
19473        editor.move_to_end(&MoveToEnd, window, cx);
19474        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19475    });
19476
19477    let breakpoints = editor.update(cx, |editor, cx| {
19478        editor
19479            .breakpoint_store()
19480            .as_ref()
19481            .unwrap()
19482            .read(cx)
19483            .all_source_breakpoints(cx)
19484            .clone()
19485    });
19486
19487    assert_eq!(0, breakpoints.len());
19488    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19489}
19490
19491#[gpui::test]
19492async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19493    init_test(cx, |_| {});
19494
19495    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19496
19497    let fs = FakeFs::new(cx.executor());
19498    fs.insert_tree(
19499        path!("/a"),
19500        json!({
19501            "main.rs": sample_text,
19502        }),
19503    )
19504    .await;
19505    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19506    let (workspace, cx) =
19507        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19508
19509    let worktree_id = workspace.update(cx, |workspace, cx| {
19510        workspace.project().update(cx, |project, cx| {
19511            project.worktrees(cx).next().unwrap().read(cx).id()
19512        })
19513    });
19514
19515    let buffer = project
19516        .update(cx, |project, cx| {
19517            project.open_buffer((worktree_id, "main.rs"), cx)
19518        })
19519        .await
19520        .unwrap();
19521
19522    let (editor, cx) = cx.add_window_view(|window, cx| {
19523        Editor::new(
19524            EditorMode::full(),
19525            MultiBuffer::build_from_buffer(buffer, cx),
19526            Some(project.clone()),
19527            window,
19528            cx,
19529        )
19530    });
19531
19532    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19533    let abs_path = project.read_with(cx, |project, cx| {
19534        project
19535            .absolute_path(&project_path, cx)
19536            .map(|path_buf| Arc::from(path_buf.to_owned()))
19537            .unwrap()
19538    });
19539
19540    editor.update_in(cx, |editor, window, cx| {
19541        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19542    });
19543
19544    let breakpoints = editor.update(cx, |editor, cx| {
19545        editor
19546            .breakpoint_store()
19547            .as_ref()
19548            .unwrap()
19549            .read(cx)
19550            .all_source_breakpoints(cx)
19551            .clone()
19552    });
19553
19554    assert_breakpoint(
19555        &breakpoints,
19556        &abs_path,
19557        vec![(0, Breakpoint::new_log("hello world"))],
19558    );
19559
19560    // Removing a log message from a log breakpoint should remove it
19561    editor.update_in(cx, |editor, window, cx| {
19562        add_log_breakpoint_at_cursor(editor, "", window, cx);
19563    });
19564
19565    let breakpoints = editor.update(cx, |editor, cx| {
19566        editor
19567            .breakpoint_store()
19568            .as_ref()
19569            .unwrap()
19570            .read(cx)
19571            .all_source_breakpoints(cx)
19572            .clone()
19573    });
19574
19575    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19576
19577    editor.update_in(cx, |editor, window, cx| {
19578        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19579        editor.move_to_end(&MoveToEnd, window, cx);
19580        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19581        // Not adding a log message to a standard breakpoint shouldn't remove it
19582        add_log_breakpoint_at_cursor(editor, "", window, cx);
19583    });
19584
19585    let breakpoints = editor.update(cx, |editor, cx| {
19586        editor
19587            .breakpoint_store()
19588            .as_ref()
19589            .unwrap()
19590            .read(cx)
19591            .all_source_breakpoints(cx)
19592            .clone()
19593    });
19594
19595    assert_breakpoint(
19596        &breakpoints,
19597        &abs_path,
19598        vec![
19599            (0, Breakpoint::new_standard()),
19600            (3, Breakpoint::new_standard()),
19601        ],
19602    );
19603
19604    editor.update_in(cx, |editor, window, cx| {
19605        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19606    });
19607
19608    let breakpoints = editor.update(cx, |editor, cx| {
19609        editor
19610            .breakpoint_store()
19611            .as_ref()
19612            .unwrap()
19613            .read(cx)
19614            .all_source_breakpoints(cx)
19615            .clone()
19616    });
19617
19618    assert_breakpoint(
19619        &breakpoints,
19620        &abs_path,
19621        vec![
19622            (0, Breakpoint::new_standard()),
19623            (3, Breakpoint::new_log("hello world")),
19624        ],
19625    );
19626
19627    editor.update_in(cx, |editor, window, cx| {
19628        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19629    });
19630
19631    let breakpoints = editor.update(cx, |editor, cx| {
19632        editor
19633            .breakpoint_store()
19634            .as_ref()
19635            .unwrap()
19636            .read(cx)
19637            .all_source_breakpoints(cx)
19638            .clone()
19639    });
19640
19641    assert_breakpoint(
19642        &breakpoints,
19643        &abs_path,
19644        vec![
19645            (0, Breakpoint::new_standard()),
19646            (3, Breakpoint::new_log("hello Earth!!")),
19647        ],
19648    );
19649}
19650
19651/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19652/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19653/// or when breakpoints were placed out of order. This tests for a regression too
19654#[gpui::test]
19655async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19656    init_test(cx, |_| {});
19657
19658    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19659    let fs = FakeFs::new(cx.executor());
19660    fs.insert_tree(
19661        path!("/a"),
19662        json!({
19663            "main.rs": sample_text,
19664        }),
19665    )
19666    .await;
19667    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19668    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19669    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19670
19671    let fs = FakeFs::new(cx.executor());
19672    fs.insert_tree(
19673        path!("/a"),
19674        json!({
19675            "main.rs": sample_text,
19676        }),
19677    )
19678    .await;
19679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19680    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19681    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19682    let worktree_id = workspace
19683        .update(cx, |workspace, _window, cx| {
19684            workspace.project().update(cx, |project, cx| {
19685                project.worktrees(cx).next().unwrap().read(cx).id()
19686            })
19687        })
19688        .unwrap();
19689
19690    let buffer = project
19691        .update(cx, |project, cx| {
19692            project.open_buffer((worktree_id, "main.rs"), cx)
19693        })
19694        .await
19695        .unwrap();
19696
19697    let (editor, cx) = cx.add_window_view(|window, cx| {
19698        Editor::new(
19699            EditorMode::full(),
19700            MultiBuffer::build_from_buffer(buffer, cx),
19701            Some(project.clone()),
19702            window,
19703            cx,
19704        )
19705    });
19706
19707    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19708    let abs_path = project.read_with(cx, |project, cx| {
19709        project
19710            .absolute_path(&project_path, cx)
19711            .map(|path_buf| Arc::from(path_buf.to_owned()))
19712            .unwrap()
19713    });
19714
19715    // assert we can add breakpoint on the first line
19716    editor.update_in(cx, |editor, window, cx| {
19717        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19718        editor.move_to_end(&MoveToEnd, window, cx);
19719        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19720        editor.move_up(&MoveUp, window, cx);
19721        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19722    });
19723
19724    let breakpoints = editor.update(cx, |editor, cx| {
19725        editor
19726            .breakpoint_store()
19727            .as_ref()
19728            .unwrap()
19729            .read(cx)
19730            .all_source_breakpoints(cx)
19731            .clone()
19732    });
19733
19734    assert_eq!(1, breakpoints.len());
19735    assert_breakpoint(
19736        &breakpoints,
19737        &abs_path,
19738        vec![
19739            (0, Breakpoint::new_standard()),
19740            (2, Breakpoint::new_standard()),
19741            (3, Breakpoint::new_standard()),
19742        ],
19743    );
19744
19745    editor.update_in(cx, |editor, window, cx| {
19746        editor.move_to_beginning(&MoveToBeginning, window, cx);
19747        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19748        editor.move_to_end(&MoveToEnd, window, cx);
19749        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19750        // Disabling a breakpoint that doesn't exist should do nothing
19751        editor.move_up(&MoveUp, window, cx);
19752        editor.move_up(&MoveUp, window, cx);
19753        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19754    });
19755
19756    let breakpoints = editor.update(cx, |editor, cx| {
19757        editor
19758            .breakpoint_store()
19759            .as_ref()
19760            .unwrap()
19761            .read(cx)
19762            .all_source_breakpoints(cx)
19763            .clone()
19764    });
19765
19766    let disable_breakpoint = {
19767        let mut bp = Breakpoint::new_standard();
19768        bp.state = BreakpointState::Disabled;
19769        bp
19770    };
19771
19772    assert_eq!(1, breakpoints.len());
19773    assert_breakpoint(
19774        &breakpoints,
19775        &abs_path,
19776        vec![
19777            (0, disable_breakpoint.clone()),
19778            (2, Breakpoint::new_standard()),
19779            (3, disable_breakpoint.clone()),
19780        ],
19781    );
19782
19783    editor.update_in(cx, |editor, window, cx| {
19784        editor.move_to_beginning(&MoveToBeginning, window, cx);
19785        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19786        editor.move_to_end(&MoveToEnd, window, cx);
19787        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19788        editor.move_up(&MoveUp, window, cx);
19789        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19790    });
19791
19792    let breakpoints = editor.update(cx, |editor, cx| {
19793        editor
19794            .breakpoint_store()
19795            .as_ref()
19796            .unwrap()
19797            .read(cx)
19798            .all_source_breakpoints(cx)
19799            .clone()
19800    });
19801
19802    assert_eq!(1, breakpoints.len());
19803    assert_breakpoint(
19804        &breakpoints,
19805        &abs_path,
19806        vec![
19807            (0, Breakpoint::new_standard()),
19808            (2, disable_breakpoint),
19809            (3, Breakpoint::new_standard()),
19810        ],
19811    );
19812}
19813
19814#[gpui::test]
19815async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19816    init_test(cx, |_| {});
19817    let capabilities = lsp::ServerCapabilities {
19818        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19819            prepare_provider: Some(true),
19820            work_done_progress_options: Default::default(),
19821        })),
19822        ..Default::default()
19823    };
19824    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19825
19826    cx.set_state(indoc! {"
19827        struct Fˇoo {}
19828    "});
19829
19830    cx.update_editor(|editor, _, cx| {
19831        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19832        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19833        editor.highlight_background::<DocumentHighlightRead>(
19834            &[highlight_range],
19835            |c| c.editor_document_highlight_read_background,
19836            cx,
19837        );
19838    });
19839
19840    let mut prepare_rename_handler = cx
19841        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19842            move |_, _, _| async move {
19843                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19844                    start: lsp::Position {
19845                        line: 0,
19846                        character: 7,
19847                    },
19848                    end: lsp::Position {
19849                        line: 0,
19850                        character: 10,
19851                    },
19852                })))
19853            },
19854        );
19855    let prepare_rename_task = cx
19856        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19857        .expect("Prepare rename was not started");
19858    prepare_rename_handler.next().await.unwrap();
19859    prepare_rename_task.await.expect("Prepare rename failed");
19860
19861    let mut rename_handler =
19862        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19863            let edit = lsp::TextEdit {
19864                range: lsp::Range {
19865                    start: lsp::Position {
19866                        line: 0,
19867                        character: 7,
19868                    },
19869                    end: lsp::Position {
19870                        line: 0,
19871                        character: 10,
19872                    },
19873                },
19874                new_text: "FooRenamed".to_string(),
19875            };
19876            Ok(Some(lsp::WorkspaceEdit::new(
19877                // Specify the same edit twice
19878                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19879            )))
19880        });
19881    let rename_task = cx
19882        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19883        .expect("Confirm rename was not started");
19884    rename_handler.next().await.unwrap();
19885    rename_task.await.expect("Confirm rename failed");
19886    cx.run_until_parked();
19887
19888    // Despite two edits, only one is actually applied as those are identical
19889    cx.assert_editor_state(indoc! {"
19890        struct FooRenamedˇ {}
19891    "});
19892}
19893
19894#[gpui::test]
19895async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19896    init_test(cx, |_| {});
19897    // These capabilities indicate that the server does not support prepare rename.
19898    let capabilities = lsp::ServerCapabilities {
19899        rename_provider: Some(lsp::OneOf::Left(true)),
19900        ..Default::default()
19901    };
19902    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19903
19904    cx.set_state(indoc! {"
19905        struct Fˇoo {}
19906    "});
19907
19908    cx.update_editor(|editor, _window, cx| {
19909        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19910        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19911        editor.highlight_background::<DocumentHighlightRead>(
19912            &[highlight_range],
19913            |c| c.editor_document_highlight_read_background,
19914            cx,
19915        );
19916    });
19917
19918    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19919        .expect("Prepare rename was not started")
19920        .await
19921        .expect("Prepare rename failed");
19922
19923    let mut rename_handler =
19924        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19925            let edit = lsp::TextEdit {
19926                range: lsp::Range {
19927                    start: lsp::Position {
19928                        line: 0,
19929                        character: 7,
19930                    },
19931                    end: lsp::Position {
19932                        line: 0,
19933                        character: 10,
19934                    },
19935                },
19936                new_text: "FooRenamed".to_string(),
19937            };
19938            Ok(Some(lsp::WorkspaceEdit::new(
19939                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19940            )))
19941        });
19942    let rename_task = cx
19943        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19944        .expect("Confirm rename was not started");
19945    rename_handler.next().await.unwrap();
19946    rename_task.await.expect("Confirm rename failed");
19947    cx.run_until_parked();
19948
19949    // Correct range is renamed, as `surrounding_word` is used to find it.
19950    cx.assert_editor_state(indoc! {"
19951        struct FooRenamedˇ {}
19952    "});
19953}
19954
19955#[gpui::test]
19956async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19957    init_test(cx, |_| {});
19958    let mut cx = EditorTestContext::new(cx).await;
19959
19960    let language = Arc::new(
19961        Language::new(
19962            LanguageConfig::default(),
19963            Some(tree_sitter_html::LANGUAGE.into()),
19964        )
19965        .with_brackets_query(
19966            r#"
19967            ("<" @open "/>" @close)
19968            ("</" @open ">" @close)
19969            ("<" @open ">" @close)
19970            ("\"" @open "\"" @close)
19971            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19972        "#,
19973        )
19974        .unwrap(),
19975    );
19976    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19977
19978    cx.set_state(indoc! {"
19979        <span>ˇ</span>
19980    "});
19981    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19982    cx.assert_editor_state(indoc! {"
19983        <span>
19984        ˇ
19985        </span>
19986    "});
19987
19988    cx.set_state(indoc! {"
19989        <span><span></span>ˇ</span>
19990    "});
19991    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19992    cx.assert_editor_state(indoc! {"
19993        <span><span></span>
19994        ˇ</span>
19995    "});
19996
19997    cx.set_state(indoc! {"
19998        <span>ˇ
19999        </span>
20000    "});
20001    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20002    cx.assert_editor_state(indoc! {"
20003        <span>
20004        ˇ
20005        </span>
20006    "});
20007}
20008
20009#[gpui::test(iterations = 10)]
20010async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20011    init_test(cx, |_| {});
20012
20013    let fs = FakeFs::new(cx.executor());
20014    fs.insert_tree(
20015        path!("/dir"),
20016        json!({
20017            "a.ts": "a",
20018        }),
20019    )
20020    .await;
20021
20022    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20023    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20024    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20025
20026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20027    language_registry.add(Arc::new(Language::new(
20028        LanguageConfig {
20029            name: "TypeScript".into(),
20030            matcher: LanguageMatcher {
20031                path_suffixes: vec!["ts".to_string()],
20032                ..Default::default()
20033            },
20034            ..Default::default()
20035        },
20036        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20037    )));
20038    let mut fake_language_servers = language_registry.register_fake_lsp(
20039        "TypeScript",
20040        FakeLspAdapter {
20041            capabilities: lsp::ServerCapabilities {
20042                code_lens_provider: Some(lsp::CodeLensOptions {
20043                    resolve_provider: Some(true),
20044                }),
20045                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20046                    commands: vec!["_the/command".to_string()],
20047                    ..lsp::ExecuteCommandOptions::default()
20048                }),
20049                ..lsp::ServerCapabilities::default()
20050            },
20051            ..FakeLspAdapter::default()
20052        },
20053    );
20054
20055    let (buffer, _handle) = project
20056        .update(cx, |p, cx| {
20057            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20058        })
20059        .await
20060        .unwrap();
20061    cx.executor().run_until_parked();
20062
20063    let fake_server = fake_language_servers.next().await.unwrap();
20064
20065    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20066    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20067    drop(buffer_snapshot);
20068    let actions = cx
20069        .update_window(*workspace, |_, window, cx| {
20070            project.code_actions(&buffer, anchor..anchor, window, cx)
20071        })
20072        .unwrap();
20073
20074    fake_server
20075        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20076            Ok(Some(vec![
20077                lsp::CodeLens {
20078                    range: lsp::Range::default(),
20079                    command: Some(lsp::Command {
20080                        title: "Code lens command".to_owned(),
20081                        command: "_the/command".to_owned(),
20082                        arguments: None,
20083                    }),
20084                    data: None,
20085                },
20086                lsp::CodeLens {
20087                    range: lsp::Range::default(),
20088                    command: Some(lsp::Command {
20089                        title: "Command not in capabilities".to_owned(),
20090                        command: "not in capabilities".to_owned(),
20091                        arguments: None,
20092                    }),
20093                    data: None,
20094                },
20095                lsp::CodeLens {
20096                    range: lsp::Range {
20097                        start: lsp::Position {
20098                            line: 1,
20099                            character: 1,
20100                        },
20101                        end: lsp::Position {
20102                            line: 1,
20103                            character: 1,
20104                        },
20105                    },
20106                    command: Some(lsp::Command {
20107                        title: "Command not in range".to_owned(),
20108                        command: "_the/command".to_owned(),
20109                        arguments: None,
20110                    }),
20111                    data: None,
20112                },
20113            ]))
20114        })
20115        .next()
20116        .await;
20117
20118    let actions = actions.await.unwrap();
20119    assert_eq!(
20120        actions.len(),
20121        1,
20122        "Should have only one valid action for the 0..0 range"
20123    );
20124    let action = actions[0].clone();
20125    let apply = project.update(cx, |project, cx| {
20126        project.apply_code_action(buffer.clone(), action, true, cx)
20127    });
20128
20129    // Resolving the code action does not populate its edits. In absence of
20130    // edits, we must execute the given command.
20131    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20132        |mut lens, _| async move {
20133            let lens_command = lens.command.as_mut().expect("should have a command");
20134            assert_eq!(lens_command.title, "Code lens command");
20135            lens_command.arguments = Some(vec![json!("the-argument")]);
20136            Ok(lens)
20137        },
20138    );
20139
20140    // While executing the command, the language server sends the editor
20141    // a `workspaceEdit` request.
20142    fake_server
20143        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20144            let fake = fake_server.clone();
20145            move |params, _| {
20146                assert_eq!(params.command, "_the/command");
20147                let fake = fake.clone();
20148                async move {
20149                    fake.server
20150                        .request::<lsp::request::ApplyWorkspaceEdit>(
20151                            lsp::ApplyWorkspaceEditParams {
20152                                label: None,
20153                                edit: lsp::WorkspaceEdit {
20154                                    changes: Some(
20155                                        [(
20156                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20157                                            vec![lsp::TextEdit {
20158                                                range: lsp::Range::new(
20159                                                    lsp::Position::new(0, 0),
20160                                                    lsp::Position::new(0, 0),
20161                                                ),
20162                                                new_text: "X".into(),
20163                                            }],
20164                                        )]
20165                                        .into_iter()
20166                                        .collect(),
20167                                    ),
20168                                    ..Default::default()
20169                                },
20170                            },
20171                        )
20172                        .await
20173                        .into_response()
20174                        .unwrap();
20175                    Ok(Some(json!(null)))
20176                }
20177            }
20178        })
20179        .next()
20180        .await;
20181
20182    // Applying the code lens command returns a project transaction containing the edits
20183    // sent by the language server in its `workspaceEdit` request.
20184    let transaction = apply.await.unwrap();
20185    assert!(transaction.0.contains_key(&buffer));
20186    buffer.update(cx, |buffer, cx| {
20187        assert_eq!(buffer.text(), "Xa");
20188        buffer.undo(cx);
20189        assert_eq!(buffer.text(), "a");
20190    });
20191}
20192
20193#[gpui::test]
20194async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20195    init_test(cx, |_| {});
20196
20197    let fs = FakeFs::new(cx.executor());
20198    let main_text = r#"fn main() {
20199println!("1");
20200println!("2");
20201println!("3");
20202println!("4");
20203println!("5");
20204}"#;
20205    let lib_text = "mod foo {}";
20206    fs.insert_tree(
20207        path!("/a"),
20208        json!({
20209            "lib.rs": lib_text,
20210            "main.rs": main_text,
20211        }),
20212    )
20213    .await;
20214
20215    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20216    let (workspace, cx) =
20217        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20218    let worktree_id = workspace.update(cx, |workspace, cx| {
20219        workspace.project().update(cx, |project, cx| {
20220            project.worktrees(cx).next().unwrap().read(cx).id()
20221        })
20222    });
20223
20224    let expected_ranges = vec![
20225        Point::new(0, 0)..Point::new(0, 0),
20226        Point::new(1, 0)..Point::new(1, 1),
20227        Point::new(2, 0)..Point::new(2, 2),
20228        Point::new(3, 0)..Point::new(3, 3),
20229    ];
20230
20231    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20232    let editor_1 = workspace
20233        .update_in(cx, |workspace, window, cx| {
20234            workspace.open_path(
20235                (worktree_id, "main.rs"),
20236                Some(pane_1.downgrade()),
20237                true,
20238                window,
20239                cx,
20240            )
20241        })
20242        .unwrap()
20243        .await
20244        .downcast::<Editor>()
20245        .unwrap();
20246    pane_1.update(cx, |pane, cx| {
20247        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20248        open_editor.update(cx, |editor, cx| {
20249            assert_eq!(
20250                editor.display_text(cx),
20251                main_text,
20252                "Original main.rs text on initial open",
20253            );
20254            assert_eq!(
20255                editor
20256                    .selections
20257                    .all::<Point>(cx)
20258                    .into_iter()
20259                    .map(|s| s.range())
20260                    .collect::<Vec<_>>(),
20261                vec![Point::zero()..Point::zero()],
20262                "Default selections on initial open",
20263            );
20264        })
20265    });
20266    editor_1.update_in(cx, |editor, window, cx| {
20267        editor.change_selections(None, window, cx, |s| {
20268            s.select_ranges(expected_ranges.clone());
20269        });
20270    });
20271
20272    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20273        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20274    });
20275    let editor_2 = workspace
20276        .update_in(cx, |workspace, window, cx| {
20277            workspace.open_path(
20278                (worktree_id, "main.rs"),
20279                Some(pane_2.downgrade()),
20280                true,
20281                window,
20282                cx,
20283            )
20284        })
20285        .unwrap()
20286        .await
20287        .downcast::<Editor>()
20288        .unwrap();
20289    pane_2.update(cx, |pane, cx| {
20290        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20291        open_editor.update(cx, |editor, cx| {
20292            assert_eq!(
20293                editor.display_text(cx),
20294                main_text,
20295                "Original main.rs text on initial open in another panel",
20296            );
20297            assert_eq!(
20298                editor
20299                    .selections
20300                    .all::<Point>(cx)
20301                    .into_iter()
20302                    .map(|s| s.range())
20303                    .collect::<Vec<_>>(),
20304                vec![Point::zero()..Point::zero()],
20305                "Default selections on initial open in another panel",
20306            );
20307        })
20308    });
20309
20310    editor_2.update_in(cx, |editor, window, cx| {
20311        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20312    });
20313
20314    let _other_editor_1 = workspace
20315        .update_in(cx, |workspace, window, cx| {
20316            workspace.open_path(
20317                (worktree_id, "lib.rs"),
20318                Some(pane_1.downgrade()),
20319                true,
20320                window,
20321                cx,
20322            )
20323        })
20324        .unwrap()
20325        .await
20326        .downcast::<Editor>()
20327        .unwrap();
20328    pane_1
20329        .update_in(cx, |pane, window, cx| {
20330            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20331        })
20332        .await
20333        .unwrap();
20334    drop(editor_1);
20335    pane_1.update(cx, |pane, cx| {
20336        pane.active_item()
20337            .unwrap()
20338            .downcast::<Editor>()
20339            .unwrap()
20340            .update(cx, |editor, cx| {
20341                assert_eq!(
20342                    editor.display_text(cx),
20343                    lib_text,
20344                    "Other file should be open and active",
20345                );
20346            });
20347        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20348    });
20349
20350    let _other_editor_2 = workspace
20351        .update_in(cx, |workspace, window, cx| {
20352            workspace.open_path(
20353                (worktree_id, "lib.rs"),
20354                Some(pane_2.downgrade()),
20355                true,
20356                window,
20357                cx,
20358            )
20359        })
20360        .unwrap()
20361        .await
20362        .downcast::<Editor>()
20363        .unwrap();
20364    pane_2
20365        .update_in(cx, |pane, window, cx| {
20366            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20367        })
20368        .await
20369        .unwrap();
20370    drop(editor_2);
20371    pane_2.update(cx, |pane, cx| {
20372        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20373        open_editor.update(cx, |editor, cx| {
20374            assert_eq!(
20375                editor.display_text(cx),
20376                lib_text,
20377                "Other file should be open and active in another panel too",
20378            );
20379        });
20380        assert_eq!(
20381            pane.items().count(),
20382            1,
20383            "No other editors should be open in another pane",
20384        );
20385    });
20386
20387    let _editor_1_reopened = workspace
20388        .update_in(cx, |workspace, window, cx| {
20389            workspace.open_path(
20390                (worktree_id, "main.rs"),
20391                Some(pane_1.downgrade()),
20392                true,
20393                window,
20394                cx,
20395            )
20396        })
20397        .unwrap()
20398        .await
20399        .downcast::<Editor>()
20400        .unwrap();
20401    let _editor_2_reopened = workspace
20402        .update_in(cx, |workspace, window, cx| {
20403            workspace.open_path(
20404                (worktree_id, "main.rs"),
20405                Some(pane_2.downgrade()),
20406                true,
20407                window,
20408                cx,
20409            )
20410        })
20411        .unwrap()
20412        .await
20413        .downcast::<Editor>()
20414        .unwrap();
20415    pane_1.update(cx, |pane, cx| {
20416        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20417        open_editor.update(cx, |editor, cx| {
20418            assert_eq!(
20419                editor.display_text(cx),
20420                main_text,
20421                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20422            );
20423            assert_eq!(
20424                editor
20425                    .selections
20426                    .all::<Point>(cx)
20427                    .into_iter()
20428                    .map(|s| s.range())
20429                    .collect::<Vec<_>>(),
20430                expected_ranges,
20431                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20432            );
20433        })
20434    });
20435    pane_2.update(cx, |pane, cx| {
20436        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20437        open_editor.update(cx, |editor, cx| {
20438            assert_eq!(
20439                editor.display_text(cx),
20440                r#"fn main() {
20441⋯rintln!("1");
20442⋯intln!("2");
20443⋯ntln!("3");
20444println!("4");
20445println!("5");
20446}"#,
20447                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20448            );
20449            assert_eq!(
20450                editor
20451                    .selections
20452                    .all::<Point>(cx)
20453                    .into_iter()
20454                    .map(|s| s.range())
20455                    .collect::<Vec<_>>(),
20456                vec![Point::zero()..Point::zero()],
20457                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20458            );
20459        })
20460    });
20461}
20462
20463#[gpui::test]
20464async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20465    init_test(cx, |_| {});
20466
20467    let fs = FakeFs::new(cx.executor());
20468    let main_text = r#"fn main() {
20469println!("1");
20470println!("2");
20471println!("3");
20472println!("4");
20473println!("5");
20474}"#;
20475    let lib_text = "mod foo {}";
20476    fs.insert_tree(
20477        path!("/a"),
20478        json!({
20479            "lib.rs": lib_text,
20480            "main.rs": main_text,
20481        }),
20482    )
20483    .await;
20484
20485    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20486    let (workspace, cx) =
20487        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20488    let worktree_id = workspace.update(cx, |workspace, cx| {
20489        workspace.project().update(cx, |project, cx| {
20490            project.worktrees(cx).next().unwrap().read(cx).id()
20491        })
20492    });
20493
20494    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20495    let editor = workspace
20496        .update_in(cx, |workspace, window, cx| {
20497            workspace.open_path(
20498                (worktree_id, "main.rs"),
20499                Some(pane.downgrade()),
20500                true,
20501                window,
20502                cx,
20503            )
20504        })
20505        .unwrap()
20506        .await
20507        .downcast::<Editor>()
20508        .unwrap();
20509    pane.update(cx, |pane, cx| {
20510        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20511        open_editor.update(cx, |editor, cx| {
20512            assert_eq!(
20513                editor.display_text(cx),
20514                main_text,
20515                "Original main.rs text on initial open",
20516            );
20517        })
20518    });
20519    editor.update_in(cx, |editor, window, cx| {
20520        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20521    });
20522
20523    cx.update_global(|store: &mut SettingsStore, cx| {
20524        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20525            s.restore_on_file_reopen = Some(false);
20526        });
20527    });
20528    editor.update_in(cx, |editor, window, cx| {
20529        editor.fold_ranges(
20530            vec![
20531                Point::new(1, 0)..Point::new(1, 1),
20532                Point::new(2, 0)..Point::new(2, 2),
20533                Point::new(3, 0)..Point::new(3, 3),
20534            ],
20535            false,
20536            window,
20537            cx,
20538        );
20539    });
20540    pane.update_in(cx, |pane, window, cx| {
20541        pane.close_all_items(&CloseAllItems::default(), window, cx)
20542    })
20543    .await
20544    .unwrap();
20545    pane.update(cx, |pane, _| {
20546        assert!(pane.active_item().is_none());
20547    });
20548    cx.update_global(|store: &mut SettingsStore, cx| {
20549        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20550            s.restore_on_file_reopen = Some(true);
20551        });
20552    });
20553
20554    let _editor_reopened = workspace
20555        .update_in(cx, |workspace, window, cx| {
20556            workspace.open_path(
20557                (worktree_id, "main.rs"),
20558                Some(pane.downgrade()),
20559                true,
20560                window,
20561                cx,
20562            )
20563        })
20564        .unwrap()
20565        .await
20566        .downcast::<Editor>()
20567        .unwrap();
20568    pane.update(cx, |pane, cx| {
20569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20570        open_editor.update(cx, |editor, cx| {
20571            assert_eq!(
20572                editor.display_text(cx),
20573                main_text,
20574                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20575            );
20576        })
20577    });
20578}
20579
20580#[gpui::test]
20581async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20582    struct EmptyModalView {
20583        focus_handle: gpui::FocusHandle,
20584    }
20585    impl EventEmitter<DismissEvent> for EmptyModalView {}
20586    impl Render for EmptyModalView {
20587        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20588            div()
20589        }
20590    }
20591    impl Focusable for EmptyModalView {
20592        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20593            self.focus_handle.clone()
20594        }
20595    }
20596    impl workspace::ModalView for EmptyModalView {}
20597    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20598        EmptyModalView {
20599            focus_handle: cx.focus_handle(),
20600        }
20601    }
20602
20603    init_test(cx, |_| {});
20604
20605    let fs = FakeFs::new(cx.executor());
20606    let project = Project::test(fs, [], cx).await;
20607    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20608    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20609    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20610    let editor = cx.new_window_entity(|window, cx| {
20611        Editor::new(
20612            EditorMode::full(),
20613            buffer,
20614            Some(project.clone()),
20615            window,
20616            cx,
20617        )
20618    });
20619    workspace
20620        .update(cx, |workspace, window, cx| {
20621            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20622        })
20623        .unwrap();
20624    editor.update_in(cx, |editor, window, cx| {
20625        editor.open_context_menu(&OpenContextMenu, window, cx);
20626        assert!(editor.mouse_context_menu.is_some());
20627    });
20628    workspace
20629        .update(cx, |workspace, window, cx| {
20630            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20631        })
20632        .unwrap();
20633    cx.read(|cx| {
20634        assert!(editor.read(cx).mouse_context_menu.is_none());
20635    });
20636}
20637
20638#[gpui::test]
20639async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20640    init_test(cx, |_| {});
20641
20642    let fs = FakeFs::new(cx.executor());
20643    fs.insert_file(path!("/file.html"), Default::default())
20644        .await;
20645
20646    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20647
20648    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20649    let html_language = Arc::new(Language::new(
20650        LanguageConfig {
20651            name: "HTML".into(),
20652            matcher: LanguageMatcher {
20653                path_suffixes: vec!["html".to_string()],
20654                ..LanguageMatcher::default()
20655            },
20656            brackets: BracketPairConfig {
20657                pairs: vec![BracketPair {
20658                    start: "<".into(),
20659                    end: ">".into(),
20660                    close: true,
20661                    ..Default::default()
20662                }],
20663                ..Default::default()
20664            },
20665            ..Default::default()
20666        },
20667        Some(tree_sitter_html::LANGUAGE.into()),
20668    ));
20669    language_registry.add(html_language);
20670    let mut fake_servers = language_registry.register_fake_lsp(
20671        "HTML",
20672        FakeLspAdapter {
20673            capabilities: lsp::ServerCapabilities {
20674                completion_provider: Some(lsp::CompletionOptions {
20675                    resolve_provider: Some(true),
20676                    ..Default::default()
20677                }),
20678                ..Default::default()
20679            },
20680            ..Default::default()
20681        },
20682    );
20683
20684    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20685    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20686
20687    let worktree_id = workspace
20688        .update(cx, |workspace, _window, cx| {
20689            workspace.project().update(cx, |project, cx| {
20690                project.worktrees(cx).next().unwrap().read(cx).id()
20691            })
20692        })
20693        .unwrap();
20694    project
20695        .update(cx, |project, cx| {
20696            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20697        })
20698        .await
20699        .unwrap();
20700    let editor = workspace
20701        .update(cx, |workspace, window, cx| {
20702            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20703        })
20704        .unwrap()
20705        .await
20706        .unwrap()
20707        .downcast::<Editor>()
20708        .unwrap();
20709
20710    let fake_server = fake_servers.next().await.unwrap();
20711    editor.update_in(cx, |editor, window, cx| {
20712        editor.set_text("<ad></ad>", window, cx);
20713        editor.change_selections(None, window, cx, |selections| {
20714            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20715        });
20716        let Some((buffer, _)) = editor
20717            .buffer
20718            .read(cx)
20719            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20720        else {
20721            panic!("Failed to get buffer for selection position");
20722        };
20723        let buffer = buffer.read(cx);
20724        let buffer_id = buffer.remote_id();
20725        let opening_range =
20726            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20727        let closing_range =
20728            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20729        let mut linked_ranges = HashMap::default();
20730        linked_ranges.insert(
20731            buffer_id,
20732            vec![(opening_range.clone(), vec![closing_range.clone()])],
20733        );
20734        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20735    });
20736    let mut completion_handle =
20737        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20738            Ok(Some(lsp::CompletionResponse::Array(vec![
20739                lsp::CompletionItem {
20740                    label: "head".to_string(),
20741                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20742                        lsp::InsertReplaceEdit {
20743                            new_text: "head".to_string(),
20744                            insert: lsp::Range::new(
20745                                lsp::Position::new(0, 1),
20746                                lsp::Position::new(0, 3),
20747                            ),
20748                            replace: lsp::Range::new(
20749                                lsp::Position::new(0, 1),
20750                                lsp::Position::new(0, 3),
20751                            ),
20752                        },
20753                    )),
20754                    ..Default::default()
20755                },
20756            ])))
20757        });
20758    editor.update_in(cx, |editor, window, cx| {
20759        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20760    });
20761    cx.run_until_parked();
20762    completion_handle.next().await.unwrap();
20763    editor.update(cx, |editor, _| {
20764        assert!(
20765            editor.context_menu_visible(),
20766            "Completion menu should be visible"
20767        );
20768    });
20769    editor.update_in(cx, |editor, window, cx| {
20770        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20771    });
20772    cx.executor().run_until_parked();
20773    editor.update(cx, |editor, cx| {
20774        assert_eq!(editor.text(cx), "<head></head>");
20775    });
20776}
20777
20778#[gpui::test]
20779async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20780    init_test(cx, |_| {});
20781
20782    let fs = FakeFs::new(cx.executor());
20783    fs.insert_tree(
20784        path!("/root"),
20785        json!({
20786            "a": {
20787                "main.rs": "fn main() {}",
20788            },
20789            "foo": {
20790                "bar": {
20791                    "external_file.rs": "pub mod external {}",
20792                }
20793            }
20794        }),
20795    )
20796    .await;
20797
20798    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20799    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20800    language_registry.add(rust_lang());
20801    let _fake_servers = language_registry.register_fake_lsp(
20802        "Rust",
20803        FakeLspAdapter {
20804            ..FakeLspAdapter::default()
20805        },
20806    );
20807    let (workspace, cx) =
20808        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20809    let worktree_id = workspace.update(cx, |workspace, cx| {
20810        workspace.project().update(cx, |project, cx| {
20811            project.worktrees(cx).next().unwrap().read(cx).id()
20812        })
20813    });
20814
20815    let assert_language_servers_count =
20816        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20817            project.update(cx, |project, cx| {
20818                let current = project
20819                    .lsp_store()
20820                    .read(cx)
20821                    .as_local()
20822                    .unwrap()
20823                    .language_servers
20824                    .len();
20825                assert_eq!(expected, current, "{context}");
20826            });
20827        };
20828
20829    assert_language_servers_count(
20830        0,
20831        "No servers should be running before any file is open",
20832        cx,
20833    );
20834    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20835    let main_editor = workspace
20836        .update_in(cx, |workspace, window, cx| {
20837            workspace.open_path(
20838                (worktree_id, "main.rs"),
20839                Some(pane.downgrade()),
20840                true,
20841                window,
20842                cx,
20843            )
20844        })
20845        .unwrap()
20846        .await
20847        .downcast::<Editor>()
20848        .unwrap();
20849    pane.update(cx, |pane, cx| {
20850        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20851        open_editor.update(cx, |editor, cx| {
20852            assert_eq!(
20853                editor.display_text(cx),
20854                "fn main() {}",
20855                "Original main.rs text on initial open",
20856            );
20857        });
20858        assert_eq!(open_editor, main_editor);
20859    });
20860    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20861
20862    let external_editor = workspace
20863        .update_in(cx, |workspace, window, cx| {
20864            workspace.open_abs_path(
20865                PathBuf::from("/root/foo/bar/external_file.rs"),
20866                OpenOptions::default(),
20867                window,
20868                cx,
20869            )
20870        })
20871        .await
20872        .expect("opening external file")
20873        .downcast::<Editor>()
20874        .expect("downcasted external file's open element to editor");
20875    pane.update(cx, |pane, cx| {
20876        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20877        open_editor.update(cx, |editor, cx| {
20878            assert_eq!(
20879                editor.display_text(cx),
20880                "pub mod external {}",
20881                "External file is open now",
20882            );
20883        });
20884        assert_eq!(open_editor, external_editor);
20885    });
20886    assert_language_servers_count(
20887        1,
20888        "Second, external, *.rs file should join the existing server",
20889        cx,
20890    );
20891
20892    pane.update_in(cx, |pane, window, cx| {
20893        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20894    })
20895    .await
20896    .unwrap();
20897    pane.update_in(cx, |pane, window, cx| {
20898        pane.navigate_backward(window, cx);
20899    });
20900    cx.run_until_parked();
20901    pane.update(cx, |pane, cx| {
20902        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20903        open_editor.update(cx, |editor, cx| {
20904            assert_eq!(
20905                editor.display_text(cx),
20906                "pub mod external {}",
20907                "External file is open now",
20908            );
20909        });
20910    });
20911    assert_language_servers_count(
20912        1,
20913        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20914        cx,
20915    );
20916
20917    cx.update(|_, cx| {
20918        workspace::reload(&workspace::Reload::default(), cx);
20919    });
20920    assert_language_servers_count(
20921        1,
20922        "After reloading the worktree with local and external files opened, only one project should be started",
20923        cx,
20924    );
20925}
20926
20927#[gpui::test]
20928async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20929    init_test(cx, |_| {});
20930
20931    let mut cx = EditorTestContext::new(cx).await;
20932    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20933    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20934
20935    // test cursor move to start of each line on tab
20936    // for `if`, `elif`, `else`, `while`, `with` and `for`
20937    cx.set_state(indoc! {"
20938        def main():
20939        ˇ    for item in items:
20940        ˇ        while item.active:
20941        ˇ            if item.value > 10:
20942        ˇ                continue
20943        ˇ            elif item.value < 0:
20944        ˇ                break
20945        ˇ            else:
20946        ˇ                with item.context() as ctx:
20947        ˇ                    yield count
20948        ˇ        else:
20949        ˇ            log('while else')
20950        ˇ    else:
20951        ˇ        log('for else')
20952    "});
20953    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20954    cx.assert_editor_state(indoc! {"
20955        def main():
20956            ˇfor item in items:
20957                ˇwhile item.active:
20958                    ˇif item.value > 10:
20959                        ˇcontinue
20960                    ˇelif item.value < 0:
20961                        ˇbreak
20962                    ˇelse:
20963                        ˇwith item.context() as ctx:
20964                            ˇyield count
20965                ˇelse:
20966                    ˇlog('while else')
20967            ˇelse:
20968                ˇlog('for else')
20969    "});
20970    // test relative indent is preserved when tab
20971    // for `if`, `elif`, `else`, `while`, `with` and `for`
20972    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20973    cx.assert_editor_state(indoc! {"
20974        def main():
20975                ˇfor item in items:
20976                    ˇwhile item.active:
20977                        ˇif item.value > 10:
20978                            ˇcontinue
20979                        ˇelif item.value < 0:
20980                            ˇbreak
20981                        ˇelse:
20982                            ˇwith item.context() as ctx:
20983                                ˇyield count
20984                    ˇelse:
20985                        ˇlog('while else')
20986                ˇelse:
20987                    ˇlog('for else')
20988    "});
20989
20990    // test cursor move to start of each line on tab
20991    // for `try`, `except`, `else`, `finally`, `match` and `def`
20992    cx.set_state(indoc! {"
20993        def main():
20994        ˇ    try:
20995        ˇ       fetch()
20996        ˇ    except ValueError:
20997        ˇ       handle_error()
20998        ˇ    else:
20999        ˇ        match value:
21000        ˇ            case _:
21001        ˇ    finally:
21002        ˇ        def status():
21003        ˇ            return 0
21004    "});
21005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21006    cx.assert_editor_state(indoc! {"
21007        def main():
21008            ˇtry:
21009                ˇfetch()
21010            ˇexcept ValueError:
21011                ˇhandle_error()
21012            ˇelse:
21013                ˇmatch value:
21014                    ˇcase _:
21015            ˇfinally:
21016                ˇdef status():
21017                    ˇreturn 0
21018    "});
21019    // test relative indent is preserved when tab
21020    // for `try`, `except`, `else`, `finally`, `match` and `def`
21021    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21022    cx.assert_editor_state(indoc! {"
21023        def main():
21024                ˇtry:
21025                    ˇfetch()
21026                ˇexcept ValueError:
21027                    ˇhandle_error()
21028                ˇelse:
21029                    ˇmatch value:
21030                        ˇcase _:
21031                ˇfinally:
21032                    ˇdef status():
21033                        ˇreturn 0
21034    "});
21035}
21036
21037#[gpui::test]
21038async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21039    init_test(cx, |_| {});
21040
21041    let mut cx = EditorTestContext::new(cx).await;
21042    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21043    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21044
21045    // test `else` auto outdents when typed inside `if` block
21046    cx.set_state(indoc! {"
21047        def main():
21048            if i == 2:
21049                return
21050                ˇ
21051    "});
21052    cx.update_editor(|editor, window, cx| {
21053        editor.handle_input("else:", window, cx);
21054    });
21055    cx.assert_editor_state(indoc! {"
21056        def main():
21057            if i == 2:
21058                return
21059            else:ˇ
21060    "});
21061
21062    // test `except` auto outdents when typed inside `try` block
21063    cx.set_state(indoc! {"
21064        def main():
21065            try:
21066                i = 2
21067                ˇ
21068    "});
21069    cx.update_editor(|editor, window, cx| {
21070        editor.handle_input("except:", window, cx);
21071    });
21072    cx.assert_editor_state(indoc! {"
21073        def main():
21074            try:
21075                i = 2
21076            except:ˇ
21077    "});
21078
21079    // test `else` auto outdents when typed inside `except` block
21080    cx.set_state(indoc! {"
21081        def main():
21082            try:
21083                i = 2
21084            except:
21085                j = 2
21086                ˇ
21087    "});
21088    cx.update_editor(|editor, window, cx| {
21089        editor.handle_input("else:", window, cx);
21090    });
21091    cx.assert_editor_state(indoc! {"
21092        def main():
21093            try:
21094                i = 2
21095            except:
21096                j = 2
21097            else:ˇ
21098    "});
21099
21100    // test `finally` auto outdents when typed inside `else` block
21101    cx.set_state(indoc! {"
21102        def main():
21103            try:
21104                i = 2
21105            except:
21106                j = 2
21107            else:
21108                k = 2
21109                ˇ
21110    "});
21111    cx.update_editor(|editor, window, cx| {
21112        editor.handle_input("finally:", window, cx);
21113    });
21114    cx.assert_editor_state(indoc! {"
21115        def main():
21116            try:
21117                i = 2
21118            except:
21119                j = 2
21120            else:
21121                k = 2
21122            finally:ˇ
21123    "});
21124
21125    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21126    // cx.set_state(indoc! {"
21127    //     def main():
21128    //         try:
21129    //             for i in range(n):
21130    //                 pass
21131    //             ˇ
21132    // "});
21133    // cx.update_editor(|editor, window, cx| {
21134    //     editor.handle_input("except:", window, cx);
21135    // });
21136    // cx.assert_editor_state(indoc! {"
21137    //     def main():
21138    //         try:
21139    //             for i in range(n):
21140    //                 pass
21141    //         except:ˇ
21142    // "});
21143
21144    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21145    // cx.set_state(indoc! {"
21146    //     def main():
21147    //         try:
21148    //             i = 2
21149    //         except:
21150    //             for i in range(n):
21151    //                 pass
21152    //             ˇ
21153    // "});
21154    // cx.update_editor(|editor, window, cx| {
21155    //     editor.handle_input("else:", window, cx);
21156    // });
21157    // cx.assert_editor_state(indoc! {"
21158    //     def main():
21159    //         try:
21160    //             i = 2
21161    //         except:
21162    //             for i in range(n):
21163    //                 pass
21164    //         else:ˇ
21165    // "});
21166
21167    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21168    // cx.set_state(indoc! {"
21169    //     def main():
21170    //         try:
21171    //             i = 2
21172    //         except:
21173    //             j = 2
21174    //         else:
21175    //             for i in range(n):
21176    //                 pass
21177    //             ˇ
21178    // "});
21179    // cx.update_editor(|editor, window, cx| {
21180    //     editor.handle_input("finally:", window, cx);
21181    // });
21182    // cx.assert_editor_state(indoc! {"
21183    //     def main():
21184    //         try:
21185    //             i = 2
21186    //         except:
21187    //             j = 2
21188    //         else:
21189    //             for i in range(n):
21190    //                 pass
21191    //         finally:ˇ
21192    // "});
21193
21194    // test `else` stays at correct indent when typed after `for` block
21195    cx.set_state(indoc! {"
21196        def main():
21197            for i in range(10):
21198                if i == 3:
21199                    break
21200            ˇ
21201    "});
21202    cx.update_editor(|editor, window, cx| {
21203        editor.handle_input("else:", window, cx);
21204    });
21205    cx.assert_editor_state(indoc! {"
21206        def main():
21207            for i in range(10):
21208                if i == 3:
21209                    break
21210            else:ˇ
21211    "});
21212
21213    // test does not outdent on typing after line with square brackets
21214    cx.set_state(indoc! {"
21215        def f() -> list[str]:
21216            ˇ
21217    "});
21218    cx.update_editor(|editor, window, cx| {
21219        editor.handle_input("a", window, cx);
21220    });
21221    cx.assert_editor_state(indoc! {"
21222        def f() -> list[str]:
2122321224    "});
21225}
21226
21227#[gpui::test]
21228async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21229    init_test(cx, |_| {});
21230    update_test_language_settings(cx, |settings| {
21231        settings.defaults.extend_comment_on_newline = Some(false);
21232    });
21233    let mut cx = EditorTestContext::new(cx).await;
21234    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21235    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21236
21237    // test correct indent after newline on comment
21238    cx.set_state(indoc! {"
21239        # COMMENT:ˇ
21240    "});
21241    cx.update_editor(|editor, window, cx| {
21242        editor.newline(&Newline, window, cx);
21243    });
21244    cx.assert_editor_state(indoc! {"
21245        # COMMENT:
21246        ˇ
21247    "});
21248
21249    // test correct indent after newline in brackets
21250    cx.set_state(indoc! {"
21251        {ˇ}
21252    "});
21253    cx.update_editor(|editor, window, cx| {
21254        editor.newline(&Newline, window, cx);
21255    });
21256    cx.run_until_parked();
21257    cx.assert_editor_state(indoc! {"
21258        {
21259            ˇ
21260        }
21261    "});
21262
21263    cx.set_state(indoc! {"
21264        (ˇ)
21265    "});
21266    cx.update_editor(|editor, window, cx| {
21267        editor.newline(&Newline, window, cx);
21268    });
21269    cx.run_until_parked();
21270    cx.assert_editor_state(indoc! {"
21271        (
21272            ˇ
21273        )
21274    "});
21275
21276    // do not indent after empty lists or dictionaries
21277    cx.set_state(indoc! {"
21278        a = []ˇ
21279    "});
21280    cx.update_editor(|editor, window, cx| {
21281        editor.newline(&Newline, window, cx);
21282    });
21283    cx.run_until_parked();
21284    cx.assert_editor_state(indoc! {"
21285        a = []
21286        ˇ
21287    "});
21288}
21289
21290fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21291    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21292    point..point
21293}
21294
21295#[track_caller]
21296fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21297    let (text, ranges) = marked_text_ranges(marked_text, true);
21298    assert_eq!(editor.text(cx), text);
21299    assert_eq!(
21300        editor.selections.ranges(cx),
21301        ranges,
21302        "Assert selections are {}",
21303        marked_text
21304    );
21305}
21306
21307pub fn handle_signature_help_request(
21308    cx: &mut EditorLspTestContext,
21309    mocked_response: lsp::SignatureHelp,
21310) -> impl Future<Output = ()> + use<> {
21311    let mut request =
21312        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21313            let mocked_response = mocked_response.clone();
21314            async move { Ok(Some(mocked_response)) }
21315        });
21316
21317    async move {
21318        request.next().await;
21319    }
21320}
21321
21322#[track_caller]
21323pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21324    cx.update_editor(|editor, _, _| {
21325        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21326            let entries = menu.entries.borrow();
21327            let entries = entries
21328                .iter()
21329                .map(|entry| entry.string.as_str())
21330                .collect::<Vec<_>>();
21331            assert_eq!(entries, expected);
21332        } else {
21333            panic!("Expected completions menu");
21334        }
21335    });
21336}
21337
21338/// Handle completion request passing a marked string specifying where the completion
21339/// should be triggered from using '|' character, what range should be replaced, and what completions
21340/// should be returned using '<' and '>' to delimit the range.
21341///
21342/// Also see `handle_completion_request_with_insert_and_replace`.
21343#[track_caller]
21344pub fn handle_completion_request(
21345    marked_string: &str,
21346    completions: Vec<&'static str>,
21347    is_incomplete: bool,
21348    counter: Arc<AtomicUsize>,
21349    cx: &mut EditorLspTestContext,
21350) -> impl Future<Output = ()> {
21351    let complete_from_marker: TextRangeMarker = '|'.into();
21352    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21353    let (_, mut marked_ranges) = marked_text_ranges_by(
21354        marked_string,
21355        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21356    );
21357
21358    let complete_from_position =
21359        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21360    let replace_range =
21361        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21362
21363    let mut request =
21364        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21365            let completions = completions.clone();
21366            counter.fetch_add(1, atomic::Ordering::Release);
21367            async move {
21368                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21369                assert_eq!(
21370                    params.text_document_position.position,
21371                    complete_from_position
21372                );
21373                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21374                    is_incomplete: is_incomplete,
21375                    item_defaults: None,
21376                    items: completions
21377                        .iter()
21378                        .map(|completion_text| lsp::CompletionItem {
21379                            label: completion_text.to_string(),
21380                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21381                                range: replace_range,
21382                                new_text: completion_text.to_string(),
21383                            })),
21384                            ..Default::default()
21385                        })
21386                        .collect(),
21387                })))
21388            }
21389        });
21390
21391    async move {
21392        request.next().await;
21393    }
21394}
21395
21396/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21397/// given instead, which also contains an `insert` range.
21398///
21399/// This function uses markers to define ranges:
21400/// - `|` marks the cursor position
21401/// - `<>` marks the replace range
21402/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21403pub fn handle_completion_request_with_insert_and_replace(
21404    cx: &mut EditorLspTestContext,
21405    marked_string: &str,
21406    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21407    counter: Arc<AtomicUsize>,
21408) -> impl Future<Output = ()> {
21409    let complete_from_marker: TextRangeMarker = '|'.into();
21410    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21411    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21412
21413    let (_, mut marked_ranges) = marked_text_ranges_by(
21414        marked_string,
21415        vec![
21416            complete_from_marker.clone(),
21417            replace_range_marker.clone(),
21418            insert_range_marker.clone(),
21419        ],
21420    );
21421
21422    let complete_from_position =
21423        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21424    let replace_range =
21425        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21426
21427    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21428        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21429        _ => lsp::Range {
21430            start: replace_range.start,
21431            end: complete_from_position,
21432        },
21433    };
21434
21435    let mut request =
21436        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21437            let completions = completions.clone();
21438            counter.fetch_add(1, atomic::Ordering::Release);
21439            async move {
21440                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21441                assert_eq!(
21442                    params.text_document_position.position, complete_from_position,
21443                    "marker `|` position doesn't match",
21444                );
21445                Ok(Some(lsp::CompletionResponse::Array(
21446                    completions
21447                        .iter()
21448                        .map(|(label, new_text)| lsp::CompletionItem {
21449                            label: label.to_string(),
21450                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21451                                lsp::InsertReplaceEdit {
21452                                    insert: insert_range,
21453                                    replace: replace_range,
21454                                    new_text: new_text.to_string(),
21455                                },
21456                            )),
21457                            ..Default::default()
21458                        })
21459                        .collect(),
21460                )))
21461            }
21462        });
21463
21464    async move {
21465        request.next().await;
21466    }
21467}
21468
21469fn handle_resolve_completion_request(
21470    cx: &mut EditorLspTestContext,
21471    edits: Option<Vec<(&'static str, &'static str)>>,
21472) -> impl Future<Output = ()> {
21473    let edits = edits.map(|edits| {
21474        edits
21475            .iter()
21476            .map(|(marked_string, new_text)| {
21477                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21478                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21479                lsp::TextEdit::new(replace_range, new_text.to_string())
21480            })
21481            .collect::<Vec<_>>()
21482    });
21483
21484    let mut request =
21485        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21486            let edits = edits.clone();
21487            async move {
21488                Ok(lsp::CompletionItem {
21489                    additional_text_edits: edits,
21490                    ..Default::default()
21491                })
21492            }
21493        });
21494
21495    async move {
21496        request.next().await;
21497    }
21498}
21499
21500pub(crate) fn update_test_language_settings(
21501    cx: &mut TestAppContext,
21502    f: impl Fn(&mut AllLanguageSettingsContent),
21503) {
21504    cx.update(|cx| {
21505        SettingsStore::update_global(cx, |store, cx| {
21506            store.update_user_settings::<AllLanguageSettings>(cx, f);
21507        });
21508    });
21509}
21510
21511pub(crate) fn update_test_project_settings(
21512    cx: &mut TestAppContext,
21513    f: impl Fn(&mut ProjectSettings),
21514) {
21515    cx.update(|cx| {
21516        SettingsStore::update_global(cx, |store, cx| {
21517            store.update_user_settings::<ProjectSettings>(cx, f);
21518        });
21519    });
21520}
21521
21522pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21523    cx.update(|cx| {
21524        assets::Assets.load_test_fonts(cx);
21525        let store = SettingsStore::test(cx);
21526        cx.set_global(store);
21527        theme::init(theme::LoadThemes::JustBase, cx);
21528        release_channel::init(SemanticVersion::default(), cx);
21529        client::init_settings(cx);
21530        language::init(cx);
21531        Project::init_settings(cx);
21532        workspace::init_settings(cx);
21533        crate::init(cx);
21534    });
21535
21536    update_test_language_settings(cx, f);
21537}
21538
21539#[track_caller]
21540fn assert_hunk_revert(
21541    not_reverted_text_with_selections: &str,
21542    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21543    expected_reverted_text_with_selections: &str,
21544    base_text: &str,
21545    cx: &mut EditorLspTestContext,
21546) {
21547    cx.set_state(not_reverted_text_with_selections);
21548    cx.set_head_text(base_text);
21549    cx.executor().run_until_parked();
21550
21551    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21552        let snapshot = editor.snapshot(window, cx);
21553        let reverted_hunk_statuses = snapshot
21554            .buffer_snapshot
21555            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21556            .map(|hunk| hunk.status().kind)
21557            .collect::<Vec<_>>();
21558
21559        editor.git_restore(&Default::default(), window, cx);
21560        reverted_hunk_statuses
21561    });
21562    cx.executor().run_until_parked();
21563    cx.assert_editor_state(expected_reverted_text_with_selections);
21564    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21565}
21566
21567#[gpui::test]
21568async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
21569    init_test(cx, |_| {});
21570
21571    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
21572    let counter = diagnostic_requests.clone();
21573
21574    let fs = FakeFs::new(cx.executor());
21575    fs.insert_tree(
21576        path!("/a"),
21577        json!({
21578            "first.rs": "fn main() { let a = 5; }",
21579            "second.rs": "// Test file",
21580        }),
21581    )
21582    .await;
21583
21584    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21585    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21586    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21587
21588    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21589    language_registry.add(rust_lang());
21590    let mut fake_servers = language_registry.register_fake_lsp(
21591        "Rust",
21592        FakeLspAdapter {
21593            capabilities: lsp::ServerCapabilities {
21594                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
21595                    lsp::DiagnosticOptions {
21596                        identifier: None,
21597                        inter_file_dependencies: true,
21598                        workspace_diagnostics: true,
21599                        work_done_progress_options: Default::default(),
21600                    },
21601                )),
21602                ..Default::default()
21603            },
21604            ..Default::default()
21605        },
21606    );
21607
21608    let editor = workspace
21609        .update(cx, |workspace, window, cx| {
21610            workspace.open_abs_path(
21611                PathBuf::from(path!("/a/first.rs")),
21612                OpenOptions::default(),
21613                window,
21614                cx,
21615            )
21616        })
21617        .unwrap()
21618        .await
21619        .unwrap()
21620        .downcast::<Editor>()
21621        .unwrap();
21622    let fake_server = fake_servers.next().await.unwrap();
21623    let mut first_request = fake_server
21624        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
21625            counter.fetch_add(1, atomic::Ordering::Release);
21626            assert_eq!(
21627                params.text_document.uri,
21628                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
21629            );
21630            async move {
21631                Ok(lsp::DocumentDiagnosticReportResult::Report(
21632                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
21633                        related_documents: None,
21634                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
21635                            items: Vec::new(),
21636                            result_id: None,
21637                        },
21638                    }),
21639                ))
21640            }
21641        });
21642
21643    cx.executor().advance_clock(Duration::from_millis(60));
21644    cx.executor().run_until_parked();
21645    assert_eq!(
21646        diagnostic_requests.load(atomic::Ordering::Acquire),
21647        1,
21648        "Opening file should trigger diagnostic request"
21649    );
21650    first_request
21651        .next()
21652        .await
21653        .expect("should have sent the first diagnostics pull request");
21654
21655    // Editing should trigger diagnostics
21656    editor.update_in(cx, |editor, window, cx| {
21657        editor.handle_input("2", window, cx)
21658    });
21659    cx.executor().advance_clock(Duration::from_millis(60));
21660    cx.executor().run_until_parked();
21661    assert_eq!(
21662        diagnostic_requests.load(atomic::Ordering::Acquire),
21663        2,
21664        "Editing should trigger diagnostic request"
21665    );
21666
21667    // Moving cursor should not trigger diagnostic request
21668    editor.update_in(cx, |editor, window, cx| {
21669        editor.change_selections(None, window, cx, |s| {
21670            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
21671        });
21672    });
21673    cx.executor().advance_clock(Duration::from_millis(60));
21674    cx.executor().run_until_parked();
21675    assert_eq!(
21676        diagnostic_requests.load(atomic::Ordering::Acquire),
21677        2,
21678        "Cursor movement should not trigger diagnostic request"
21679    );
21680
21681    // Multiple rapid edits should be debounced
21682    for _ in 0..5 {
21683        editor.update_in(cx, |editor, window, cx| {
21684            editor.handle_input("x", window, cx)
21685        });
21686    }
21687    cx.executor().advance_clock(Duration::from_millis(60));
21688    cx.executor().run_until_parked();
21689
21690    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
21691    assert!(
21692        final_requests <= 4,
21693        "Multiple rapid edits should be debounced (got {} requests)",
21694        final_requests
21695    );
21696}