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, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, 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, SaveOptions},
   60};
   61
   62#[gpui::test]
   63fn test_edit_events(cx: &mut TestAppContext) {
   64    init_test(cx, |_| {});
   65
   66    let buffer = cx.new(|cx| {
   67        let mut buffer = language::Buffer::local("123456", cx);
   68        buffer.set_group_interval(Duration::from_secs(1));
   69        buffer
   70    });
   71
   72    let events = Rc::new(RefCell::new(Vec::new()));
   73    let editor1 = cx.add_window({
   74        let events = events.clone();
   75        |window, cx| {
   76            let entity = cx.entity().clone();
   77            cx.subscribe_in(
   78                &entity,
   79                window,
   80                move |_, _, event: &EditorEvent, _, _| match event {
   81                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   82                    EditorEvent::BufferEdited => {
   83                        events.borrow_mut().push(("editor1", "buffer edited"))
   84                    }
   85                    _ => {}
   86                },
   87            )
   88            .detach();
   89            Editor::for_buffer(buffer.clone(), None, window, cx)
   90        }
   91    });
   92
   93    let editor2 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            cx.subscribe_in(
   97                &cx.entity().clone(),
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor2", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  113
  114    // Mutating editor 1 will emit an `Edited` event only for that editor.
  115    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  116    assert_eq!(
  117        mem::take(&mut *events.borrow_mut()),
  118        [
  119            ("editor1", "edited"),
  120            ("editor1", "buffer edited"),
  121            ("editor2", "buffer edited"),
  122        ]
  123    );
  124
  125    // Mutating editor 2 will emit an `Edited` event only for that editor.
  126    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  127    assert_eq!(
  128        mem::take(&mut *events.borrow_mut()),
  129        [
  130            ("editor2", "edited"),
  131            ("editor1", "buffer edited"),
  132            ("editor2", "buffer edited"),
  133        ]
  134    );
  135
  136    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  137    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  138    assert_eq!(
  139        mem::take(&mut *events.borrow_mut()),
  140        [
  141            ("editor1", "edited"),
  142            ("editor1", "buffer edited"),
  143            ("editor2", "buffer edited"),
  144        ]
  145    );
  146
  147    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  148    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  149    assert_eq!(
  150        mem::take(&mut *events.borrow_mut()),
  151        [
  152            ("editor1", "edited"),
  153            ("editor1", "buffer edited"),
  154            ("editor2", "buffer edited"),
  155        ]
  156    );
  157
  158    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  159    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  160    assert_eq!(
  161        mem::take(&mut *events.borrow_mut()),
  162        [
  163            ("editor2", "edited"),
  164            ("editor1", "buffer edited"),
  165            ("editor2", "buffer edited"),
  166        ]
  167    );
  168
  169    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  170    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  171    assert_eq!(
  172        mem::take(&mut *events.borrow_mut()),
  173        [
  174            ("editor2", "edited"),
  175            ("editor1", "buffer edited"),
  176            ("editor2", "buffer edited"),
  177        ]
  178    );
  179
  180    // No event is emitted when the mutation is a no-op.
  181    _ = editor2.update(cx, |editor, window, cx| {
  182        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  183
  184        editor.backspace(&Backspace, window, cx);
  185    });
  186    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  187}
  188
  189#[gpui::test]
  190fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  191    init_test(cx, |_| {});
  192
  193    let mut now = Instant::now();
  194    let group_interval = Duration::from_millis(1);
  195    let buffer = cx.new(|cx| {
  196        let mut buf = language::Buffer::local("123456", cx);
  197        buf.set_group_interval(group_interval);
  198        buf
  199    });
  200    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  201    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  202
  203    _ = editor.update(cx, |editor, window, cx| {
  204        editor.start_transaction_at(now, window, cx);
  205        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  206
  207        editor.insert("cd", window, cx);
  208        editor.end_transaction_at(now, cx);
  209        assert_eq!(editor.text(cx), "12cd56");
  210        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  211
  212        editor.start_transaction_at(now, window, cx);
  213        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  214        editor.insert("e", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cde6");
  217        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  218
  219        now += group_interval + Duration::from_millis(1);
  220        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  221
  222        // Simulate an edit in another editor
  223        buffer.update(cx, |buffer, cx| {
  224            buffer.start_transaction_at(now, cx);
  225            buffer.edit([(0..1, "a")], None, cx);
  226            buffer.edit([(1..1, "b")], None, cx);
  227            buffer.end_transaction_at(now, cx);
  228        });
  229
  230        assert_eq!(editor.text(cx), "ab2cde6");
  231        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  232
  233        // Last transaction happened past the group interval in a different editor.
  234        // Undo it individually and don't restore selections.
  235        editor.undo(&Undo, window, cx);
  236        assert_eq!(editor.text(cx), "12cde6");
  237        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  238
  239        // First two transactions happened within the group interval in this editor.
  240        // Undo them together and restore selections.
  241        editor.undo(&Undo, window, cx);
  242        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  243        assert_eq!(editor.text(cx), "123456");
  244        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  245
  246        // Redo the first two transactions together.
  247        editor.redo(&Redo, window, cx);
  248        assert_eq!(editor.text(cx), "12cde6");
  249        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  250
  251        // Redo the last transaction on its own.
  252        editor.redo(&Redo, window, cx);
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  255
  256        // Test empty transactions.
  257        editor.start_transaction_at(now, window, cx);
  258        editor.end_transaction_at(now, cx);
  259        editor.undo(&Undo, window, cx);
  260        assert_eq!(editor.text(cx), "12cde6");
  261    });
  262}
  263
  264#[gpui::test]
  265fn test_ime_composition(cx: &mut TestAppContext) {
  266    init_test(cx, |_| {});
  267
  268    let buffer = cx.new(|cx| {
  269        let mut buffer = language::Buffer::local("abcde", cx);
  270        // Ensure automatic grouping doesn't occur.
  271        buffer.set_group_interval(Duration::ZERO);
  272        buffer
  273    });
  274
  275    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  276    cx.add_window(|window, cx| {
  277        let mut editor = build_editor(buffer.clone(), window, cx);
  278
  279        // Start a new IME composition.
  280        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  281        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  282        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  283        assert_eq!(editor.text(cx), "äbcde");
  284        assert_eq!(
  285            editor.marked_text_ranges(cx),
  286            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  287        );
  288
  289        // Finalize IME composition.
  290        editor.replace_text_in_range(None, "ā", window, cx);
  291        assert_eq!(editor.text(cx), "ābcde");
  292        assert_eq!(editor.marked_text_ranges(cx), None);
  293
  294        // IME composition edits are grouped and are undone/redone at once.
  295        editor.undo(&Default::default(), window, cx);
  296        assert_eq!(editor.text(cx), "abcde");
  297        assert_eq!(editor.marked_text_ranges(cx), None);
  298        editor.redo(&Default::default(), window, cx);
  299        assert_eq!(editor.text(cx), "ābcde");
  300        assert_eq!(editor.marked_text_ranges(cx), None);
  301
  302        // Start a new IME composition.
  303        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  304        assert_eq!(
  305            editor.marked_text_ranges(cx),
  306            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  307        );
  308
  309        // Undoing during an IME composition cancels it.
  310        editor.undo(&Default::default(), window, cx);
  311        assert_eq!(editor.text(cx), "ābcde");
  312        assert_eq!(editor.marked_text_ranges(cx), None);
  313
  314        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  315        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  316        assert_eq!(editor.text(cx), "ābcdè");
  317        assert_eq!(
  318            editor.marked_text_ranges(cx),
  319            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  320        );
  321
  322        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  323        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  324        assert_eq!(editor.text(cx), "ābcdę");
  325        assert_eq!(editor.marked_text_ranges(cx), None);
  326
  327        // Start a new IME composition with multiple cursors.
  328        editor.change_selections(None, window, cx, |s| {
  329            s.select_ranges([
  330                OffsetUtf16(1)..OffsetUtf16(1),
  331                OffsetUtf16(3)..OffsetUtf16(3),
  332                OffsetUtf16(5)..OffsetUtf16(5),
  333            ])
  334        });
  335        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  336        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  337        assert_eq!(
  338            editor.marked_text_ranges(cx),
  339            Some(vec![
  340                OffsetUtf16(0)..OffsetUtf16(3),
  341                OffsetUtf16(4)..OffsetUtf16(7),
  342                OffsetUtf16(8)..OffsetUtf16(11)
  343            ])
  344        );
  345
  346        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  347        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  348        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  349        assert_eq!(
  350            editor.marked_text_ranges(cx),
  351            Some(vec![
  352                OffsetUtf16(1)..OffsetUtf16(2),
  353                OffsetUtf16(5)..OffsetUtf16(6),
  354                OffsetUtf16(9)..OffsetUtf16(10)
  355            ])
  356        );
  357
  358        // Finalize IME composition with multiple cursors.
  359        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  360        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  361        assert_eq!(editor.marked_text_ranges(cx), None);
  362
  363        editor
  364    });
  365}
  366
  367#[gpui::test]
  368fn test_selection_with_mouse(cx: &mut TestAppContext) {
  369    init_test(cx, |_| {});
  370
  371    let editor = cx.add_window(|window, cx| {
  372        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  373        build_editor(buffer, window, cx)
  374    });
  375
  376    _ = editor.update(cx, |editor, window, cx| {
  377        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  378    });
  379    assert_eq!(
  380        editor
  381            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  382            .unwrap(),
  383        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  384    );
  385
  386    _ = editor.update(cx, |editor, window, cx| {
  387        editor.update_selection(
  388            DisplayPoint::new(DisplayRow(3), 3),
  389            0,
  390            gpui::Point::<f32>::default(),
  391            window,
  392            cx,
  393        );
  394    });
  395
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(1), 1),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.end_selection(window, cx);
  422        editor.update_selection(
  423            DisplayPoint::new(DisplayRow(3), 3),
  424            0,
  425            gpui::Point::<f32>::default(),
  426            window,
  427            cx,
  428        );
  429    });
  430
  431    assert_eq!(
  432        editor
  433            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  434            .unwrap(),
  435        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  436    );
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  440        editor.update_selection(
  441            DisplayPoint::new(DisplayRow(0), 0),
  442            0,
  443            gpui::Point::<f32>::default(),
  444            window,
  445            cx,
  446        );
  447    });
  448
  449    assert_eq!(
  450        editor
  451            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  452            .unwrap(),
  453        [
  454            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  455            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  456        ]
  457    );
  458
  459    _ = editor.update(cx, |editor, window, cx| {
  460        editor.end_selection(window, cx);
  461    });
  462
  463    assert_eq!(
  464        editor
  465            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  466            .unwrap(),
  467        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  468    );
  469}
  470
  471#[gpui::test]
  472fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  473    init_test(cx, |_| {});
  474
  475    let editor = cx.add_window(|window, cx| {
  476        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  477        build_editor(buffer, window, cx)
  478    });
  479
  480    _ = editor.update(cx, |editor, window, cx| {
  481        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  482    });
  483
  484    _ = editor.update(cx, |editor, window, cx| {
  485        editor.end_selection(window, cx);
  486    });
  487
  488    _ = editor.update(cx, |editor, window, cx| {
  489        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  490    });
  491
  492    _ = editor.update(cx, |editor, window, cx| {
  493        editor.end_selection(window, cx);
  494    });
  495
  496    assert_eq!(
  497        editor
  498            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  499            .unwrap(),
  500        [
  501            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  502            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  503        ]
  504    );
  505
  506    _ = editor.update(cx, |editor, window, cx| {
  507        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  508    });
  509
  510    _ = editor.update(cx, |editor, window, cx| {
  511        editor.end_selection(window, cx);
  512    });
  513
  514    assert_eq!(
  515        editor
  516            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  517            .unwrap(),
  518        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  519    );
  520}
  521
  522#[gpui::test]
  523fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  524    init_test(cx, |_| {});
  525
  526    let editor = cx.add_window(|window, cx| {
  527        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  528        build_editor(buffer, window, cx)
  529    });
  530
  531    _ = editor.update(cx, |editor, window, cx| {
  532        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  533        assert_eq!(
  534            editor.selections.display_ranges(cx),
  535            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  536        );
  537    });
  538
  539    _ = editor.update(cx, |editor, window, cx| {
  540        editor.update_selection(
  541            DisplayPoint::new(DisplayRow(3), 3),
  542            0,
  543            gpui::Point::<f32>::default(),
  544            window,
  545            cx,
  546        );
  547        assert_eq!(
  548            editor.selections.display_ranges(cx),
  549            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  550        );
  551    });
  552
  553    _ = editor.update(cx, |editor, window, cx| {
  554        editor.cancel(&Cancel, window, cx);
  555        editor.update_selection(
  556            DisplayPoint::new(DisplayRow(1), 1),
  557            0,
  558            gpui::Point::<f32>::default(),
  559            window,
  560            cx,
  561        );
  562        assert_eq!(
  563            editor.selections.display_ranges(cx),
  564            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  565        );
  566    });
  567}
  568
  569#[gpui::test]
  570fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  571    init_test(cx, |_| {});
  572
  573    let editor = cx.add_window(|window, cx| {
  574        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  575        build_editor(buffer, window, cx)
  576    });
  577
  578    _ = editor.update(cx, |editor, window, cx| {
  579        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  580        assert_eq!(
  581            editor.selections.display_ranges(cx),
  582            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  583        );
  584
  585        editor.move_down(&Default::default(), window, cx);
  586        assert_eq!(
  587            editor.selections.display_ranges(cx),
  588            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  589        );
  590
  591        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  592        assert_eq!(
  593            editor.selections.display_ranges(cx),
  594            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  595        );
  596
  597        editor.move_up(&Default::default(), window, cx);
  598        assert_eq!(
  599            editor.selections.display_ranges(cx),
  600            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  601        );
  602    });
  603}
  604
  605#[gpui::test]
  606fn test_clone(cx: &mut TestAppContext) {
  607    init_test(cx, |_| {});
  608
  609    let (text, selection_ranges) = marked_text_ranges(
  610        indoc! {"
  611            one
  612            two
  613            threeˇ
  614            four
  615            fiveˇ
  616        "},
  617        true,
  618    );
  619
  620    let editor = cx.add_window(|window, cx| {
  621        let buffer = MultiBuffer::build_simple(&text, cx);
  622        build_editor(buffer, window, cx)
  623    });
  624
  625    _ = editor.update(cx, |editor, window, cx| {
  626        editor.change_selections(None, window, cx, |s| {
  627            s.select_ranges(selection_ranges.clone())
  628        });
  629        editor.fold_creases(
  630            vec![
  631                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  632                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  633            ],
  634            true,
  635            window,
  636            cx,
  637        );
  638    });
  639
  640    let cloned_editor = editor
  641        .update(cx, |editor, _, cx| {
  642            cx.open_window(Default::default(), |window, cx| {
  643                cx.new(|cx| editor.clone(window, cx))
  644            })
  645        })
  646        .unwrap()
  647        .unwrap();
  648
  649    let snapshot = editor
  650        .update(cx, |e, window, cx| e.snapshot(window, cx))
  651        .unwrap();
  652    let cloned_snapshot = cloned_editor
  653        .update(cx, |e, window, cx| e.snapshot(window, cx))
  654        .unwrap();
  655
  656    assert_eq!(
  657        cloned_editor
  658            .update(cx, |e, _, cx| e.display_text(cx))
  659            .unwrap(),
  660        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  661    );
  662    assert_eq!(
  663        cloned_snapshot
  664            .folds_in_range(0..text.len())
  665            .collect::<Vec<_>>(),
  666        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  667    );
  668    assert_set_eq!(
  669        cloned_editor
  670            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  671            .unwrap(),
  672        editor
  673            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  674            .unwrap()
  675    );
  676    assert_set_eq!(
  677        cloned_editor
  678            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  679            .unwrap(),
  680        editor
  681            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  682            .unwrap()
  683    );
  684}
  685
  686#[gpui::test]
  687async fn test_navigation_history(cx: &mut TestAppContext) {
  688    init_test(cx, |_| {});
  689
  690    use workspace::item::Item;
  691
  692    let fs = FakeFs::new(cx.executor());
  693    let project = Project::test(fs, [], cx).await;
  694    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  695    let pane = workspace
  696        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  697        .unwrap();
  698
  699    _ = workspace.update(cx, |_v, window, cx| {
  700        cx.new(|cx| {
  701            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  702            let mut editor = build_editor(buffer.clone(), window, cx);
  703            let handle = cx.entity();
  704            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  705
  706            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  707                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  708            }
  709
  710            // Move the cursor a small distance.
  711            // Nothing is added to the navigation history.
  712            editor.change_selections(None, window, cx, |s| {
  713                s.select_display_ranges([
  714                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  715                ])
  716            });
  717            editor.change_selections(None, window, cx, |s| {
  718                s.select_display_ranges([
  719                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  720                ])
  721            });
  722            assert!(pop_history(&mut editor, cx).is_none());
  723
  724            // Move the cursor a large distance.
  725            // The history can jump back to the previous position.
  726            editor.change_selections(None, window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  729                ])
  730            });
  731            let nav_entry = pop_history(&mut editor, cx).unwrap();
  732            editor.navigate(nav_entry.data.unwrap(), window, cx);
  733            assert_eq!(nav_entry.item.id(), cx.entity_id());
  734            assert_eq!(
  735                editor.selections.display_ranges(cx),
  736                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  737            );
  738            assert!(pop_history(&mut editor, cx).is_none());
  739
  740            // Move the cursor a small distance via the mouse.
  741            // Nothing is added to the navigation history.
  742            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  743            editor.end_selection(window, cx);
  744            assert_eq!(
  745                editor.selections.display_ranges(cx),
  746                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  747            );
  748            assert!(pop_history(&mut editor, cx).is_none());
  749
  750            // Move the cursor a large distance via the mouse.
  751            // The history can jump back to the previous position.
  752            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  753            editor.end_selection(window, cx);
  754            assert_eq!(
  755                editor.selections.display_ranges(cx),
  756                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  757            );
  758            let nav_entry = pop_history(&mut editor, cx).unwrap();
  759            editor.navigate(nav_entry.data.unwrap(), window, cx);
  760            assert_eq!(nav_entry.item.id(), cx.entity_id());
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Set scroll position to check later
  768            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  769            let original_scroll_position = editor.scroll_manager.anchor();
  770
  771            // Jump to the end of the document and adjust scroll
  772            editor.move_to_end(&MoveToEnd, window, cx);
  773            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  774            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  775
  776            let nav_entry = pop_history(&mut editor, cx).unwrap();
  777            editor.navigate(nav_entry.data.unwrap(), window, cx);
  778            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  779
  780            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  781            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  782            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  783            let invalid_point = Point::new(9999, 0);
  784            editor.navigate(
  785                Box::new(NavigationData {
  786                    cursor_anchor: invalid_anchor,
  787                    cursor_position: invalid_point,
  788                    scroll_anchor: ScrollAnchor {
  789                        anchor: invalid_anchor,
  790                        offset: Default::default(),
  791                    },
  792                    scroll_top_row: invalid_point.row,
  793                }),
  794                window,
  795                cx,
  796            );
  797            assert_eq!(
  798                editor.selections.display_ranges(cx),
  799                &[editor.max_point(cx)..editor.max_point(cx)]
  800            );
  801            assert_eq!(
  802                editor.scroll_position(cx),
  803                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  804            );
  805
  806            editor
  807        })
  808    });
  809}
  810
  811#[gpui::test]
  812fn test_cancel(cx: &mut TestAppContext) {
  813    init_test(cx, |_| {});
  814
  815    let editor = cx.add_window(|window, cx| {
  816        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  817        build_editor(buffer, window, cx)
  818    });
  819
  820    _ = editor.update(cx, |editor, window, cx| {
  821        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  822        editor.update_selection(
  823            DisplayPoint::new(DisplayRow(1), 1),
  824            0,
  825            gpui::Point::<f32>::default(),
  826            window,
  827            cx,
  828        );
  829        editor.end_selection(window, cx);
  830
  831        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  832        editor.update_selection(
  833            DisplayPoint::new(DisplayRow(0), 3),
  834            0,
  835            gpui::Point::<f32>::default(),
  836            window,
  837            cx,
  838        );
  839        editor.end_selection(window, cx);
  840        assert_eq!(
  841            editor.selections.display_ranges(cx),
  842            [
  843                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  844                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  845            ]
  846        );
  847    });
  848
  849    _ = editor.update(cx, |editor, window, cx| {
  850        editor.cancel(&Cancel, window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  854        );
  855    });
  856
  857    _ = editor.update(cx, |editor, window, cx| {
  858        editor.cancel(&Cancel, window, cx);
  859        assert_eq!(
  860            editor.selections.display_ranges(cx),
  861            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  862        );
  863    });
  864}
  865
  866#[gpui::test]
  867fn test_fold_action(cx: &mut TestAppContext) {
  868    init_test(cx, |_| {});
  869
  870    let editor = cx.add_window(|window, cx| {
  871        let buffer = MultiBuffer::build_simple(
  872            &"
  873                impl Foo {
  874                    // Hello!
  875
  876                    fn a() {
  877                        1
  878                    }
  879
  880                    fn b() {
  881                        2
  882                    }
  883
  884                    fn c() {
  885                        3
  886                    }
  887                }
  888            "
  889            .unindent(),
  890            cx,
  891        );
  892        build_editor(buffer.clone(), window, cx)
  893    });
  894
  895    _ = editor.update(cx, |editor, window, cx| {
  896        editor.change_selections(None, window, cx, |s| {
  897            s.select_display_ranges([
  898                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  899            ]);
  900        });
  901        editor.fold(&Fold, window, cx);
  902        assert_eq!(
  903            editor.display_text(cx),
  904            "
  905                impl Foo {
  906                    // Hello!
  907
  908                    fn a() {
  909                        1
  910                    }
  911
  912                    fn b() {⋯
  913                    }
  914
  915                    fn c() {⋯
  916                    }
  917                }
  918            "
  919            .unindent(),
  920        );
  921
  922        editor.fold(&Fold, window, cx);
  923        assert_eq!(
  924            editor.display_text(cx),
  925            "
  926                impl Foo {⋯
  927                }
  928            "
  929            .unindent(),
  930        );
  931
  932        editor.unfold_lines(&UnfoldLines, window, cx);
  933        assert_eq!(
  934            editor.display_text(cx),
  935            "
  936                impl Foo {
  937                    // Hello!
  938
  939                    fn a() {
  940                        1
  941                    }
  942
  943                    fn b() {⋯
  944                    }
  945
  946                    fn c() {⋯
  947                    }
  948                }
  949            "
  950            .unindent(),
  951        );
  952
  953        editor.unfold_lines(&UnfoldLines, window, cx);
  954        assert_eq!(
  955            editor.display_text(cx),
  956            editor.buffer.read(cx).read(cx).text()
  957        );
  958    });
  959}
  960
  961#[gpui::test]
  962fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  963    init_test(cx, |_| {});
  964
  965    let editor = cx.add_window(|window, cx| {
  966        let buffer = MultiBuffer::build_simple(
  967            &"
  968                class Foo:
  969                    # Hello!
  970
  971                    def a():
  972                        print(1)
  973
  974                    def b():
  975                        print(2)
  976
  977                    def c():
  978                        print(3)
  979            "
  980            .unindent(),
  981            cx,
  982        );
  983        build_editor(buffer.clone(), window, cx)
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.change_selections(None, window, cx, |s| {
  988            s.select_display_ranges([
  989                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  990            ]);
  991        });
  992        editor.fold(&Fold, window, cx);
  993        assert_eq!(
  994            editor.display_text(cx),
  995            "
  996                class Foo:
  997                    # Hello!
  998
  999                    def a():
 1000                        print(1)
 1001
 1002                    def b():⋯
 1003
 1004                    def c():⋯
 1005            "
 1006            .unindent(),
 1007        );
 1008
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.unfold_lines(&UnfoldLines, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:
 1023                    # Hello!
 1024
 1025                    def a():
 1026                        print(1)
 1027
 1028                    def b():⋯
 1029
 1030                    def c():⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            editor.buffer.read(cx).read(cx).text()
 1039        );
 1040    });
 1041}
 1042
 1043#[gpui::test]
 1044fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1045    init_test(cx, |_| {});
 1046
 1047    let editor = cx.add_window(|window, cx| {
 1048        let buffer = MultiBuffer::build_simple(
 1049            &"
 1050                class Foo:
 1051                    # Hello!
 1052
 1053                    def a():
 1054                        print(1)
 1055
 1056                    def b():
 1057                        print(2)
 1058
 1059
 1060                    def c():
 1061                        print(3)
 1062
 1063
 1064            "
 1065            .unindent(),
 1066            cx,
 1067        );
 1068        build_editor(buffer.clone(), window, cx)
 1069    });
 1070
 1071    _ = editor.update(cx, |editor, window, cx| {
 1072        editor.change_selections(None, window, cx, |s| {
 1073            s.select_display_ranges([
 1074                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1075            ]);
 1076        });
 1077        editor.fold(&Fold, window, cx);
 1078        assert_eq!(
 1079            editor.display_text(cx),
 1080            "
 1081                class Foo:
 1082                    # Hello!
 1083
 1084                    def a():
 1085                        print(1)
 1086
 1087                    def b():⋯
 1088
 1089
 1090                    def c():⋯
 1091
 1092
 1093            "
 1094            .unindent(),
 1095        );
 1096
 1097        editor.fold(&Fold, window, cx);
 1098        assert_eq!(
 1099            editor.display_text(cx),
 1100            "
 1101                class Foo:⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.unfold_lines(&UnfoldLines, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:
 1113                    # Hello!
 1114
 1115                    def a():
 1116                        print(1)
 1117
 1118                    def b():⋯
 1119
 1120
 1121                    def c():⋯
 1122
 1123
 1124            "
 1125            .unindent(),
 1126        );
 1127
 1128        editor.unfold_lines(&UnfoldLines, window, cx);
 1129        assert_eq!(
 1130            editor.display_text(cx),
 1131            editor.buffer.read(cx).read(cx).text()
 1132        );
 1133    });
 1134}
 1135
 1136#[gpui::test]
 1137fn test_fold_at_level(cx: &mut TestAppContext) {
 1138    init_test(cx, |_| {});
 1139
 1140    let editor = cx.add_window(|window, cx| {
 1141        let buffer = MultiBuffer::build_simple(
 1142            &"
 1143                class Foo:
 1144                    # Hello!
 1145
 1146                    def a():
 1147                        print(1)
 1148
 1149                    def b():
 1150                        print(2)
 1151
 1152
 1153                class Bar:
 1154                    # World!
 1155
 1156                    def a():
 1157                        print(1)
 1158
 1159                    def b():
 1160                        print(2)
 1161
 1162
 1163            "
 1164            .unindent(),
 1165            cx,
 1166        );
 1167        build_editor(buffer.clone(), window, cx)
 1168    });
 1169
 1170    _ = editor.update(cx, |editor, window, cx| {
 1171        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1172        assert_eq!(
 1173            editor.display_text(cx),
 1174            "
 1175                class Foo:
 1176                    # Hello!
 1177
 1178                    def a():⋯
 1179
 1180                    def b():⋯
 1181
 1182
 1183                class Bar:
 1184                    # World!
 1185
 1186                    def a():⋯
 1187
 1188                    def b():⋯
 1189
 1190
 1191            "
 1192            .unindent(),
 1193        );
 1194
 1195        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1196        assert_eq!(
 1197            editor.display_text(cx),
 1198            "
 1199                class Foo:⋯
 1200
 1201
 1202                class Bar:⋯
 1203
 1204
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_all(&UnfoldAll, window, cx);
 1210        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1211        assert_eq!(
 1212            editor.display_text(cx),
 1213            "
 1214                class Foo:
 1215                    # Hello!
 1216
 1217                    def a():
 1218                        print(1)
 1219
 1220                    def b():
 1221                        print(2)
 1222
 1223
 1224                class Bar:
 1225                    # World!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234            "
 1235            .unindent(),
 1236        );
 1237
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            editor.buffer.read(cx).read(cx).text()
 1241        );
 1242    });
 1243}
 1244
 1245#[gpui::test]
 1246fn test_move_cursor(cx: &mut TestAppContext) {
 1247    init_test(cx, |_| {});
 1248
 1249    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1250    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1251
 1252    buffer.update(cx, |buffer, cx| {
 1253        buffer.edit(
 1254            vec![
 1255                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1256                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1257            ],
 1258            None,
 1259            cx,
 1260        );
 1261    });
 1262    _ = editor.update(cx, |editor, window, cx| {
 1263        assert_eq!(
 1264            editor.selections.display_ranges(cx),
 1265            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1266        );
 1267
 1268        editor.move_down(&MoveDown, window, cx);
 1269        assert_eq!(
 1270            editor.selections.display_ranges(cx),
 1271            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1272        );
 1273
 1274        editor.move_right(&MoveRight, window, cx);
 1275        assert_eq!(
 1276            editor.selections.display_ranges(cx),
 1277            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1278        );
 1279
 1280        editor.move_left(&MoveLeft, window, cx);
 1281        assert_eq!(
 1282            editor.selections.display_ranges(cx),
 1283            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1284        );
 1285
 1286        editor.move_up(&MoveUp, window, cx);
 1287        assert_eq!(
 1288            editor.selections.display_ranges(cx),
 1289            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1290        );
 1291
 1292        editor.move_to_end(&MoveToEnd, window, cx);
 1293        assert_eq!(
 1294            editor.selections.display_ranges(cx),
 1295            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1296        );
 1297
 1298        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1299        assert_eq!(
 1300            editor.selections.display_ranges(cx),
 1301            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1302        );
 1303
 1304        editor.change_selections(None, window, cx, |s| {
 1305            s.select_display_ranges([
 1306                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1307            ]);
 1308        });
 1309        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.select_to_end(&SelectToEnd, window, cx);
 1316        assert_eq!(
 1317            editor.selections.display_ranges(cx),
 1318            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1319        );
 1320    });
 1321}
 1322
 1323#[gpui::test]
 1324fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1325    init_test(cx, |_| {});
 1326
 1327    let editor = cx.add_window(|window, cx| {
 1328        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1329        build_editor(buffer.clone(), window, cx)
 1330    });
 1331
 1332    assert_eq!('🟥'.len_utf8(), 4);
 1333    assert_eq!('α'.len_utf8(), 2);
 1334
 1335    _ = editor.update(cx, |editor, window, cx| {
 1336        editor.fold_creases(
 1337            vec![
 1338                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1339                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1340                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1341            ],
 1342            true,
 1343            window,
 1344            cx,
 1345        );
 1346        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1347
 1348        editor.move_right(&MoveRight, window, cx);
 1349        assert_eq!(
 1350            editor.selections.display_ranges(cx),
 1351            &[empty_range(0, "🟥".len())]
 1352        );
 1353        editor.move_right(&MoveRight, window, cx);
 1354        assert_eq!(
 1355            editor.selections.display_ranges(cx),
 1356            &[empty_range(0, "🟥🟧".len())]
 1357        );
 1358        editor.move_right(&MoveRight, window, cx);
 1359        assert_eq!(
 1360            editor.selections.display_ranges(cx),
 1361            &[empty_range(0, "🟥🟧⋯".len())]
 1362        );
 1363
 1364        editor.move_down(&MoveDown, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(1, "ab⋯e".len())]
 1368        );
 1369        editor.move_left(&MoveLeft, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(1, "ab⋯".len())]
 1373        );
 1374        editor.move_left(&MoveLeft, window, cx);
 1375        assert_eq!(
 1376            editor.selections.display_ranges(cx),
 1377            &[empty_range(1, "ab".len())]
 1378        );
 1379        editor.move_left(&MoveLeft, window, cx);
 1380        assert_eq!(
 1381            editor.selections.display_ranges(cx),
 1382            &[empty_range(1, "a".len())]
 1383        );
 1384
 1385        editor.move_down(&MoveDown, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(2, "α".len())]
 1389        );
 1390        editor.move_right(&MoveRight, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(2, "αβ".len())]
 1394        );
 1395        editor.move_right(&MoveRight, window, cx);
 1396        assert_eq!(
 1397            editor.selections.display_ranges(cx),
 1398            &[empty_range(2, "αβ⋯".len())]
 1399        );
 1400        editor.move_right(&MoveRight, window, cx);
 1401        assert_eq!(
 1402            editor.selections.display_ranges(cx),
 1403            &[empty_range(2, "αβ⋯ε".len())]
 1404        );
 1405
 1406        editor.move_up(&MoveUp, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(1, "ab⋯e".len())]
 1410        );
 1411        editor.move_down(&MoveDown, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416        editor.move_up(&MoveUp, window, cx);
 1417        assert_eq!(
 1418            editor.selections.display_ranges(cx),
 1419            &[empty_range(1, "ab⋯e".len())]
 1420        );
 1421
 1422        editor.move_up(&MoveUp, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(0, "🟥🟧".len())]
 1426        );
 1427        editor.move_left(&MoveLeft, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(0, "🟥".len())]
 1431        );
 1432        editor.move_left(&MoveLeft, window, cx);
 1433        assert_eq!(
 1434            editor.selections.display_ranges(cx),
 1435            &[empty_range(0, "".len())]
 1436        );
 1437    });
 1438}
 1439
 1440#[gpui::test]
 1441fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1442    init_test(cx, |_| {});
 1443
 1444    let editor = cx.add_window(|window, cx| {
 1445        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1446        build_editor(buffer.clone(), window, cx)
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        editor.change_selections(None, window, cx, |s| {
 1450            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1451        });
 1452
 1453        // moving above start of document should move selection to start of document,
 1454        // but the next move down should still be at the original goal_x
 1455        editor.move_up(&MoveUp, window, cx);
 1456        assert_eq!(
 1457            editor.selections.display_ranges(cx),
 1458            &[empty_range(0, "".len())]
 1459        );
 1460
 1461        editor.move_down(&MoveDown, window, cx);
 1462        assert_eq!(
 1463            editor.selections.display_ranges(cx),
 1464            &[empty_range(1, "abcd".len())]
 1465        );
 1466
 1467        editor.move_down(&MoveDown, window, cx);
 1468        assert_eq!(
 1469            editor.selections.display_ranges(cx),
 1470            &[empty_range(2, "αβγ".len())]
 1471        );
 1472
 1473        editor.move_down(&MoveDown, window, cx);
 1474        assert_eq!(
 1475            editor.selections.display_ranges(cx),
 1476            &[empty_range(3, "abcd".len())]
 1477        );
 1478
 1479        editor.move_down(&MoveDown, window, cx);
 1480        assert_eq!(
 1481            editor.selections.display_ranges(cx),
 1482            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1483        );
 1484
 1485        // moving past end of document should not change goal_x
 1486        editor.move_down(&MoveDown, window, cx);
 1487        assert_eq!(
 1488            editor.selections.display_ranges(cx),
 1489            &[empty_range(5, "".len())]
 1490        );
 1491
 1492        editor.move_down(&MoveDown, window, cx);
 1493        assert_eq!(
 1494            editor.selections.display_ranges(cx),
 1495            &[empty_range(5, "".len())]
 1496        );
 1497
 1498        editor.move_up(&MoveUp, window, cx);
 1499        assert_eq!(
 1500            editor.selections.display_ranges(cx),
 1501            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1502        );
 1503
 1504        editor.move_up(&MoveUp, window, cx);
 1505        assert_eq!(
 1506            editor.selections.display_ranges(cx),
 1507            &[empty_range(3, "abcd".len())]
 1508        );
 1509
 1510        editor.move_up(&MoveUp, window, cx);
 1511        assert_eq!(
 1512            editor.selections.display_ranges(cx),
 1513            &[empty_range(2, "αβγ".len())]
 1514        );
 1515    });
 1516}
 1517
 1518#[gpui::test]
 1519fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1520    init_test(cx, |_| {});
 1521    let move_to_beg = MoveToBeginningOfLine {
 1522        stop_at_soft_wraps: true,
 1523        stop_at_indent: true,
 1524    };
 1525
 1526    let delete_to_beg = DeleteToBeginningOfLine {
 1527        stop_at_indent: false,
 1528    };
 1529
 1530    let move_to_end = MoveToEndOfLine {
 1531        stop_at_soft_wraps: true,
 1532    };
 1533
 1534    let editor = cx.add_window(|window, cx| {
 1535        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1536        build_editor(buffer, window, cx)
 1537    });
 1538    _ = editor.update(cx, |editor, window, cx| {
 1539        editor.change_selections(None, window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1542                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1543            ]);
 1544        });
 1545    });
 1546
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1549        assert_eq!(
 1550            editor.selections.display_ranges(cx),
 1551            &[
 1552                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1553                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1554            ]
 1555        );
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_end_of_line(&move_to_end, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1586                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1587            ]
 1588        );
 1589    });
 1590
 1591    // Moving to the end of line again is a no-op.
 1592    _ = editor.update(cx, |editor, window, cx| {
 1593        editor.move_to_end_of_line(&move_to_end, window, cx);
 1594        assert_eq!(
 1595            editor.selections.display_ranges(cx),
 1596            &[
 1597                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1598                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1599            ]
 1600        );
 1601    });
 1602
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_left(&MoveLeft, window, cx);
 1605        editor.select_to_beginning_of_line(
 1606            &SelectToBeginningOfLine {
 1607                stop_at_soft_wraps: true,
 1608                stop_at_indent: true,
 1609            },
 1610            window,
 1611            cx,
 1612        );
 1613        assert_eq!(
 1614            editor.selections.display_ranges(cx),
 1615            &[
 1616                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1617                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1618            ]
 1619        );
 1620    });
 1621
 1622    _ = editor.update(cx, |editor, window, cx| {
 1623        editor.select_to_beginning_of_line(
 1624            &SelectToBeginningOfLine {
 1625                stop_at_soft_wraps: true,
 1626                stop_at_indent: true,
 1627            },
 1628            window,
 1629            cx,
 1630        );
 1631        assert_eq!(
 1632            editor.selections.display_ranges(cx),
 1633            &[
 1634                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1635                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1636            ]
 1637        );
 1638    });
 1639
 1640    _ = editor.update(cx, |editor, window, cx| {
 1641        editor.select_to_beginning_of_line(
 1642            &SelectToBeginningOfLine {
 1643                stop_at_soft_wraps: true,
 1644                stop_at_indent: true,
 1645            },
 1646            window,
 1647            cx,
 1648        );
 1649        assert_eq!(
 1650            editor.selections.display_ranges(cx),
 1651            &[
 1652                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1653                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1654            ]
 1655        );
 1656    });
 1657
 1658    _ = editor.update(cx, |editor, window, cx| {
 1659        editor.select_to_end_of_line(
 1660            &SelectToEndOfLine {
 1661                stop_at_soft_wraps: true,
 1662            },
 1663            window,
 1664            cx,
 1665        );
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[
 1669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1670                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1671            ]
 1672        );
 1673    });
 1674
 1675    _ = editor.update(cx, |editor, window, cx| {
 1676        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1677        assert_eq!(editor.display_text(cx), "ab\n  de");
 1678        assert_eq!(
 1679            editor.selections.display_ranges(cx),
 1680            &[
 1681                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1682                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1683            ]
 1684        );
 1685    });
 1686
 1687    _ = editor.update(cx, |editor, window, cx| {
 1688        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1689        assert_eq!(editor.display_text(cx), "\n");
 1690        assert_eq!(
 1691            editor.selections.display_ranges(cx),
 1692            &[
 1693                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1694                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1695            ]
 1696        );
 1697    });
 1698}
 1699
 1700#[gpui::test]
 1701fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1702    init_test(cx, |_| {});
 1703    let move_to_beg = MoveToBeginningOfLine {
 1704        stop_at_soft_wraps: false,
 1705        stop_at_indent: false,
 1706    };
 1707
 1708    let move_to_end = MoveToEndOfLine {
 1709        stop_at_soft_wraps: false,
 1710    };
 1711
 1712    let editor = cx.add_window(|window, cx| {
 1713        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1714        build_editor(buffer, window, cx)
 1715    });
 1716
 1717    _ = editor.update(cx, |editor, window, cx| {
 1718        editor.set_wrap_width(Some(140.0.into()), cx);
 1719
 1720        // We expect the following lines after wrapping
 1721        // ```
 1722        // thequickbrownfox
 1723        // jumpedoverthelazydo
 1724        // gs
 1725        // ```
 1726        // The final `gs` was soft-wrapped onto a new line.
 1727        assert_eq!(
 1728            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1729            editor.display_text(cx),
 1730        );
 1731
 1732        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1733        // Start the cursor at the `k` on the first line
 1734        editor.change_selections(None, window, cx, |s| {
 1735            s.select_display_ranges([
 1736                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1737            ]);
 1738        });
 1739
 1740        // Moving to the beginning of the line should put us at the beginning of the line.
 1741        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1742        assert_eq!(
 1743            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1744            editor.selections.display_ranges(cx)
 1745        );
 1746
 1747        // Moving to the end of the line should put us at the end of the line.
 1748        editor.move_to_end_of_line(&move_to_end, window, cx);
 1749        assert_eq!(
 1750            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1751            editor.selections.display_ranges(cx)
 1752        );
 1753
 1754        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1755        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1756        editor.change_selections(None, window, cx, |s| {
 1757            s.select_display_ranges([
 1758                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1759            ]);
 1760        });
 1761
 1762        // Moving to the beginning of the line should put us at the start of the second line of
 1763        // display text, i.e., the `j`.
 1764        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1765        assert_eq!(
 1766            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1767            editor.selections.display_ranges(cx)
 1768        );
 1769
 1770        // Moving to the beginning of the line again should be a no-op.
 1771        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1772        assert_eq!(
 1773            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1774            editor.selections.display_ranges(cx)
 1775        );
 1776
 1777        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1778        // next display line.
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1782            editor.selections.display_ranges(cx)
 1783        );
 1784
 1785        // Moving to the end of the line again should be a no-op.
 1786        editor.move_to_end_of_line(&move_to_end, window, cx);
 1787        assert_eq!(
 1788            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1789            editor.selections.display_ranges(cx)
 1790        );
 1791    });
 1792}
 1793
 1794#[gpui::test]
 1795fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1796    init_test(cx, |_| {});
 1797
 1798    let move_to_beg = MoveToBeginningOfLine {
 1799        stop_at_soft_wraps: true,
 1800        stop_at_indent: true,
 1801    };
 1802
 1803    let select_to_beg = SelectToBeginningOfLine {
 1804        stop_at_soft_wraps: true,
 1805        stop_at_indent: true,
 1806    };
 1807
 1808    let delete_to_beg = DeleteToBeginningOfLine {
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let move_to_end = MoveToEndOfLine {
 1813        stop_at_soft_wraps: false,
 1814    };
 1815
 1816    let editor = cx.add_window(|window, cx| {
 1817        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1818        build_editor(buffer, window, cx)
 1819    });
 1820
 1821    _ = editor.update(cx, |editor, window, cx| {
 1822        editor.change_selections(None, window, cx, |s| {
 1823            s.select_display_ranges([
 1824                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1825                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1826            ]);
 1827        });
 1828
 1829        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1830        // and the second cursor at the first non-whitespace character in the line.
 1831        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1832        assert_eq!(
 1833            editor.selections.display_ranges(cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1836                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1837            ]
 1838        );
 1839
 1840        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1841        // and should move the second cursor to the beginning of the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1852        // and should move the second cursor back to the first non-whitespace character in the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1859            ]
 1860        );
 1861
 1862        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1863        // and to the first non-whitespace character in the line for the second cursor.
 1864        editor.move_to_end_of_line(&move_to_end, window, cx);
 1865        editor.move_left(&MoveLeft, window, cx);
 1866        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1867        assert_eq!(
 1868            editor.selections.display_ranges(cx),
 1869            &[
 1870                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1871                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1872            ]
 1873        );
 1874
 1875        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1876        // and should select to the beginning of the line for the second cursor.
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1883            ]
 1884        );
 1885
 1886        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1887        // and should delete to the first non-whitespace character in the line for the second cursor.
 1888        editor.move_to_end_of_line(&move_to_end, window, cx);
 1889        editor.move_left(&MoveLeft, window, cx);
 1890        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1891        assert_eq!(editor.text(cx), "c\n  f");
 1892    });
 1893}
 1894
 1895#[gpui::test]
 1896fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1897    init_test(cx, |_| {});
 1898
 1899    let editor = cx.add_window(|window, cx| {
 1900        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1901        build_editor(buffer, window, cx)
 1902    });
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.change_selections(None, window, cx, |s| {
 1905            s.select_display_ranges([
 1906                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1907                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1908            ])
 1909        });
 1910        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1911        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1912
 1913        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1914        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1915
 1916        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1917        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1918
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1926        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1929        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1932        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_right(&MoveRight, window, cx);
 1935        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1936        assert_selection_ranges(
 1937            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1938            editor,
 1939            cx,
 1940        );
 1941
 1942        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1943        assert_selection_ranges(
 1944            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1945            editor,
 1946            cx,
 1947        );
 1948
 1949        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1950        assert_selection_ranges(
 1951            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1952            editor,
 1953            cx,
 1954        );
 1955    });
 1956}
 1957
 1958#[gpui::test]
 1959fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1960    init_test(cx, |_| {});
 1961
 1962    let editor = cx.add_window(|window, cx| {
 1963        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1964        build_editor(buffer, window, cx)
 1965    });
 1966
 1967    _ = editor.update(cx, |editor, window, cx| {
 1968        editor.set_wrap_width(Some(140.0.into()), cx);
 1969        assert_eq!(
 1970            editor.display_text(cx),
 1971            "use one::{\n    two::three::\n    four::five\n};"
 1972        );
 1973
 1974        editor.change_selections(None, window, cx, |s| {
 1975            s.select_display_ranges([
 1976                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1977            ]);
 1978        });
 1979
 1980        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1981        assert_eq!(
 1982            editor.selections.display_ranges(cx),
 1983            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1984        );
 1985
 1986        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1987        assert_eq!(
 1988            editor.selections.display_ranges(cx),
 1989            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1990        );
 1991
 1992        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1996        );
 1997
 1998        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1999        assert_eq!(
 2000            editor.selections.display_ranges(cx),
 2001            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2002        );
 2003
 2004        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2005        assert_eq!(
 2006            editor.selections.display_ranges(cx),
 2007            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2008        );
 2009
 2010        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2011        assert_eq!(
 2012            editor.selections.display_ranges(cx),
 2013            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2014        );
 2015    });
 2016}
 2017
 2018#[gpui::test]
 2019async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2020    init_test(cx, |_| {});
 2021    let mut cx = EditorTestContext::new(cx).await;
 2022
 2023    let line_height = cx.editor(|editor, window, _| {
 2024        editor
 2025            .style()
 2026            .unwrap()
 2027            .text
 2028            .line_height_in_pixels(window.rem_size())
 2029    });
 2030    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2031
 2032    cx.set_state(
 2033        &r#"ˇone
 2034        two
 2035
 2036        three
 2037        fourˇ
 2038        five
 2039
 2040        six"#
 2041            .unindent(),
 2042    );
 2043
 2044    cx.update_editor(|editor, window, cx| {
 2045        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2046    });
 2047    cx.assert_editor_state(
 2048        &r#"one
 2049        two
 2050        ˇ
 2051        three
 2052        four
 2053        five
 2054        ˇ
 2055        six"#
 2056            .unindent(),
 2057    );
 2058
 2059    cx.update_editor(|editor, window, cx| {
 2060        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2061    });
 2062    cx.assert_editor_state(
 2063        &r#"one
 2064        two
 2065
 2066        three
 2067        four
 2068        five
 2069        ˇ
 2070        sixˇ"#
 2071            .unindent(),
 2072    );
 2073
 2074    cx.update_editor(|editor, window, cx| {
 2075        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2076    });
 2077    cx.assert_editor_state(
 2078        &r#"one
 2079        two
 2080
 2081        three
 2082        four
 2083        five
 2084
 2085        sixˇ"#
 2086            .unindent(),
 2087    );
 2088
 2089    cx.update_editor(|editor, window, cx| {
 2090        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2091    });
 2092    cx.assert_editor_state(
 2093        &r#"one
 2094        two
 2095
 2096        three
 2097        four
 2098        five
 2099        ˇ
 2100        six"#
 2101            .unindent(),
 2102    );
 2103
 2104    cx.update_editor(|editor, window, cx| {
 2105        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2106    });
 2107    cx.assert_editor_state(
 2108        &r#"one
 2109        two
 2110        ˇ
 2111        three
 2112        four
 2113        five
 2114
 2115        six"#
 2116            .unindent(),
 2117    );
 2118
 2119    cx.update_editor(|editor, window, cx| {
 2120        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2121    });
 2122    cx.assert_editor_state(
 2123        &r#"ˇone
 2124        two
 2125
 2126        three
 2127        four
 2128        five
 2129
 2130        six"#
 2131            .unindent(),
 2132    );
 2133}
 2134
 2135#[gpui::test]
 2136async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2137    init_test(cx, |_| {});
 2138    let mut cx = EditorTestContext::new(cx).await;
 2139    let line_height = cx.editor(|editor, window, _| {
 2140        editor
 2141            .style()
 2142            .unwrap()
 2143            .text
 2144            .line_height_in_pixels(window.rem_size())
 2145    });
 2146    let window = cx.window;
 2147    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2148
 2149    cx.set_state(
 2150        r#"ˇone
 2151        two
 2152        three
 2153        four
 2154        five
 2155        six
 2156        seven
 2157        eight
 2158        nine
 2159        ten
 2160        "#,
 2161    );
 2162
 2163    cx.update_editor(|editor, window, cx| {
 2164        assert_eq!(
 2165            editor.snapshot(window, cx).scroll_position(),
 2166            gpui::Point::new(0., 0.)
 2167        );
 2168        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2169        assert_eq!(
 2170            editor.snapshot(window, cx).scroll_position(),
 2171            gpui::Point::new(0., 3.)
 2172        );
 2173        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2174        assert_eq!(
 2175            editor.snapshot(window, cx).scroll_position(),
 2176            gpui::Point::new(0., 6.)
 2177        );
 2178        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2179        assert_eq!(
 2180            editor.snapshot(window, cx).scroll_position(),
 2181            gpui::Point::new(0., 3.)
 2182        );
 2183
 2184        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2185        assert_eq!(
 2186            editor.snapshot(window, cx).scroll_position(),
 2187            gpui::Point::new(0., 1.)
 2188        );
 2189        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2190        assert_eq!(
 2191            editor.snapshot(window, cx).scroll_position(),
 2192            gpui::Point::new(0., 3.)
 2193        );
 2194    });
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_autoscroll(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201
 2202    let line_height = cx.update_editor(|editor, window, cx| {
 2203        editor.set_vertical_scroll_margin(2, cx);
 2204        editor
 2205            .style()
 2206            .unwrap()
 2207            .text
 2208            .line_height_in_pixels(window.rem_size())
 2209    });
 2210    let window = cx.window;
 2211    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2212
 2213    cx.set_state(
 2214        r#"ˇone
 2215            two
 2216            three
 2217            four
 2218            five
 2219            six
 2220            seven
 2221            eight
 2222            nine
 2223            ten
 2224        "#,
 2225    );
 2226    cx.update_editor(|editor, window, cx| {
 2227        assert_eq!(
 2228            editor.snapshot(window, cx).scroll_position(),
 2229            gpui::Point::new(0., 0.0)
 2230        );
 2231    });
 2232
 2233    // Add a cursor below the visible area. Since both cursors cannot fit
 2234    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2235    // allows the vertical scroll margin below that cursor.
 2236    cx.update_editor(|editor, window, cx| {
 2237        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2238            selections.select_ranges([
 2239                Point::new(0, 0)..Point::new(0, 0),
 2240                Point::new(6, 0)..Point::new(6, 0),
 2241            ]);
 2242        })
 2243    });
 2244    cx.update_editor(|editor, window, cx| {
 2245        assert_eq!(
 2246            editor.snapshot(window, cx).scroll_position(),
 2247            gpui::Point::new(0., 3.0)
 2248        );
 2249    });
 2250
 2251    // Move down. The editor cursor scrolls down to track the newest cursor.
 2252    cx.update_editor(|editor, window, cx| {
 2253        editor.move_down(&Default::default(), window, cx);
 2254    });
 2255    cx.update_editor(|editor, window, cx| {
 2256        assert_eq!(
 2257            editor.snapshot(window, cx).scroll_position(),
 2258            gpui::Point::new(0., 4.0)
 2259        );
 2260    });
 2261
 2262    // Add a cursor above the visible area. Since both cursors fit on screen,
 2263    // the editor scrolls to show both.
 2264    cx.update_editor(|editor, window, cx| {
 2265        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2266            selections.select_ranges([
 2267                Point::new(1, 0)..Point::new(1, 0),
 2268                Point::new(6, 0)..Point::new(6, 0),
 2269            ]);
 2270        })
 2271    });
 2272    cx.update_editor(|editor, window, cx| {
 2273        assert_eq!(
 2274            editor.snapshot(window, cx).scroll_position(),
 2275            gpui::Point::new(0., 1.0)
 2276        );
 2277    });
 2278}
 2279
 2280#[gpui::test]
 2281async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2282    init_test(cx, |_| {});
 2283    let mut cx = EditorTestContext::new(cx).await;
 2284
 2285    let line_height = cx.editor(|editor, window, _cx| {
 2286        editor
 2287            .style()
 2288            .unwrap()
 2289            .text
 2290            .line_height_in_pixels(window.rem_size())
 2291    });
 2292    let window = cx.window;
 2293    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2294    cx.set_state(
 2295        &r#"
 2296        ˇone
 2297        two
 2298        threeˇ
 2299        four
 2300        five
 2301        six
 2302        seven
 2303        eight
 2304        nine
 2305        ten
 2306        "#
 2307        .unindent(),
 2308    );
 2309
 2310    cx.update_editor(|editor, window, cx| {
 2311        editor.move_page_down(&MovePageDown::default(), window, cx)
 2312    });
 2313    cx.assert_editor_state(
 2314        &r#"
 2315        one
 2316        two
 2317        three
 2318        ˇfour
 2319        five
 2320        sixˇ
 2321        seven
 2322        eight
 2323        nine
 2324        ten
 2325        "#
 2326        .unindent(),
 2327    );
 2328
 2329    cx.update_editor(|editor, window, cx| {
 2330        editor.move_page_down(&MovePageDown::default(), window, cx)
 2331    });
 2332    cx.assert_editor_state(
 2333        &r#"
 2334        one
 2335        two
 2336        three
 2337        four
 2338        five
 2339        six
 2340        ˇseven
 2341        eight
 2342        nineˇ
 2343        ten
 2344        "#
 2345        .unindent(),
 2346    );
 2347
 2348    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2349    cx.assert_editor_state(
 2350        &r#"
 2351        one
 2352        two
 2353        three
 2354        ˇfour
 2355        five
 2356        sixˇ
 2357        seven
 2358        eight
 2359        nine
 2360        ten
 2361        "#
 2362        .unindent(),
 2363    );
 2364
 2365    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2366    cx.assert_editor_state(
 2367        &r#"
 2368        ˇone
 2369        two
 2370        threeˇ
 2371        four
 2372        five
 2373        six
 2374        seven
 2375        eight
 2376        nine
 2377        ten
 2378        "#
 2379        .unindent(),
 2380    );
 2381
 2382    // Test select collapsing
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386        editor.move_page_down(&MovePageDown::default(), window, cx);
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        seven
 2397        eight
 2398        nine
 2399        ˇten
 2400        ˇ"#
 2401        .unindent(),
 2402    );
 2403}
 2404
 2405#[gpui::test]
 2406async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2407    init_test(cx, |_| {});
 2408    let mut cx = EditorTestContext::new(cx).await;
 2409    cx.set_state("one «two threeˇ» four");
 2410    cx.update_editor(|editor, window, cx| {
 2411        editor.delete_to_beginning_of_line(
 2412            &DeleteToBeginningOfLine {
 2413                stop_at_indent: false,
 2414            },
 2415            window,
 2416            cx,
 2417        );
 2418        assert_eq!(editor.text(cx), " four");
 2419    });
 2420}
 2421
 2422#[gpui::test]
 2423fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2424    init_test(cx, |_| {});
 2425
 2426    let editor = cx.add_window(|window, cx| {
 2427        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2428        build_editor(buffer.clone(), window, cx)
 2429    });
 2430
 2431    _ = editor.update(cx, |editor, window, cx| {
 2432        editor.change_selections(None, window, cx, |s| {
 2433            s.select_display_ranges([
 2434                // an empty selection - the preceding word fragment is deleted
 2435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2436                // characters selected - they are deleted
 2437                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2438            ])
 2439        });
 2440        editor.delete_to_previous_word_start(
 2441            &DeleteToPreviousWordStart {
 2442                ignore_newlines: false,
 2443            },
 2444            window,
 2445            cx,
 2446        );
 2447        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2448    });
 2449
 2450    _ = editor.update(cx, |editor, window, cx| {
 2451        editor.change_selections(None, window, cx, |s| {
 2452            s.select_display_ranges([
 2453                // an empty selection - the following word fragment is deleted
 2454                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2455                // characters selected - they are deleted
 2456                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2457            ])
 2458        });
 2459        editor.delete_to_next_word_end(
 2460            &DeleteToNextWordEnd {
 2461                ignore_newlines: false,
 2462            },
 2463            window,
 2464            cx,
 2465        );
 2466        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2467    });
 2468}
 2469
 2470#[gpui::test]
 2471fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2472    init_test(cx, |_| {});
 2473
 2474    let editor = cx.add_window(|window, cx| {
 2475        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2476        build_editor(buffer.clone(), window, cx)
 2477    });
 2478    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2479        ignore_newlines: false,
 2480    };
 2481    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2482        ignore_newlines: true,
 2483    };
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(None, window, cx, |s| {
 2487            s.select_display_ranges([
 2488                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2489            ])
 2490        });
 2491        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2492        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2493        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2494        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2495        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2496        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2497        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2498        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2499        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2500        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2501        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2502        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2503    });
 2504}
 2505
 2506#[gpui::test]
 2507fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2508    init_test(cx, |_| {});
 2509
 2510    let editor = cx.add_window(|window, cx| {
 2511        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2512        build_editor(buffer.clone(), window, cx)
 2513    });
 2514    let del_to_next_word_end = DeleteToNextWordEnd {
 2515        ignore_newlines: false,
 2516    };
 2517    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2518        ignore_newlines: true,
 2519    };
 2520
 2521    _ = editor.update(cx, |editor, window, cx| {
 2522        editor.change_selections(None, window, cx, |s| {
 2523            s.select_display_ranges([
 2524                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2525            ])
 2526        });
 2527        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2528        assert_eq!(
 2529            editor.buffer.read(cx).read(cx).text(),
 2530            "one\n   two\nthree\n   four"
 2531        );
 2532        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2533        assert_eq!(
 2534            editor.buffer.read(cx).read(cx).text(),
 2535            "\n   two\nthree\n   four"
 2536        );
 2537        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2538        assert_eq!(
 2539            editor.buffer.read(cx).read(cx).text(),
 2540            "two\nthree\n   four"
 2541        );
 2542        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2543        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2544        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2545        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2546        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2547        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2548    });
 2549}
 2550
 2551#[gpui::test]
 2552fn test_newline(cx: &mut TestAppContext) {
 2553    init_test(cx, |_| {});
 2554
 2555    let editor = cx.add_window(|window, cx| {
 2556        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2557        build_editor(buffer.clone(), window, cx)
 2558    });
 2559
 2560    _ = editor.update(cx, |editor, window, cx| {
 2561        editor.change_selections(None, window, cx, |s| {
 2562            s.select_display_ranges([
 2563                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2565                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2566            ])
 2567        });
 2568
 2569        editor.newline(&Newline, window, cx);
 2570        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2571    });
 2572}
 2573
 2574#[gpui::test]
 2575fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2576    init_test(cx, |_| {});
 2577
 2578    let editor = cx.add_window(|window, cx| {
 2579        let buffer = MultiBuffer::build_simple(
 2580            "
 2581                a
 2582                b(
 2583                    X
 2584                )
 2585                c(
 2586                    X
 2587                )
 2588            "
 2589            .unindent()
 2590            .as_str(),
 2591            cx,
 2592        );
 2593        let mut editor = build_editor(buffer.clone(), window, cx);
 2594        editor.change_selections(None, window, cx, |s| {
 2595            s.select_ranges([
 2596                Point::new(2, 4)..Point::new(2, 5),
 2597                Point::new(5, 4)..Point::new(5, 5),
 2598            ])
 2599        });
 2600        editor
 2601    });
 2602
 2603    _ = editor.update(cx, |editor, window, cx| {
 2604        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2605        editor.buffer.update(cx, |buffer, cx| {
 2606            buffer.edit(
 2607                [
 2608                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2609                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2610                ],
 2611                None,
 2612                cx,
 2613            );
 2614            assert_eq!(
 2615                buffer.read(cx).text(),
 2616                "
 2617                    a
 2618                    b()
 2619                    c()
 2620                "
 2621                .unindent()
 2622            );
 2623        });
 2624        assert_eq!(
 2625            editor.selections.ranges(cx),
 2626            &[
 2627                Point::new(1, 2)..Point::new(1, 2),
 2628                Point::new(2, 2)..Point::new(2, 2),
 2629            ],
 2630        );
 2631
 2632        editor.newline(&Newline, window, cx);
 2633        assert_eq!(
 2634            editor.text(cx),
 2635            "
 2636                a
 2637                b(
 2638                )
 2639                c(
 2640                )
 2641            "
 2642            .unindent()
 2643        );
 2644
 2645        // The selections are moved after the inserted newlines
 2646        assert_eq!(
 2647            editor.selections.ranges(cx),
 2648            &[
 2649                Point::new(2, 0)..Point::new(2, 0),
 2650                Point::new(4, 0)..Point::new(4, 0),
 2651            ],
 2652        );
 2653    });
 2654}
 2655
 2656#[gpui::test]
 2657async fn test_newline_above(cx: &mut TestAppContext) {
 2658    init_test(cx, |settings| {
 2659        settings.defaults.tab_size = NonZeroU32::new(4)
 2660    });
 2661
 2662    let language = Arc::new(
 2663        Language::new(
 2664            LanguageConfig::default(),
 2665            Some(tree_sitter_rust::LANGUAGE.into()),
 2666        )
 2667        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2668        .unwrap(),
 2669    );
 2670
 2671    let mut cx = EditorTestContext::new(cx).await;
 2672    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2673    cx.set_state(indoc! {"
 2674        const a: ˇA = (
 2675 2676                «const_functionˇ»(ˇ),
 2677                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2678 2679        ˇ);ˇ
 2680    "});
 2681
 2682    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2683    cx.assert_editor_state(indoc! {"
 2684        ˇ
 2685        const a: A = (
 2686            ˇ
 2687            (
 2688                ˇ
 2689                ˇ
 2690                const_function(),
 2691                ˇ
 2692                ˇ
 2693                ˇ
 2694                ˇ
 2695                something_else,
 2696                ˇ
 2697            )
 2698            ˇ
 2699            ˇ
 2700        );
 2701    "});
 2702}
 2703
 2704#[gpui::test]
 2705async fn test_newline_below(cx: &mut TestAppContext) {
 2706    init_test(cx, |settings| {
 2707        settings.defaults.tab_size = NonZeroU32::new(4)
 2708    });
 2709
 2710    let language = Arc::new(
 2711        Language::new(
 2712            LanguageConfig::default(),
 2713            Some(tree_sitter_rust::LANGUAGE.into()),
 2714        )
 2715        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2716        .unwrap(),
 2717    );
 2718
 2719    let mut cx = EditorTestContext::new(cx).await;
 2720    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2721    cx.set_state(indoc! {"
 2722        const a: ˇA = (
 2723 2724                «const_functionˇ»(ˇ),
 2725                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2726 2727        ˇ);ˇ
 2728    "});
 2729
 2730    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2731    cx.assert_editor_state(indoc! {"
 2732        const a: A = (
 2733            ˇ
 2734            (
 2735                ˇ
 2736                const_function(),
 2737                ˇ
 2738                ˇ
 2739                something_else,
 2740                ˇ
 2741                ˇ
 2742                ˇ
 2743                ˇ
 2744            )
 2745            ˇ
 2746        );
 2747        ˇ
 2748        ˇ
 2749    "});
 2750}
 2751
 2752#[gpui::test]
 2753async fn test_newline_comments(cx: &mut TestAppContext) {
 2754    init_test(cx, |settings| {
 2755        settings.defaults.tab_size = NonZeroU32::new(4)
 2756    });
 2757
 2758    let language = Arc::new(Language::new(
 2759        LanguageConfig {
 2760            line_comments: vec!["// ".into()],
 2761            ..LanguageConfig::default()
 2762        },
 2763        None,
 2764    ));
 2765    {
 2766        let mut cx = EditorTestContext::new(cx).await;
 2767        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2768        cx.set_state(indoc! {"
 2769        // Fooˇ
 2770    "});
 2771
 2772        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2773        cx.assert_editor_state(indoc! {"
 2774        // Foo
 2775        // ˇ
 2776    "});
 2777        // Ensure that we add comment prefix when existing line contains space
 2778        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2779        cx.assert_editor_state(
 2780            indoc! {"
 2781        // Foo
 2782        //s
 2783        // ˇ
 2784    "}
 2785            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2786            .as_str(),
 2787        );
 2788        // Ensure that we add comment prefix when existing line does not contain space
 2789        cx.set_state(indoc! {"
 2790        // Foo
 2791        //ˇ
 2792    "});
 2793        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2794        cx.assert_editor_state(indoc! {"
 2795        // Foo
 2796        //
 2797        // ˇ
 2798    "});
 2799        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2800        cx.set_state(indoc! {"
 2801        ˇ// Foo
 2802    "});
 2803        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2804        cx.assert_editor_state(indoc! {"
 2805
 2806        ˇ// Foo
 2807    "});
 2808    }
 2809    // Ensure that comment continuations can be disabled.
 2810    update_test_language_settings(cx, |settings| {
 2811        settings.defaults.extend_comment_on_newline = Some(false);
 2812    });
 2813    let mut cx = EditorTestContext::new(cx).await;
 2814    cx.set_state(indoc! {"
 2815        // Fooˇ
 2816    "});
 2817    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2818    cx.assert_editor_state(indoc! {"
 2819        // Foo
 2820        ˇ
 2821    "});
 2822}
 2823
 2824#[gpui::test]
 2825async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2826    init_test(cx, |settings| {
 2827        settings.defaults.tab_size = NonZeroU32::new(4)
 2828    });
 2829
 2830    let language = Arc::new(Language::new(
 2831        LanguageConfig {
 2832            line_comments: vec!["// ".into(), "/// ".into()],
 2833            ..LanguageConfig::default()
 2834        },
 2835        None,
 2836    ));
 2837    {
 2838        let mut cx = EditorTestContext::new(cx).await;
 2839        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2840        cx.set_state(indoc! {"
 2841        //ˇ
 2842    "});
 2843        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2844        cx.assert_editor_state(indoc! {"
 2845        //
 2846        // ˇ
 2847    "});
 2848
 2849        cx.set_state(indoc! {"
 2850        ///ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        ///
 2855        /// ˇ
 2856    "});
 2857    }
 2858}
 2859
 2860#[gpui::test]
 2861async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2862    init_test(cx, |settings| {
 2863        settings.defaults.tab_size = NonZeroU32::new(4)
 2864    });
 2865
 2866    let language = Arc::new(
 2867        Language::new(
 2868            LanguageConfig {
 2869                documentation: Some(language::DocumentationConfig {
 2870                    start: "/**".into(),
 2871                    end: "*/".into(),
 2872                    prefix: "* ".into(),
 2873                    tab_size: NonZeroU32::new(1).unwrap(),
 2874                }),
 2875
 2876                ..LanguageConfig::default()
 2877            },
 2878            Some(tree_sitter_rust::LANGUAGE.into()),
 2879        )
 2880        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2881        .unwrap(),
 2882    );
 2883
 2884    {
 2885        let mut cx = EditorTestContext::new(cx).await;
 2886        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2887        cx.set_state(indoc! {"
 2888        /**ˇ
 2889    "});
 2890
 2891        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2892        cx.assert_editor_state(indoc! {"
 2893        /**
 2894         * ˇ
 2895    "});
 2896        // Ensure that if cursor is before the comment start,
 2897        // we do not actually insert a comment prefix.
 2898        cx.set_state(indoc! {"
 2899        ˇ/**
 2900    "});
 2901        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2902        cx.assert_editor_state(indoc! {"
 2903
 2904        ˇ/**
 2905    "});
 2906        // Ensure that if cursor is between it doesn't add comment prefix.
 2907        cx.set_state(indoc! {"
 2908        /*ˇ*
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912        /*
 2913        ˇ*
 2914    "});
 2915        // Ensure that if suffix exists on same line after cursor it adds new line.
 2916        cx.set_state(indoc! {"
 2917        /**ˇ*/
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /**
 2922         * ˇ
 2923         */
 2924    "});
 2925        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2926        cx.set_state(indoc! {"
 2927        /**ˇ */
 2928    "});
 2929        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2930        cx.assert_editor_state(indoc! {"
 2931        /**
 2932         * ˇ
 2933         */
 2934    "});
 2935        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2936        cx.set_state(indoc! {"
 2937        /** ˇ*/
 2938    "});
 2939        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2940        cx.assert_editor_state(
 2941            indoc! {"
 2942        /**s
 2943         * ˇ
 2944         */
 2945    "}
 2946            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2947            .as_str(),
 2948        );
 2949        // Ensure that delimiter space is preserved when newline on already
 2950        // spaced delimiter.
 2951        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2952        cx.assert_editor_state(
 2953            indoc! {"
 2954        /**s
 2955         *s
 2956         * ˇ
 2957         */
 2958    "}
 2959            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2960            .as_str(),
 2961        );
 2962        // Ensure that delimiter space is preserved when space is not
 2963        // on existing delimiter.
 2964        cx.set_state(indoc! {"
 2965        /**
 2966 2967         */
 2968    "});
 2969        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2970        cx.assert_editor_state(indoc! {"
 2971        /**
 2972         *
 2973         * ˇ
 2974         */
 2975    "});
 2976        // Ensure that if suffix exists on same line after cursor it
 2977        // doesn't add extra new line if prefix is not on same line.
 2978        cx.set_state(indoc! {"
 2979        /**
 2980        ˇ*/
 2981    "});
 2982        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2983        cx.assert_editor_state(indoc! {"
 2984        /**
 2985
 2986        ˇ*/
 2987    "});
 2988        // Ensure that it detects suffix after existing prefix.
 2989        cx.set_state(indoc! {"
 2990        /**ˇ/
 2991    "});
 2992        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2993        cx.assert_editor_state(indoc! {"
 2994        /**
 2995        ˇ/
 2996    "});
 2997        // Ensure that if suffix exists on same line before
 2998        // cursor it does not add comment prefix.
 2999        cx.set_state(indoc! {"
 3000        /** */ˇ
 3001    "});
 3002        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3003        cx.assert_editor_state(indoc! {"
 3004        /** */
 3005        ˇ
 3006    "});
 3007        // Ensure that if suffix exists on same line before
 3008        // cursor it does not add comment prefix.
 3009        cx.set_state(indoc! {"
 3010        /**
 3011         *
 3012         */ˇ
 3013    "});
 3014        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3015        cx.assert_editor_state(indoc! {"
 3016        /**
 3017         *
 3018         */
 3019         ˇ
 3020    "});
 3021
 3022        // Ensure that inline comment followed by code
 3023        // doesn't add comment prefix on newline
 3024        cx.set_state(indoc! {"
 3025        /** */ textˇ
 3026    "});
 3027        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3028        cx.assert_editor_state(indoc! {"
 3029        /** */ text
 3030        ˇ
 3031    "});
 3032
 3033        // Ensure that text after comment end tag
 3034        // doesn't add comment prefix on newline
 3035        cx.set_state(indoc! {"
 3036        /**
 3037         *
 3038         */ˇtext
 3039    "});
 3040        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3041        cx.assert_editor_state(indoc! {"
 3042        /**
 3043         *
 3044         */
 3045         ˇtext
 3046    "});
 3047
 3048        // Ensure if not comment block it doesn't
 3049        // add comment prefix on newline
 3050        cx.set_state(indoc! {"
 3051        * textˇ
 3052    "});
 3053        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3054        cx.assert_editor_state(indoc! {"
 3055        * text
 3056        ˇ
 3057    "});
 3058    }
 3059    // Ensure that comment continuations can be disabled.
 3060    update_test_language_settings(cx, |settings| {
 3061        settings.defaults.extend_comment_on_newline = Some(false);
 3062    });
 3063    let mut cx = EditorTestContext::new(cx).await;
 3064    cx.set_state(indoc! {"
 3065        /**ˇ
 3066    "});
 3067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3068    cx.assert_editor_state(indoc! {"
 3069        /**
 3070        ˇ
 3071    "});
 3072}
 3073
 3074#[gpui::test]
 3075fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3076    init_test(cx, |_| {});
 3077
 3078    let editor = cx.add_window(|window, cx| {
 3079        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3080        let mut editor = build_editor(buffer.clone(), window, cx);
 3081        editor.change_selections(None, window, cx, |s| {
 3082            s.select_ranges([3..4, 11..12, 19..20])
 3083        });
 3084        editor
 3085    });
 3086
 3087    _ = editor.update(cx, |editor, window, cx| {
 3088        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3089        editor.buffer.update(cx, |buffer, cx| {
 3090            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3091            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3092        });
 3093        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3094
 3095        editor.insert("Z", window, cx);
 3096        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3097
 3098        // The selections are moved after the inserted characters
 3099        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3100    });
 3101}
 3102
 3103#[gpui::test]
 3104async fn test_tab(cx: &mut TestAppContext) {
 3105    init_test(cx, |settings| {
 3106        settings.defaults.tab_size = NonZeroU32::new(3)
 3107    });
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.set_state(indoc! {"
 3111        ˇabˇc
 3112        ˇ🏀ˇ🏀ˇefg
 3113 3114    "});
 3115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117           ˇab ˇc
 3118           ˇ🏀  ˇ🏀  ˇefg
 3119        d  ˇ
 3120    "});
 3121
 3122    cx.set_state(indoc! {"
 3123        a
 3124        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3125    "});
 3126    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        a
 3129           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3130    "});
 3131}
 3132
 3133#[gpui::test]
 3134async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3135    init_test(cx, |_| {});
 3136
 3137    let mut cx = EditorTestContext::new(cx).await;
 3138    let language = Arc::new(
 3139        Language::new(
 3140            LanguageConfig::default(),
 3141            Some(tree_sitter_rust::LANGUAGE.into()),
 3142        )
 3143        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3144        .unwrap(),
 3145    );
 3146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3147
 3148    // test when all cursors are not at suggested indent
 3149    // then simply move to their suggested indent location
 3150    cx.set_state(indoc! {"
 3151        const a: B = (
 3152            c(
 3153        ˇ
 3154        ˇ    )
 3155        );
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        const a: B = (
 3160            c(
 3161                ˇ
 3162            ˇ)
 3163        );
 3164    "});
 3165
 3166    // test cursor already at suggested indent not moving when
 3167    // other cursors are yet to reach their suggested indents
 3168    cx.set_state(indoc! {"
 3169        ˇ
 3170        const a: B = (
 3171            c(
 3172                d(
 3173        ˇ
 3174                )
 3175        ˇ
 3176        ˇ    )
 3177        );
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        ˇ
 3182        const a: B = (
 3183            c(
 3184                d(
 3185                    ˇ
 3186                )
 3187                ˇ
 3188            ˇ)
 3189        );
 3190    "});
 3191    // test when all cursors are at suggested indent then tab is inserted
 3192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194            ˇ
 3195        const a: B = (
 3196            c(
 3197                d(
 3198                        ˇ
 3199                )
 3200                    ˇ
 3201                ˇ)
 3202        );
 3203    "});
 3204
 3205    // test when current indent is less than suggested indent,
 3206    // we adjust line to match suggested indent and move cursor to it
 3207    //
 3208    // when no other cursor is at word boundary, all of them should move
 3209    cx.set_state(indoc! {"
 3210        const a: B = (
 3211            c(
 3212                d(
 3213        ˇ
 3214        ˇ   )
 3215        ˇ   )
 3216        );
 3217    "});
 3218    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3219    cx.assert_editor_state(indoc! {"
 3220        const a: B = (
 3221            c(
 3222                d(
 3223                    ˇ
 3224                ˇ)
 3225            ˇ)
 3226        );
 3227    "});
 3228
 3229    // test when current indent is less than suggested indent,
 3230    // we adjust line to match suggested indent and move cursor to it
 3231    //
 3232    // when some other cursor is at word boundary, it should not move
 3233    cx.set_state(indoc! {"
 3234        const a: B = (
 3235            c(
 3236                d(
 3237        ˇ
 3238        ˇ   )
 3239           ˇ)
 3240        );
 3241    "});
 3242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244        const a: B = (
 3245            c(
 3246                d(
 3247                    ˇ
 3248                ˇ)
 3249            ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is more than suggested indent,
 3254    // we just move cursor to current indent instead of suggested indent
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ                )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                        ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3277    cx.assert_editor_state(indoc! {"
 3278        const a: B = (
 3279            c(
 3280                d(
 3281                        ˇ
 3282                            ˇ)
 3283                ˇ)
 3284        );
 3285    "});
 3286
 3287    // test when current indent is more than suggested indent,
 3288    // we just move cursor to current indent instead of suggested indent
 3289    //
 3290    // when some other cursor is at word boundary, it doesn't move
 3291    cx.set_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295        ˇ
 3296        ˇ                )
 3297            ˇ)
 3298        );
 3299    "});
 3300    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3301    cx.assert_editor_state(indoc! {"
 3302        const a: B = (
 3303            c(
 3304                d(
 3305                    ˇ
 3306                        ˇ)
 3307            ˇ)
 3308        );
 3309    "});
 3310
 3311    // handle auto-indent when there are multiple cursors on the same line
 3312    cx.set_state(indoc! {"
 3313        const a: B = (
 3314            c(
 3315        ˇ    ˇ
 3316        ˇ    )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                ˇ
 3324            ˇ)
 3325        );
 3326    "});
 3327}
 3328
 3329#[gpui::test]
 3330async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3331    init_test(cx, |settings| {
 3332        settings.defaults.tab_size = NonZeroU32::new(3)
 3333    });
 3334
 3335    let mut cx = EditorTestContext::new(cx).await;
 3336    cx.set_state(indoc! {"
 3337         ˇ
 3338        \t ˇ
 3339        \t  ˇ
 3340        \t   ˇ
 3341         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3342    "});
 3343
 3344    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3345    cx.assert_editor_state(indoc! {"
 3346           ˇ
 3347        \t   ˇ
 3348        \t   ˇ
 3349        \t      ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3351    "});
 3352}
 3353
 3354#[gpui::test]
 3355async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3356    init_test(cx, |settings| {
 3357        settings.defaults.tab_size = NonZeroU32::new(4)
 3358    });
 3359
 3360    let language = Arc::new(
 3361        Language::new(
 3362            LanguageConfig::default(),
 3363            Some(tree_sitter_rust::LANGUAGE.into()),
 3364        )
 3365        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3366        .unwrap(),
 3367    );
 3368
 3369    let mut cx = EditorTestContext::new(cx).await;
 3370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3371    cx.set_state(indoc! {"
 3372        fn a() {
 3373            if b {
 3374        \t ˇc
 3375            }
 3376        }
 3377    "});
 3378
 3379    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3380    cx.assert_editor_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383                ˇc
 3384            }
 3385        }
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_indent_outdent(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4);
 3393    });
 3394
 3395    let mut cx = EditorTestContext::new(cx).await;
 3396
 3397    cx.set_state(indoc! {"
 3398          «oneˇ» «twoˇ»
 3399        three
 3400         four
 3401    "});
 3402    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3403    cx.assert_editor_state(indoc! {"
 3404            «oneˇ» «twoˇ»
 3405        three
 3406         four
 3407    "});
 3408
 3409    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3410    cx.assert_editor_state(indoc! {"
 3411        «oneˇ» «twoˇ»
 3412        three
 3413         four
 3414    "});
 3415
 3416    // select across line ending
 3417    cx.set_state(indoc! {"
 3418        one two
 3419        t«hree
 3420        ˇ» four
 3421    "});
 3422    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3423    cx.assert_editor_state(indoc! {"
 3424        one two
 3425            t«hree
 3426        ˇ» four
 3427    "});
 3428
 3429    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3430    cx.assert_editor_state(indoc! {"
 3431        one two
 3432        t«hree
 3433        ˇ» four
 3434    "});
 3435
 3436    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3437    cx.set_state(indoc! {"
 3438        one two
 3439        ˇthree
 3440            four
 3441    "});
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444        one two
 3445            ˇthree
 3446            four
 3447    "});
 3448
 3449    cx.set_state(indoc! {"
 3450        one two
 3451        ˇ    three
 3452            four
 3453    "});
 3454    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3455    cx.assert_editor_state(indoc! {"
 3456        one two
 3457        ˇthree
 3458            four
 3459    "});
 3460}
 3461
 3462#[gpui::test]
 3463async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3464    init_test(cx, |settings| {
 3465        settings.defaults.hard_tabs = Some(true);
 3466    });
 3467
 3468    let mut cx = EditorTestContext::new(cx).await;
 3469
 3470    // select two ranges on one line
 3471    cx.set_state(indoc! {"
 3472        «oneˇ» «twoˇ»
 3473        three
 3474        four
 3475    "});
 3476    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3477    cx.assert_editor_state(indoc! {"
 3478        \t«oneˇ» «twoˇ»
 3479        three
 3480        four
 3481    "});
 3482    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        \t\t«oneˇ» «twoˇ»
 3485        three
 3486        four
 3487    "});
 3488    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3489    cx.assert_editor_state(indoc! {"
 3490        \t«oneˇ» «twoˇ»
 3491        three
 3492        four
 3493    "});
 3494    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3495    cx.assert_editor_state(indoc! {"
 3496        «oneˇ» «twoˇ»
 3497        three
 3498        four
 3499    "});
 3500
 3501    // select across a line ending
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        t«hree
 3505        ˇ»four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        \tt«hree
 3511        ˇ»four
 3512    "});
 3513    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3514    cx.assert_editor_state(indoc! {"
 3515        one two
 3516        \t\tt«hree
 3517        ˇ»four
 3518    "});
 3519    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3520    cx.assert_editor_state(indoc! {"
 3521        one two
 3522        \tt«hree
 3523        ˇ»four
 3524    "});
 3525    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3526    cx.assert_editor_state(indoc! {"
 3527        one two
 3528        t«hree
 3529        ˇ»four
 3530    "});
 3531
 3532    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3533    cx.set_state(indoc! {"
 3534        one two
 3535        ˇthree
 3536        four
 3537    "});
 3538    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3539    cx.assert_editor_state(indoc! {"
 3540        one two
 3541        ˇthree
 3542        four
 3543    "});
 3544    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3545    cx.assert_editor_state(indoc! {"
 3546        one two
 3547        \tˇthree
 3548        four
 3549    "});
 3550    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3551    cx.assert_editor_state(indoc! {"
 3552        one two
 3553        ˇthree
 3554        four
 3555    "});
 3556}
 3557
 3558#[gpui::test]
 3559fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3560    init_test(cx, |settings| {
 3561        settings.languages.extend([
 3562            (
 3563                "TOML".into(),
 3564                LanguageSettingsContent {
 3565                    tab_size: NonZeroU32::new(2),
 3566                    ..Default::default()
 3567                },
 3568            ),
 3569            (
 3570                "Rust".into(),
 3571                LanguageSettingsContent {
 3572                    tab_size: NonZeroU32::new(4),
 3573                    ..Default::default()
 3574                },
 3575            ),
 3576        ]);
 3577    });
 3578
 3579    let toml_language = Arc::new(Language::new(
 3580        LanguageConfig {
 3581            name: "TOML".into(),
 3582            ..Default::default()
 3583        },
 3584        None,
 3585    ));
 3586    let rust_language = Arc::new(Language::new(
 3587        LanguageConfig {
 3588            name: "Rust".into(),
 3589            ..Default::default()
 3590        },
 3591        None,
 3592    ));
 3593
 3594    let toml_buffer =
 3595        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3596    let rust_buffer =
 3597        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3598    let multibuffer = cx.new(|cx| {
 3599        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3600        multibuffer.push_excerpts(
 3601            toml_buffer.clone(),
 3602            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3603            cx,
 3604        );
 3605        multibuffer.push_excerpts(
 3606            rust_buffer.clone(),
 3607            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3608            cx,
 3609        );
 3610        multibuffer
 3611    });
 3612
 3613    cx.add_window(|window, cx| {
 3614        let mut editor = build_editor(multibuffer, window, cx);
 3615
 3616        assert_eq!(
 3617            editor.text(cx),
 3618            indoc! {"
 3619                a = 1
 3620                b = 2
 3621
 3622                const c: usize = 3;
 3623            "}
 3624        );
 3625
 3626        select_ranges(
 3627            &mut editor,
 3628            indoc! {"
 3629                «aˇ» = 1
 3630                b = 2
 3631
 3632                «const c:ˇ» usize = 3;
 3633            "},
 3634            window,
 3635            cx,
 3636        );
 3637
 3638        editor.tab(&Tab, window, cx);
 3639        assert_text_with_selections(
 3640            &mut editor,
 3641            indoc! {"
 3642                  «aˇ» = 1
 3643                b = 2
 3644
 3645                    «const c:ˇ» usize = 3;
 3646            "},
 3647            cx,
 3648        );
 3649        editor.backtab(&Backtab, window, cx);
 3650        assert_text_with_selections(
 3651            &mut editor,
 3652            indoc! {"
 3653                «aˇ» = 1
 3654                b = 2
 3655
 3656                «const c:ˇ» usize = 3;
 3657            "},
 3658            cx,
 3659        );
 3660
 3661        editor
 3662    });
 3663}
 3664
 3665#[gpui::test]
 3666async fn test_backspace(cx: &mut TestAppContext) {
 3667    init_test(cx, |_| {});
 3668
 3669    let mut cx = EditorTestContext::new(cx).await;
 3670
 3671    // Basic backspace
 3672    cx.set_state(indoc! {"
 3673        onˇe two three
 3674        fou«rˇ» five six
 3675        seven «ˇeight nine
 3676        »ten
 3677    "});
 3678    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3679    cx.assert_editor_state(indoc! {"
 3680        oˇe two three
 3681        fouˇ five six
 3682        seven ˇten
 3683    "});
 3684
 3685    // Test backspace inside and around indents
 3686    cx.set_state(indoc! {"
 3687        zero
 3688            ˇone
 3689                ˇtwo
 3690            ˇ ˇ ˇ  three
 3691        ˇ  ˇ  four
 3692    "});
 3693    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3694    cx.assert_editor_state(indoc! {"
 3695        zero
 3696        ˇone
 3697            ˇtwo
 3698        ˇ  threeˇ  four
 3699    "});
 3700}
 3701
 3702#[gpui::test]
 3703async fn test_delete(cx: &mut TestAppContext) {
 3704    init_test(cx, |_| {});
 3705
 3706    let mut cx = EditorTestContext::new(cx).await;
 3707    cx.set_state(indoc! {"
 3708        onˇe two three
 3709        fou«rˇ» five six
 3710        seven «ˇeight nine
 3711        »ten
 3712    "});
 3713    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3714    cx.assert_editor_state(indoc! {"
 3715        onˇ two three
 3716        fouˇ five six
 3717        seven ˇten
 3718    "});
 3719}
 3720
 3721#[gpui::test]
 3722fn test_delete_line(cx: &mut TestAppContext) {
 3723    init_test(cx, |_| {});
 3724
 3725    let editor = cx.add_window(|window, cx| {
 3726        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3727        build_editor(buffer, window, cx)
 3728    });
 3729    _ = editor.update(cx, |editor, window, cx| {
 3730        editor.change_selections(None, window, cx, |s| {
 3731            s.select_display_ranges([
 3732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3733                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3734                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3735            ])
 3736        });
 3737        editor.delete_line(&DeleteLine, window, cx);
 3738        assert_eq!(editor.display_text(cx), "ghi");
 3739        assert_eq!(
 3740            editor.selections.display_ranges(cx),
 3741            vec![
 3742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3744            ]
 3745        );
 3746    });
 3747
 3748    let editor = cx.add_window(|window, cx| {
 3749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3750        build_editor(buffer, window, cx)
 3751    });
 3752    _ = editor.update(cx, |editor, window, cx| {
 3753        editor.change_selections(None, window, cx, |s| {
 3754            s.select_display_ranges([
 3755                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3756            ])
 3757        });
 3758        editor.delete_line(&DeleteLine, window, cx);
 3759        assert_eq!(editor.display_text(cx), "ghi\n");
 3760        assert_eq!(
 3761            editor.selections.display_ranges(cx),
 3762            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3763        );
 3764    });
 3765}
 3766
 3767#[gpui::test]
 3768fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3769    init_test(cx, |_| {});
 3770
 3771    cx.add_window(|window, cx| {
 3772        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3773        let mut editor = build_editor(buffer.clone(), window, cx);
 3774        let buffer = buffer.read(cx).as_singleton().unwrap();
 3775
 3776        assert_eq!(
 3777            editor.selections.ranges::<Point>(cx),
 3778            &[Point::new(0, 0)..Point::new(0, 0)]
 3779        );
 3780
 3781        // When on single line, replace newline at end by space
 3782        editor.join_lines(&JoinLines, window, cx);
 3783        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3784        assert_eq!(
 3785            editor.selections.ranges::<Point>(cx),
 3786            &[Point::new(0, 3)..Point::new(0, 3)]
 3787        );
 3788
 3789        // When multiple lines are selected, remove newlines that are spanned by the selection
 3790        editor.change_selections(None, window, cx, |s| {
 3791            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3792        });
 3793        editor.join_lines(&JoinLines, window, cx);
 3794        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3795        assert_eq!(
 3796            editor.selections.ranges::<Point>(cx),
 3797            &[Point::new(0, 11)..Point::new(0, 11)]
 3798        );
 3799
 3800        // Undo should be transactional
 3801        editor.undo(&Undo, window, cx);
 3802        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3803        assert_eq!(
 3804            editor.selections.ranges::<Point>(cx),
 3805            &[Point::new(0, 5)..Point::new(2, 2)]
 3806        );
 3807
 3808        // When joining an empty line don't insert a space
 3809        editor.change_selections(None, window, cx, |s| {
 3810            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3811        });
 3812        editor.join_lines(&JoinLines, window, cx);
 3813        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3814        assert_eq!(
 3815            editor.selections.ranges::<Point>(cx),
 3816            [Point::new(2, 3)..Point::new(2, 3)]
 3817        );
 3818
 3819        // We can remove trailing newlines
 3820        editor.join_lines(&JoinLines, window, cx);
 3821        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3822        assert_eq!(
 3823            editor.selections.ranges::<Point>(cx),
 3824            [Point::new(2, 3)..Point::new(2, 3)]
 3825        );
 3826
 3827        // We don't blow up on the last line
 3828        editor.join_lines(&JoinLines, window, cx);
 3829        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3830        assert_eq!(
 3831            editor.selections.ranges::<Point>(cx),
 3832            [Point::new(2, 3)..Point::new(2, 3)]
 3833        );
 3834
 3835        // reset to test indentation
 3836        editor.buffer.update(cx, |buffer, cx| {
 3837            buffer.edit(
 3838                [
 3839                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3840                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3841                ],
 3842                None,
 3843                cx,
 3844            )
 3845        });
 3846
 3847        // We remove any leading spaces
 3848        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3849        editor.change_selections(None, window, cx, |s| {
 3850            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3851        });
 3852        editor.join_lines(&JoinLines, window, cx);
 3853        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3854
 3855        // We don't insert a space for a line containing only spaces
 3856        editor.join_lines(&JoinLines, window, cx);
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3858
 3859        // We ignore any leading tabs
 3860        editor.join_lines(&JoinLines, window, cx);
 3861        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3862
 3863        editor
 3864    });
 3865}
 3866
 3867#[gpui::test]
 3868fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3869    init_test(cx, |_| {});
 3870
 3871    cx.add_window(|window, cx| {
 3872        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3873        let mut editor = build_editor(buffer.clone(), window, cx);
 3874        let buffer = buffer.read(cx).as_singleton().unwrap();
 3875
 3876        editor.change_selections(None, window, cx, |s| {
 3877            s.select_ranges([
 3878                Point::new(0, 2)..Point::new(1, 1),
 3879                Point::new(1, 2)..Point::new(1, 2),
 3880                Point::new(3, 1)..Point::new(3, 2),
 3881            ])
 3882        });
 3883
 3884        editor.join_lines(&JoinLines, window, cx);
 3885        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3886
 3887        assert_eq!(
 3888            editor.selections.ranges::<Point>(cx),
 3889            [
 3890                Point::new(0, 7)..Point::new(0, 7),
 3891                Point::new(1, 3)..Point::new(1, 3)
 3892            ]
 3893        );
 3894        editor
 3895    });
 3896}
 3897
 3898#[gpui::test]
 3899async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3900    init_test(cx, |_| {});
 3901
 3902    let mut cx = EditorTestContext::new(cx).await;
 3903
 3904    let diff_base = r#"
 3905        Line 0
 3906        Line 1
 3907        Line 2
 3908        Line 3
 3909        "#
 3910    .unindent();
 3911
 3912    cx.set_state(
 3913        &r#"
 3914        ˇLine 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919        .unindent(),
 3920    );
 3921
 3922    cx.set_head_text(&diff_base);
 3923    executor.run_until_parked();
 3924
 3925    // Join lines
 3926    cx.update_editor(|editor, window, cx| {
 3927        editor.join_lines(&JoinLines, window, cx);
 3928    });
 3929    executor.run_until_parked();
 3930
 3931    cx.assert_editor_state(
 3932        &r#"
 3933        Line 0ˇ Line 1
 3934        Line 2
 3935        Line 3
 3936        "#
 3937        .unindent(),
 3938    );
 3939    // Join again
 3940    cx.update_editor(|editor, window, cx| {
 3941        editor.join_lines(&JoinLines, window, cx);
 3942    });
 3943    executor.run_until_parked();
 3944
 3945    cx.assert_editor_state(
 3946        &r#"
 3947        Line 0 Line 1ˇ Line 2
 3948        Line 3
 3949        "#
 3950        .unindent(),
 3951    );
 3952}
 3953
 3954#[gpui::test]
 3955async fn test_custom_newlines_cause_no_false_positive_diffs(
 3956    executor: BackgroundExecutor,
 3957    cx: &mut TestAppContext,
 3958) {
 3959    init_test(cx, |_| {});
 3960    let mut cx = EditorTestContext::new(cx).await;
 3961    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3962    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3963    executor.run_until_parked();
 3964
 3965    cx.update_editor(|editor, window, cx| {
 3966        let snapshot = editor.snapshot(window, cx);
 3967        assert_eq!(
 3968            snapshot
 3969                .buffer_snapshot
 3970                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3971                .collect::<Vec<_>>(),
 3972            Vec::new(),
 3973            "Should not have any diffs for files with custom newlines"
 3974        );
 3975    });
 3976}
 3977
 3978#[gpui::test]
 3979async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3980    init_test(cx, |_| {});
 3981
 3982    let mut cx = EditorTestContext::new(cx).await;
 3983
 3984    // Test sort_lines_case_insensitive()
 3985    cx.set_state(indoc! {"
 3986        «z
 3987        y
 3988        x
 3989        Z
 3990        Y
 3991        Xˇ»
 3992    "});
 3993    cx.update_editor(|e, window, cx| {
 3994        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3995    });
 3996    cx.assert_editor_state(indoc! {"
 3997        «x
 3998        X
 3999        y
 4000        Y
 4001        z
 4002        Zˇ»
 4003    "});
 4004
 4005    // Test reverse_lines()
 4006    cx.set_state(indoc! {"
 4007        «5
 4008        4
 4009        3
 4010        2
 4011        1ˇ»
 4012    "});
 4013    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4014    cx.assert_editor_state(indoc! {"
 4015        «1
 4016        2
 4017        3
 4018        4
 4019        5ˇ»
 4020    "});
 4021
 4022    // Skip testing shuffle_line()
 4023
 4024    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 4025    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 4026
 4027    // Don't manipulate when cursor is on single line, but expand the selection
 4028    cx.set_state(indoc! {"
 4029        ddˇdd
 4030        ccc
 4031        bb
 4032        a
 4033    "});
 4034    cx.update_editor(|e, window, cx| {
 4035        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4036    });
 4037    cx.assert_editor_state(indoc! {"
 4038        «ddddˇ»
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043
 4044    // Basic manipulate case
 4045    // Start selection moves to column 0
 4046    // End of selection shrinks to fit shorter line
 4047    cx.set_state(indoc! {"
 4048        dd«d
 4049        ccc
 4050        bb
 4051        aaaaaˇ»
 4052    "});
 4053    cx.update_editor(|e, window, cx| {
 4054        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4055    });
 4056    cx.assert_editor_state(indoc! {"
 4057        «aaaaa
 4058        bb
 4059        ccc
 4060        dddˇ»
 4061    "});
 4062
 4063    // Manipulate case with newlines
 4064    cx.set_state(indoc! {"
 4065        dd«d
 4066        ccc
 4067
 4068        bb
 4069        aaaaa
 4070
 4071        ˇ»
 4072    "});
 4073    cx.update_editor(|e, window, cx| {
 4074        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4075    });
 4076    cx.assert_editor_state(indoc! {"
 4077        «
 4078
 4079        aaaaa
 4080        bb
 4081        ccc
 4082        dddˇ»
 4083
 4084    "});
 4085
 4086    // Adding new line
 4087    cx.set_state(indoc! {"
 4088        aa«a
 4089        bbˇ»b
 4090    "});
 4091    cx.update_editor(|e, window, cx| {
 4092        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4093    });
 4094    cx.assert_editor_state(indoc! {"
 4095        «aaa
 4096        bbb
 4097        added_lineˇ»
 4098    "});
 4099
 4100    // Removing line
 4101    cx.set_state(indoc! {"
 4102        aa«a
 4103        bbbˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.manipulate_lines(window, cx, |lines| {
 4107            lines.pop();
 4108        })
 4109    });
 4110    cx.assert_editor_state(indoc! {"
 4111        «aaaˇ»
 4112    "});
 4113
 4114    // Removing all lines
 4115    cx.set_state(indoc! {"
 4116        aa«a
 4117        bbbˇ»
 4118    "});
 4119    cx.update_editor(|e, window, cx| {
 4120        e.manipulate_lines(window, cx, |lines| {
 4121            lines.drain(..);
 4122        })
 4123    });
 4124    cx.assert_editor_state(indoc! {"
 4125        ˇ
 4126    "});
 4127}
 4128
 4129#[gpui::test]
 4130async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4131    init_test(cx, |_| {});
 4132
 4133    let mut cx = EditorTestContext::new(cx).await;
 4134
 4135    // Consider continuous selection as single selection
 4136    cx.set_state(indoc! {"
 4137        Aaa«aa
 4138        cˇ»c«c
 4139        bb
 4140        aaaˇ»aa
 4141    "});
 4142    cx.update_editor(|e, window, cx| {
 4143        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4144    });
 4145    cx.assert_editor_state(indoc! {"
 4146        «Aaaaa
 4147        ccc
 4148        bb
 4149        aaaaaˇ»
 4150    "});
 4151
 4152    cx.set_state(indoc! {"
 4153        Aaa«aa
 4154        cˇ»c«c
 4155        bb
 4156        aaaˇ»aa
 4157    "});
 4158    cx.update_editor(|e, window, cx| {
 4159        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4160    });
 4161    cx.assert_editor_state(indoc! {"
 4162        «Aaaaa
 4163        ccc
 4164        bbˇ»
 4165    "});
 4166
 4167    // Consider non continuous selection as distinct dedup operations
 4168    cx.set_state(indoc! {"
 4169        «aaaaa
 4170        bb
 4171        aaaaa
 4172        aaaaaˇ»
 4173
 4174        aaa«aaˇ»
 4175    "});
 4176    cx.update_editor(|e, window, cx| {
 4177        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4178    });
 4179    cx.assert_editor_state(indoc! {"
 4180        «aaaaa
 4181        bbˇ»
 4182
 4183        «aaaaaˇ»
 4184    "});
 4185}
 4186
 4187#[gpui::test]
 4188async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4189    init_test(cx, |_| {});
 4190
 4191    let mut cx = EditorTestContext::new(cx).await;
 4192
 4193    cx.set_state(indoc! {"
 4194        «Aaa
 4195        aAa
 4196        Aaaˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| {
 4199        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4200    });
 4201    cx.assert_editor_state(indoc! {"
 4202        «Aaa
 4203        aAaˇ»
 4204    "});
 4205
 4206    cx.set_state(indoc! {"
 4207        «Aaa
 4208        aAa
 4209        aaAˇ»
 4210    "});
 4211    cx.update_editor(|e, window, cx| {
 4212        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4213    });
 4214    cx.assert_editor_state(indoc! {"
 4215        «Aaaˇ»
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224
 4225    // Manipulate with multiple selections on a single line
 4226    cx.set_state(indoc! {"
 4227        dd«dd
 4228        cˇ»c«c
 4229        bb
 4230        aaaˇ»aa
 4231    "});
 4232    cx.update_editor(|e, window, cx| {
 4233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4234    });
 4235    cx.assert_editor_state(indoc! {"
 4236        «aaaaa
 4237        bb
 4238        ccc
 4239        ddddˇ»
 4240    "});
 4241
 4242    // Manipulate with multiple disjoin selections
 4243    cx.set_state(indoc! {"
 4244 4245        4
 4246        3
 4247        2
 4248        1ˇ»
 4249
 4250        dd«dd
 4251        ccc
 4252        bb
 4253        aaaˇ»aa
 4254    "});
 4255    cx.update_editor(|e, window, cx| {
 4256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4257    });
 4258    cx.assert_editor_state(indoc! {"
 4259        «1
 4260        2
 4261        3
 4262        4
 4263        5ˇ»
 4264
 4265        «aaaaa
 4266        bb
 4267        ccc
 4268        ddddˇ»
 4269    "});
 4270
 4271    // Adding lines on each selection
 4272    cx.set_state(indoc! {"
 4273 4274        1ˇ»
 4275
 4276        bb«bb
 4277        aaaˇ»aa
 4278    "});
 4279    cx.update_editor(|e, window, cx| {
 4280        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4281    });
 4282    cx.assert_editor_state(indoc! {"
 4283        «2
 4284        1
 4285        added lineˇ»
 4286
 4287        «bbbb
 4288        aaaaa
 4289        added lineˇ»
 4290    "});
 4291
 4292    // Removing lines on each selection
 4293    cx.set_state(indoc! {"
 4294 4295        1ˇ»
 4296
 4297        bb«bb
 4298        aaaˇ»aa
 4299    "});
 4300    cx.update_editor(|e, window, cx| {
 4301        e.manipulate_lines(window, cx, |lines| {
 4302            lines.pop();
 4303        })
 4304    });
 4305    cx.assert_editor_state(indoc! {"
 4306        «2ˇ»
 4307
 4308        «bbbbˇ»
 4309    "});
 4310}
 4311
 4312#[gpui::test]
 4313async fn test_toggle_case(cx: &mut TestAppContext) {
 4314    init_test(cx, |_| {});
 4315
 4316    let mut cx = EditorTestContext::new(cx).await;
 4317
 4318    // If all lower case -> upper case
 4319    cx.set_state(indoc! {"
 4320        «hello worldˇ»
 4321    "});
 4322    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4323    cx.assert_editor_state(indoc! {"
 4324        «HELLO WORLDˇ»
 4325    "});
 4326
 4327    // If all upper case -> lower case
 4328    cx.set_state(indoc! {"
 4329        «HELLO WORLDˇ»
 4330    "});
 4331    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4332    cx.assert_editor_state(indoc! {"
 4333        «hello worldˇ»
 4334    "});
 4335
 4336    // If any upper case characters are identified -> lower case
 4337    // This matches JetBrains IDEs
 4338    cx.set_state(indoc! {"
 4339        «hEllo worldˇ»
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        «hello worldˇ»
 4344    "});
 4345}
 4346
 4347#[gpui::test]
 4348async fn test_manipulate_text(cx: &mut TestAppContext) {
 4349    init_test(cx, |_| {});
 4350
 4351    let mut cx = EditorTestContext::new(cx).await;
 4352
 4353    // Test convert_to_upper_case()
 4354    cx.set_state(indoc! {"
 4355        «hello worldˇ»
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        «HELLO WORLDˇ»
 4360    "});
 4361
 4362    // Test convert_to_lower_case()
 4363    cx.set_state(indoc! {"
 4364        «HELLO WORLDˇ»
 4365    "});
 4366    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4367    cx.assert_editor_state(indoc! {"
 4368        «hello worldˇ»
 4369    "});
 4370
 4371    // Test multiple line, single selection case
 4372    cx.set_state(indoc! {"
 4373        «The quick brown
 4374        fox jumps over
 4375        the lazy dogˇ»
 4376    "});
 4377    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4378    cx.assert_editor_state(indoc! {"
 4379        «The Quick Brown
 4380        Fox Jumps Over
 4381        The Lazy Dogˇ»
 4382    "});
 4383
 4384    // Test multiple line, single selection case
 4385    cx.set_state(indoc! {"
 4386        «The quick brown
 4387        fox jumps over
 4388        the lazy dogˇ»
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «TheQuickBrown
 4395        FoxJumpsOver
 4396        TheLazyDogˇ»
 4397    "});
 4398
 4399    // From here on out, test more complex cases of manipulate_text()
 4400
 4401    // Test no selection case - should affect words cursors are in
 4402    // Cursor at beginning, middle, and end of word
 4403    cx.set_state(indoc! {"
 4404        ˇhello big beauˇtiful worldˇ
 4405    "});
 4406    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4407    cx.assert_editor_state(indoc! {"
 4408        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4409    "});
 4410
 4411    // Test multiple selections on a single line and across multiple lines
 4412    cx.set_state(indoc! {"
 4413        «Theˇ» quick «brown
 4414        foxˇ» jumps «overˇ»
 4415        the «lazyˇ» dog
 4416    "});
 4417    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4418    cx.assert_editor_state(indoc! {"
 4419        «THEˇ» quick «BROWN
 4420        FOXˇ» jumps «OVERˇ»
 4421        the «LAZYˇ» dog
 4422    "});
 4423
 4424    // Test case where text length grows
 4425    cx.set_state(indoc! {"
 4426        «tschüߡ»
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        «TSCHÜSSˇ»
 4431    "});
 4432
 4433    // Test to make sure we don't crash when text shrinks
 4434    cx.set_state(indoc! {"
 4435        aaa_bbbˇ
 4436    "});
 4437    cx.update_editor(|e, window, cx| {
 4438        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «aaaBbbˇ»
 4442    "});
 4443
 4444    // Test to make sure we all aware of the fact that each word can grow and shrink
 4445    // Final selections should be aware of this fact
 4446    cx.set_state(indoc! {"
 4447        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4448    "});
 4449    cx.update_editor(|e, window, cx| {
 4450        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4451    });
 4452    cx.assert_editor_state(indoc! {"
 4453        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4454    "});
 4455
 4456    cx.set_state(indoc! {"
 4457        «hElLo, WoRld!ˇ»
 4458    "});
 4459    cx.update_editor(|e, window, cx| {
 4460        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4461    });
 4462    cx.assert_editor_state(indoc! {"
 4463        «HeLlO, wOrLD!ˇ»
 4464    "});
 4465}
 4466
 4467#[gpui::test]
 4468fn test_duplicate_line(cx: &mut TestAppContext) {
 4469    init_test(cx, |_| {});
 4470
 4471    let editor = cx.add_window(|window, cx| {
 4472        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4473        build_editor(buffer, window, cx)
 4474    });
 4475    _ = editor.update(cx, |editor, window, cx| {
 4476        editor.change_selections(None, window, cx, |s| {
 4477            s.select_display_ranges([
 4478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4479                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4480                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4481                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4482            ])
 4483        });
 4484        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4485        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4486        assert_eq!(
 4487            editor.selections.display_ranges(cx),
 4488            vec![
 4489                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4490                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4491                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4492                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4493            ]
 4494        );
 4495    });
 4496
 4497    let editor = cx.add_window(|window, cx| {
 4498        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4499        build_editor(buffer, window, cx)
 4500    });
 4501    _ = editor.update(cx, |editor, window, cx| {
 4502        editor.change_selections(None, window, cx, |s| {
 4503            s.select_display_ranges([
 4504                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4505                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4506            ])
 4507        });
 4508        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4509        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![
 4513                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4514                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4515            ]
 4516        );
 4517    });
 4518
 4519    // With `move_upwards` the selections stay in place, except for
 4520    // the lines inserted above them
 4521    let editor = cx.add_window(|window, cx| {
 4522        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4523        build_editor(buffer, window, cx)
 4524    });
 4525    _ = editor.update(cx, |editor, window, cx| {
 4526        editor.change_selections(None, window, cx, |s| {
 4527            s.select_display_ranges([
 4528                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4529                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4530                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4531                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4532            ])
 4533        });
 4534        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4535        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4536        assert_eq!(
 4537            editor.selections.display_ranges(cx),
 4538            vec![
 4539                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4540                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4541                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4542                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4543            ]
 4544        );
 4545    });
 4546
 4547    let editor = cx.add_window(|window, cx| {
 4548        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4549        build_editor(buffer, window, cx)
 4550    });
 4551    _ = editor.update(cx, |editor, window, cx| {
 4552        editor.change_selections(None, window, cx, |s| {
 4553            s.select_display_ranges([
 4554                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4556            ])
 4557        });
 4558        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4559        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4560        assert_eq!(
 4561            editor.selections.display_ranges(cx),
 4562            vec![
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4565            ]
 4566        );
 4567    });
 4568
 4569    let editor = cx.add_window(|window, cx| {
 4570        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4571        build_editor(buffer, window, cx)
 4572    });
 4573    _ = editor.update(cx, |editor, window, cx| {
 4574        editor.change_selections(None, window, cx, |s| {
 4575            s.select_display_ranges([
 4576                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4577                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4578            ])
 4579        });
 4580        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4581        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4582        assert_eq!(
 4583            editor.selections.display_ranges(cx),
 4584            vec![
 4585                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4586                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4587            ]
 4588        );
 4589    });
 4590}
 4591
 4592#[gpui::test]
 4593fn test_move_line_up_down(cx: &mut TestAppContext) {
 4594    init_test(cx, |_| {});
 4595
 4596    let editor = cx.add_window(|window, cx| {
 4597        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4598        build_editor(buffer, window, cx)
 4599    });
 4600    _ = editor.update(cx, |editor, window, cx| {
 4601        editor.fold_creases(
 4602            vec![
 4603                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4604                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4605                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4606            ],
 4607            true,
 4608            window,
 4609            cx,
 4610        );
 4611        editor.change_selections(None, window, cx, |s| {
 4612            s.select_display_ranges([
 4613                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4614                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4615                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4616                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4617            ])
 4618        });
 4619        assert_eq!(
 4620            editor.display_text(cx),
 4621            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4622        );
 4623
 4624        editor.move_line_up(&MoveLineUp, window, cx);
 4625        assert_eq!(
 4626            editor.display_text(cx),
 4627            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4628        );
 4629        assert_eq!(
 4630            editor.selections.display_ranges(cx),
 4631            vec![
 4632                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4633                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4634                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4635                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4636            ]
 4637        );
 4638    });
 4639
 4640    _ = editor.update(cx, |editor, window, cx| {
 4641        editor.move_line_down(&MoveLineDown, window, cx);
 4642        assert_eq!(
 4643            editor.display_text(cx),
 4644            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4645        );
 4646        assert_eq!(
 4647            editor.selections.display_ranges(cx),
 4648            vec![
 4649                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4650                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4651                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4652                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4653            ]
 4654        );
 4655    });
 4656
 4657    _ = editor.update(cx, |editor, window, cx| {
 4658        editor.move_line_down(&MoveLineDown, window, cx);
 4659        assert_eq!(
 4660            editor.display_text(cx),
 4661            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4662        );
 4663        assert_eq!(
 4664            editor.selections.display_ranges(cx),
 4665            vec![
 4666                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4667                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4668                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4669                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4670            ]
 4671        );
 4672    });
 4673
 4674    _ = editor.update(cx, |editor, window, cx| {
 4675        editor.move_line_up(&MoveLineUp, window, cx);
 4676        assert_eq!(
 4677            editor.display_text(cx),
 4678            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4679        );
 4680        assert_eq!(
 4681            editor.selections.display_ranges(cx),
 4682            vec![
 4683                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4684                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4685                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4686                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4687            ]
 4688        );
 4689    });
 4690}
 4691
 4692#[gpui::test]
 4693fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let editor = cx.add_window(|window, cx| {
 4697        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4698        build_editor(buffer, window, cx)
 4699    });
 4700    _ = editor.update(cx, |editor, window, cx| {
 4701        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4702        editor.insert_blocks(
 4703            [BlockProperties {
 4704                style: BlockStyle::Fixed,
 4705                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4706                height: Some(1),
 4707                render: Arc::new(|_| div().into_any()),
 4708                priority: 0,
 4709                render_in_minimap: true,
 4710            }],
 4711            Some(Autoscroll::fit()),
 4712            cx,
 4713        );
 4714        editor.change_selections(None, window, cx, |s| {
 4715            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4716        });
 4717        editor.move_line_down(&MoveLineDown, window, cx);
 4718    });
 4719}
 4720
 4721#[gpui::test]
 4722async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4723    init_test(cx, |_| {});
 4724
 4725    let mut cx = EditorTestContext::new(cx).await;
 4726    cx.set_state(
 4727        &"
 4728            ˇzero
 4729            one
 4730            two
 4731            three
 4732            four
 4733            five
 4734        "
 4735        .unindent(),
 4736    );
 4737
 4738    // Create a four-line block that replaces three lines of text.
 4739    cx.update_editor(|editor, window, cx| {
 4740        let snapshot = editor.snapshot(window, cx);
 4741        let snapshot = &snapshot.buffer_snapshot;
 4742        let placement = BlockPlacement::Replace(
 4743            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4744        );
 4745        editor.insert_blocks(
 4746            [BlockProperties {
 4747                placement,
 4748                height: Some(4),
 4749                style: BlockStyle::Sticky,
 4750                render: Arc::new(|_| gpui::div().into_any_element()),
 4751                priority: 0,
 4752                render_in_minimap: true,
 4753            }],
 4754            None,
 4755            cx,
 4756        );
 4757    });
 4758
 4759    // Move down so that the cursor touches the block.
 4760    cx.update_editor(|editor, window, cx| {
 4761        editor.move_down(&Default::default(), window, cx);
 4762    });
 4763    cx.assert_editor_state(
 4764        &"
 4765            zero
 4766            «one
 4767            two
 4768            threeˇ»
 4769            four
 4770            five
 4771        "
 4772        .unindent(),
 4773    );
 4774
 4775    // Move down past the block.
 4776    cx.update_editor(|editor, window, cx| {
 4777        editor.move_down(&Default::default(), window, cx);
 4778    });
 4779    cx.assert_editor_state(
 4780        &"
 4781            zero
 4782            one
 4783            two
 4784            three
 4785            ˇfour
 4786            five
 4787        "
 4788        .unindent(),
 4789    );
 4790}
 4791
 4792#[gpui::test]
 4793fn test_transpose(cx: &mut TestAppContext) {
 4794    init_test(cx, |_| {});
 4795
 4796    _ = cx.add_window(|window, cx| {
 4797        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4798        editor.set_style(EditorStyle::default(), window, cx);
 4799        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4800        editor.transpose(&Default::default(), window, cx);
 4801        assert_eq!(editor.text(cx), "bac");
 4802        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4803
 4804        editor.transpose(&Default::default(), window, cx);
 4805        assert_eq!(editor.text(cx), "bca");
 4806        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4807
 4808        editor.transpose(&Default::default(), window, cx);
 4809        assert_eq!(editor.text(cx), "bac");
 4810        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4811
 4812        editor
 4813    });
 4814
 4815    _ = cx.add_window(|window, cx| {
 4816        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4817        editor.set_style(EditorStyle::default(), window, cx);
 4818        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4819        editor.transpose(&Default::default(), window, cx);
 4820        assert_eq!(editor.text(cx), "acb\nde");
 4821        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4822
 4823        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4824        editor.transpose(&Default::default(), window, cx);
 4825        assert_eq!(editor.text(cx), "acbd\ne");
 4826        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4827
 4828        editor.transpose(&Default::default(), window, cx);
 4829        assert_eq!(editor.text(cx), "acbde\n");
 4830        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4831
 4832        editor.transpose(&Default::default(), window, cx);
 4833        assert_eq!(editor.text(cx), "acbd\ne");
 4834        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4835
 4836        editor
 4837    });
 4838
 4839    _ = cx.add_window(|window, cx| {
 4840        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4841        editor.set_style(EditorStyle::default(), window, cx);
 4842        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4843        editor.transpose(&Default::default(), window, cx);
 4844        assert_eq!(editor.text(cx), "bacd\ne");
 4845        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4846
 4847        editor.transpose(&Default::default(), window, cx);
 4848        assert_eq!(editor.text(cx), "bcade\n");
 4849        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4850
 4851        editor.transpose(&Default::default(), window, cx);
 4852        assert_eq!(editor.text(cx), "bcda\ne");
 4853        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4854
 4855        editor.transpose(&Default::default(), window, cx);
 4856        assert_eq!(editor.text(cx), "bcade\n");
 4857        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4858
 4859        editor.transpose(&Default::default(), window, cx);
 4860        assert_eq!(editor.text(cx), "bcaed\n");
 4861        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4862
 4863        editor
 4864    });
 4865
 4866    _ = cx.add_window(|window, cx| {
 4867        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4868        editor.set_style(EditorStyle::default(), window, cx);
 4869        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4870        editor.transpose(&Default::default(), window, cx);
 4871        assert_eq!(editor.text(cx), "🏀🍐✋");
 4872        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4873
 4874        editor.transpose(&Default::default(), window, cx);
 4875        assert_eq!(editor.text(cx), "🏀✋🍐");
 4876        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4877
 4878        editor.transpose(&Default::default(), window, cx);
 4879        assert_eq!(editor.text(cx), "🏀🍐✋");
 4880        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4881
 4882        editor
 4883    });
 4884}
 4885
 4886#[gpui::test]
 4887async fn test_rewrap(cx: &mut TestAppContext) {
 4888    init_test(cx, |settings| {
 4889        settings.languages.extend([
 4890            (
 4891                "Markdown".into(),
 4892                LanguageSettingsContent {
 4893                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4894                    ..Default::default()
 4895                },
 4896            ),
 4897            (
 4898                "Plain Text".into(),
 4899                LanguageSettingsContent {
 4900                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4901                    ..Default::default()
 4902                },
 4903            ),
 4904        ])
 4905    });
 4906
 4907    let mut cx = EditorTestContext::new(cx).await;
 4908
 4909    let language_with_c_comments = Arc::new(Language::new(
 4910        LanguageConfig {
 4911            line_comments: vec!["// ".into()],
 4912            ..LanguageConfig::default()
 4913        },
 4914        None,
 4915    ));
 4916    let language_with_pound_comments = Arc::new(Language::new(
 4917        LanguageConfig {
 4918            line_comments: vec!["# ".into()],
 4919            ..LanguageConfig::default()
 4920        },
 4921        None,
 4922    ));
 4923    let markdown_language = Arc::new(Language::new(
 4924        LanguageConfig {
 4925            name: "Markdown".into(),
 4926            ..LanguageConfig::default()
 4927        },
 4928        None,
 4929    ));
 4930    let language_with_doc_comments = Arc::new(Language::new(
 4931        LanguageConfig {
 4932            line_comments: vec!["// ".into(), "/// ".into()],
 4933            ..LanguageConfig::default()
 4934        },
 4935        Some(tree_sitter_rust::LANGUAGE.into()),
 4936    ));
 4937
 4938    let plaintext_language = Arc::new(Language::new(
 4939        LanguageConfig {
 4940            name: "Plain Text".into(),
 4941            ..LanguageConfig::default()
 4942        },
 4943        None,
 4944    ));
 4945
 4946    assert_rewrap(
 4947        indoc! {"
 4948            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4949        "},
 4950        indoc! {"
 4951            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4952            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4953            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4954            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4955            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4956            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4957            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4958            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4959            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4960            // porttitor id. Aliquam id accumsan eros.
 4961        "},
 4962        language_with_c_comments.clone(),
 4963        &mut cx,
 4964    );
 4965
 4966    // Test that rewrapping works inside of a selection
 4967    assert_rewrap(
 4968        indoc! {"
 4969            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
 4970        "},
 4971        indoc! {"
 4972            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4973            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4974            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4975            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4976            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4977            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4978            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4979            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4980            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4981            // porttitor id. Aliquam id accumsan eros.ˇ»
 4982        "},
 4983        language_with_c_comments.clone(),
 4984        &mut cx,
 4985    );
 4986
 4987    // Test that cursors that expand to the same region are collapsed.
 4988    assert_rewrap(
 4989        indoc! {"
 4990            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4991            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4992            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4993            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4994        "},
 4995        indoc! {"
 4996            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4997            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4998            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4999            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5000            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5001            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5002            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5003            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5004            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5005            // porttitor id. Aliquam id accumsan eros.
 5006        "},
 5007        language_with_c_comments.clone(),
 5008        &mut cx,
 5009    );
 5010
 5011    // Test that non-contiguous selections are treated separately.
 5012    assert_rewrap(
 5013        indoc! {"
 5014            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5015            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5016            //
 5017            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5018            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5019        "},
 5020        indoc! {"
 5021            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5022            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5023            // auctor, eu lacinia sapien scelerisque.
 5024            //
 5025            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5026            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5027            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5028            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5029            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5030            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5031            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5032        "},
 5033        language_with_c_comments.clone(),
 5034        &mut cx,
 5035    );
 5036
 5037    // Test that different comment prefixes are supported.
 5038    assert_rewrap(
 5039        indoc! {"
 5040            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5041        "},
 5042        indoc! {"
 5043            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5044            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5045            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5046            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5047            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5048            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5049            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5050            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5051            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5052            # accumsan eros.
 5053        "},
 5054        language_with_pound_comments.clone(),
 5055        &mut cx,
 5056    );
 5057
 5058    // Test that rewrapping is ignored outside of comments in most languages.
 5059    assert_rewrap(
 5060        indoc! {"
 5061            /// Adds two numbers.
 5062            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5063            fn add(a: u32, b: u32) -> u32 {
 5064                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5065            }
 5066        "},
 5067        indoc! {"
 5068            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5069            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5070            fn add(a: u32, b: u32) -> u32 {
 5071                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5072            }
 5073        "},
 5074        language_with_doc_comments.clone(),
 5075        &mut cx,
 5076    );
 5077
 5078    // Test that rewrapping works in Markdown and Plain Text languages.
 5079    assert_rewrap(
 5080        indoc! {"
 5081            # Hello
 5082
 5083            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5084        "},
 5085        indoc! {"
 5086            # Hello
 5087
 5088            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5089            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5090            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5091            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5092            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5093            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5094            Integer sit amet scelerisque nisi.
 5095        "},
 5096        markdown_language,
 5097        &mut cx,
 5098    );
 5099
 5100    assert_rewrap(
 5101        indoc! {"
 5102            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5103        "},
 5104        indoc! {"
 5105            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5106            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5107            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5108            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5109            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5110            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5111            Integer sit amet scelerisque nisi.
 5112        "},
 5113        plaintext_language.clone(),
 5114        &mut cx,
 5115    );
 5116
 5117    // Test rewrapping unaligned comments in a selection.
 5118    assert_rewrap(
 5119        indoc! {"
 5120            fn foo() {
 5121                if true {
 5122            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5123            // Praesent semper egestas tellus id dignissim.ˇ»
 5124                    do_something();
 5125                } else {
 5126                    //
 5127                }
 5128            }
 5129        "},
 5130        indoc! {"
 5131            fn foo() {
 5132                if true {
 5133            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5134                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5135                    // egestas tellus id dignissim.ˇ»
 5136                    do_something();
 5137                } else {
 5138                    //
 5139                }
 5140            }
 5141        "},
 5142        language_with_doc_comments.clone(),
 5143        &mut cx,
 5144    );
 5145
 5146    assert_rewrap(
 5147        indoc! {"
 5148            fn foo() {
 5149                if true {
 5150            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5151            // Praesent semper egestas tellus id dignissim.»
 5152                    do_something();
 5153                } else {
 5154                    //
 5155                }
 5156
 5157            }
 5158        "},
 5159        indoc! {"
 5160            fn foo() {
 5161                if true {
 5162            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5163                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5164                    // egestas tellus id dignissim.»
 5165                    do_something();
 5166                } else {
 5167                    //
 5168                }
 5169
 5170            }
 5171        "},
 5172        language_with_doc_comments.clone(),
 5173        &mut cx,
 5174    );
 5175
 5176    assert_rewrap(
 5177        indoc! {"
 5178            «ˇone one one one one one one one one one one one one one one one one one one one one one one one one
 5179
 5180            two»
 5181
 5182            three
 5183
 5184            «ˇ\t
 5185
 5186            four four four four four four four four four four four four four four four four four four four four»
 5187
 5188            «ˇfive five five five five five five five five five five five five five five five five five five five
 5189            \t»
 5190            six six six six six six six six six six six six six six six six six six six six six six six six six
 5191        "},
 5192        indoc! {"
 5193            «ˇone one one one one one one one one one one one one one one one one one one one
 5194            one one one one one
 5195
 5196            two»
 5197
 5198            three
 5199
 5200            «ˇ\t
 5201
 5202            four four four four four four four four four four four four four four four four
 5203            four four four four»
 5204
 5205            «ˇfive five five five five five five five five five five five five five five five
 5206            five five five five
 5207            \t»
 5208            six six six six six six six six six six six six six six six six six six six six six six six six six
 5209        "},
 5210        plaintext_language.clone(),
 5211        &mut cx,
 5212    );
 5213
 5214    assert_rewrap(
 5215        indoc! {"
 5216            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5217            //ˇ
 5218            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5219            //ˇ short short short
 5220            int main(void) {
 5221                return 17;
 5222            }
 5223        "},
 5224        indoc! {"
 5225            //ˇ long long long long long long long long long long long long long long long
 5226            // long long long long long long long long long long long long long
 5227            //ˇ
 5228            //ˇ long long long long long long long long long long long long long long long
 5229            //ˇ long long long long long long long long long long long long long short short
 5230            // short
 5231            int main(void) {
 5232                return 17;
 5233            }
 5234        "},
 5235        language_with_c_comments,
 5236        &mut cx,
 5237    );
 5238
 5239    #[track_caller]
 5240    fn assert_rewrap(
 5241        unwrapped_text: &str,
 5242        wrapped_text: &str,
 5243        language: Arc<Language>,
 5244        cx: &mut EditorTestContext,
 5245    ) {
 5246        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5247        cx.set_state(unwrapped_text);
 5248        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5249        cx.assert_editor_state(wrapped_text);
 5250    }
 5251}
 5252
 5253#[gpui::test]
 5254async fn test_hard_wrap(cx: &mut TestAppContext) {
 5255    init_test(cx, |_| {});
 5256    let mut cx = EditorTestContext::new(cx).await;
 5257
 5258    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5259    cx.update_editor(|editor, _, cx| {
 5260        editor.set_hard_wrap(Some(14), cx);
 5261    });
 5262
 5263    cx.set_state(indoc!(
 5264        "
 5265        one two three ˇ
 5266        "
 5267    ));
 5268    cx.simulate_input("four");
 5269    cx.run_until_parked();
 5270
 5271    cx.assert_editor_state(indoc!(
 5272        "
 5273        one two three
 5274        fourˇ
 5275        "
 5276    ));
 5277
 5278    cx.update_editor(|editor, window, cx| {
 5279        editor.newline(&Default::default(), window, cx);
 5280    });
 5281    cx.run_until_parked();
 5282    cx.assert_editor_state(indoc!(
 5283        "
 5284        one two three
 5285        four
 5286        ˇ
 5287        "
 5288    ));
 5289
 5290    cx.simulate_input("five");
 5291    cx.run_until_parked();
 5292    cx.assert_editor_state(indoc!(
 5293        "
 5294        one two three
 5295        four
 5296        fiveˇ
 5297        "
 5298    ));
 5299
 5300    cx.update_editor(|editor, window, cx| {
 5301        editor.newline(&Default::default(), window, cx);
 5302    });
 5303    cx.run_until_parked();
 5304    cx.simulate_input("# ");
 5305    cx.run_until_parked();
 5306    cx.assert_editor_state(indoc!(
 5307        "
 5308        one two three
 5309        four
 5310        five
 5311        # ˇ
 5312        "
 5313    ));
 5314
 5315    cx.update_editor(|editor, window, cx| {
 5316        editor.newline(&Default::default(), window, cx);
 5317    });
 5318    cx.run_until_parked();
 5319    cx.assert_editor_state(indoc!(
 5320        "
 5321        one two three
 5322        four
 5323        five
 5324        #\x20
 5325 5326        "
 5327    ));
 5328
 5329    cx.simulate_input(" 6");
 5330    cx.run_until_parked();
 5331    cx.assert_editor_state(indoc!(
 5332        "
 5333        one two three
 5334        four
 5335        five
 5336        #
 5337        # 6ˇ
 5338        "
 5339    ));
 5340}
 5341
 5342#[gpui::test]
 5343async fn test_clipboard(cx: &mut TestAppContext) {
 5344    init_test(cx, |_| {});
 5345
 5346    let mut cx = EditorTestContext::new(cx).await;
 5347
 5348    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5349    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5350    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5351
 5352    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5353    cx.set_state("two ˇfour ˇsix ˇ");
 5354    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5355    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5356
 5357    // Paste again but with only two cursors. Since the number of cursors doesn't
 5358    // match the number of slices in the clipboard, the entire clipboard text
 5359    // is pasted at each cursor.
 5360    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5361    cx.update_editor(|e, window, cx| {
 5362        e.handle_input("( ", window, cx);
 5363        e.paste(&Paste, window, cx);
 5364        e.handle_input(") ", window, cx);
 5365    });
 5366    cx.assert_editor_state(
 5367        &([
 5368            "( one✅ ",
 5369            "three ",
 5370            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5371            "three ",
 5372            "five ) ˇ",
 5373        ]
 5374        .join("\n")),
 5375    );
 5376
 5377    // Cut with three selections, one of which is full-line.
 5378    cx.set_state(indoc! {"
 5379        1«2ˇ»3
 5380        4ˇ567
 5381        «8ˇ»9"});
 5382    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5383    cx.assert_editor_state(indoc! {"
 5384        1ˇ3
 5385        ˇ9"});
 5386
 5387    // Paste with three selections, noticing how the copied selection that was full-line
 5388    // gets inserted before the second cursor.
 5389    cx.set_state(indoc! {"
 5390        1ˇ3
 5391 5392        «oˇ»ne"});
 5393    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5394    cx.assert_editor_state(indoc! {"
 5395        12ˇ3
 5396        4567
 5397 5398        8ˇne"});
 5399
 5400    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5401    cx.set_state(indoc! {"
 5402        The quick brown
 5403        fox juˇmps over
 5404        the lazy dog"});
 5405    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5406    assert_eq!(
 5407        cx.read_from_clipboard()
 5408            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5409        Some("fox jumps over\n".to_string())
 5410    );
 5411
 5412    // Paste with three selections, noticing how the copied full-line selection is inserted
 5413    // before the empty selections but replaces the selection that is non-empty.
 5414    cx.set_state(indoc! {"
 5415        Tˇhe quick brown
 5416        «foˇ»x jumps over
 5417        tˇhe lazy dog"});
 5418    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5419    cx.assert_editor_state(indoc! {"
 5420        fox jumps over
 5421        Tˇhe quick brown
 5422        fox jumps over
 5423        ˇx jumps over
 5424        fox jumps over
 5425        tˇhe lazy dog"});
 5426}
 5427
 5428#[gpui::test]
 5429async fn test_copy_trim(cx: &mut TestAppContext) {
 5430    init_test(cx, |_| {});
 5431
 5432    let mut cx = EditorTestContext::new(cx).await;
 5433    cx.set_state(
 5434        r#"            «for selection in selections.iter() {
 5435            let mut start = selection.start;
 5436            let mut end = selection.end;
 5437            let is_entire_line = selection.is_empty();
 5438            if is_entire_line {
 5439                start = Point::new(start.row, 0);ˇ»
 5440                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5441            }
 5442        "#,
 5443    );
 5444    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5445    assert_eq!(
 5446        cx.read_from_clipboard()
 5447            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5448        Some(
 5449            "for selection in selections.iter() {
 5450            let mut start = selection.start;
 5451            let mut end = selection.end;
 5452            let is_entire_line = selection.is_empty();
 5453            if is_entire_line {
 5454                start = Point::new(start.row, 0);"
 5455                .to_string()
 5456        ),
 5457        "Regular copying preserves all indentation selected",
 5458    );
 5459    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5460    assert_eq!(
 5461        cx.read_from_clipboard()
 5462            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5463        Some(
 5464            "for selection in selections.iter() {
 5465let mut start = selection.start;
 5466let mut end = selection.end;
 5467let is_entire_line = selection.is_empty();
 5468if is_entire_line {
 5469    start = Point::new(start.row, 0);"
 5470                .to_string()
 5471        ),
 5472        "Copying with stripping should strip all leading whitespaces"
 5473    );
 5474
 5475    cx.set_state(
 5476        r#"       «     for selection in selections.iter() {
 5477            let mut start = selection.start;
 5478            let mut end = selection.end;
 5479            let is_entire_line = selection.is_empty();
 5480            if is_entire_line {
 5481                start = Point::new(start.row, 0);ˇ»
 5482                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5483            }
 5484        "#,
 5485    );
 5486    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5487    assert_eq!(
 5488        cx.read_from_clipboard()
 5489            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5490        Some(
 5491            "     for selection in selections.iter() {
 5492            let mut start = selection.start;
 5493            let mut end = selection.end;
 5494            let is_entire_line = selection.is_empty();
 5495            if is_entire_line {
 5496                start = Point::new(start.row, 0);"
 5497                .to_string()
 5498        ),
 5499        "Regular copying preserves all indentation selected",
 5500    );
 5501    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5502    assert_eq!(
 5503        cx.read_from_clipboard()
 5504            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5505        Some(
 5506            "for selection in selections.iter() {
 5507let mut start = selection.start;
 5508let mut end = selection.end;
 5509let is_entire_line = selection.is_empty();
 5510if is_entire_line {
 5511    start = Point::new(start.row, 0);"
 5512                .to_string()
 5513        ),
 5514        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5515    );
 5516
 5517    cx.set_state(
 5518        r#"       «ˇ     for selection in selections.iter() {
 5519            let mut start = selection.start;
 5520            let mut end = selection.end;
 5521            let is_entire_line = selection.is_empty();
 5522            if is_entire_line {
 5523                start = Point::new(start.row, 0);»
 5524                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5525            }
 5526        "#,
 5527    );
 5528    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5529    assert_eq!(
 5530        cx.read_from_clipboard()
 5531            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5532        Some(
 5533            "     for selection in selections.iter() {
 5534            let mut start = selection.start;
 5535            let mut end = selection.end;
 5536            let is_entire_line = selection.is_empty();
 5537            if is_entire_line {
 5538                start = Point::new(start.row, 0);"
 5539                .to_string()
 5540        ),
 5541        "Regular copying for reverse selection works the same",
 5542    );
 5543    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5544    assert_eq!(
 5545        cx.read_from_clipboard()
 5546            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5547        Some(
 5548            "for selection in selections.iter() {
 5549let mut start = selection.start;
 5550let mut end = selection.end;
 5551let is_entire_line = selection.is_empty();
 5552if is_entire_line {
 5553    start = Point::new(start.row, 0);"
 5554                .to_string()
 5555        ),
 5556        "Copying with stripping for reverse selection works the same"
 5557    );
 5558
 5559    cx.set_state(
 5560        r#"            for selection «in selections.iter() {
 5561            let mut start = selection.start;
 5562            let mut end = selection.end;
 5563            let is_entire_line = selection.is_empty();
 5564            if is_entire_line {
 5565                start = Point::new(start.row, 0);ˇ»
 5566                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5567            }
 5568        "#,
 5569    );
 5570    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5571    assert_eq!(
 5572        cx.read_from_clipboard()
 5573            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5574        Some(
 5575            "in selections.iter() {
 5576            let mut start = selection.start;
 5577            let mut end = selection.end;
 5578            let is_entire_line = selection.is_empty();
 5579            if is_entire_line {
 5580                start = Point::new(start.row, 0);"
 5581                .to_string()
 5582        ),
 5583        "When selecting past the indent, the copying works as usual",
 5584    );
 5585    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5586    assert_eq!(
 5587        cx.read_from_clipboard()
 5588            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5589        Some(
 5590            "in selections.iter() {
 5591            let mut start = selection.start;
 5592            let mut end = selection.end;
 5593            let is_entire_line = selection.is_empty();
 5594            if is_entire_line {
 5595                start = Point::new(start.row, 0);"
 5596                .to_string()
 5597        ),
 5598        "When selecting past the indent, nothing is trimmed"
 5599    );
 5600
 5601    cx.set_state(
 5602        r#"            «for selection in selections.iter() {
 5603            let mut start = selection.start;
 5604
 5605            let mut end = selection.end;
 5606            let is_entire_line = selection.is_empty();
 5607            if is_entire_line {
 5608                start = Point::new(start.row, 0);
 5609ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5610            }
 5611        "#,
 5612    );
 5613    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5614    assert_eq!(
 5615        cx.read_from_clipboard()
 5616            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5617        Some(
 5618            "for selection in selections.iter() {
 5619let mut start = selection.start;
 5620
 5621let mut end = selection.end;
 5622let is_entire_line = selection.is_empty();
 5623if is_entire_line {
 5624    start = Point::new(start.row, 0);
 5625"
 5626            .to_string()
 5627        ),
 5628        "Copying with stripping should ignore empty lines"
 5629    );
 5630}
 5631
 5632#[gpui::test]
 5633async fn test_paste_multiline(cx: &mut TestAppContext) {
 5634    init_test(cx, |_| {});
 5635
 5636    let mut cx = EditorTestContext::new(cx).await;
 5637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5638
 5639    // Cut an indented block, without the leading whitespace.
 5640    cx.set_state(indoc! {"
 5641        const a: B = (
 5642            c(),
 5643            «d(
 5644                e,
 5645                f
 5646            )ˇ»
 5647        );
 5648    "});
 5649    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5650    cx.assert_editor_state(indoc! {"
 5651        const a: B = (
 5652            c(),
 5653            ˇ
 5654        );
 5655    "});
 5656
 5657    // Paste it at the same position.
 5658    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5659    cx.assert_editor_state(indoc! {"
 5660        const a: B = (
 5661            c(),
 5662            d(
 5663                e,
 5664                f
 5665 5666        );
 5667    "});
 5668
 5669    // Paste it at a line with a lower indent level.
 5670    cx.set_state(indoc! {"
 5671        ˇ
 5672        const a: B = (
 5673            c(),
 5674        );
 5675    "});
 5676    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5677    cx.assert_editor_state(indoc! {"
 5678        d(
 5679            e,
 5680            f
 5681 5682        const a: B = (
 5683            c(),
 5684        );
 5685    "});
 5686
 5687    // Cut an indented block, with the leading whitespace.
 5688    cx.set_state(indoc! {"
 5689        const a: B = (
 5690            c(),
 5691        «    d(
 5692                e,
 5693                f
 5694            )
 5695        ˇ»);
 5696    "});
 5697    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5698    cx.assert_editor_state(indoc! {"
 5699        const a: B = (
 5700            c(),
 5701        ˇ);
 5702    "});
 5703
 5704    // Paste it at the same position.
 5705    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5706    cx.assert_editor_state(indoc! {"
 5707        const a: B = (
 5708            c(),
 5709            d(
 5710                e,
 5711                f
 5712            )
 5713        ˇ);
 5714    "});
 5715
 5716    // Paste it at a line with a higher indent level.
 5717    cx.set_state(indoc! {"
 5718        const a: B = (
 5719            c(),
 5720            d(
 5721                e,
 5722 5723            )
 5724        );
 5725    "});
 5726    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5727    cx.assert_editor_state(indoc! {"
 5728        const a: B = (
 5729            c(),
 5730            d(
 5731                e,
 5732                f    d(
 5733                    e,
 5734                    f
 5735                )
 5736        ˇ
 5737            )
 5738        );
 5739    "});
 5740
 5741    // Copy an indented block, starting mid-line
 5742    cx.set_state(indoc! {"
 5743        const a: B = (
 5744            c(),
 5745            somethin«g(
 5746                e,
 5747                f
 5748            )ˇ»
 5749        );
 5750    "});
 5751    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5752
 5753    // Paste it on a line with a lower indent level
 5754    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5755    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5756    cx.assert_editor_state(indoc! {"
 5757        const a: B = (
 5758            c(),
 5759            something(
 5760                e,
 5761                f
 5762            )
 5763        );
 5764        g(
 5765            e,
 5766            f
 5767"});
 5768}
 5769
 5770#[gpui::test]
 5771async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5772    init_test(cx, |_| {});
 5773
 5774    cx.write_to_clipboard(ClipboardItem::new_string(
 5775        "    d(\n        e\n    );\n".into(),
 5776    ));
 5777
 5778    let mut cx = EditorTestContext::new(cx).await;
 5779    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5780
 5781    cx.set_state(indoc! {"
 5782        fn a() {
 5783            b();
 5784            if c() {
 5785                ˇ
 5786            }
 5787        }
 5788    "});
 5789
 5790    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5791    cx.assert_editor_state(indoc! {"
 5792        fn a() {
 5793            b();
 5794            if c() {
 5795                d(
 5796                    e
 5797                );
 5798        ˇ
 5799            }
 5800        }
 5801    "});
 5802
 5803    cx.set_state(indoc! {"
 5804        fn a() {
 5805            b();
 5806            ˇ
 5807        }
 5808    "});
 5809
 5810    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5811    cx.assert_editor_state(indoc! {"
 5812        fn a() {
 5813            b();
 5814            d(
 5815                e
 5816            );
 5817        ˇ
 5818        }
 5819    "});
 5820}
 5821
 5822#[gpui::test]
 5823fn test_select_all(cx: &mut TestAppContext) {
 5824    init_test(cx, |_| {});
 5825
 5826    let editor = cx.add_window(|window, cx| {
 5827        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5828        build_editor(buffer, window, cx)
 5829    });
 5830    _ = editor.update(cx, |editor, window, cx| {
 5831        editor.select_all(&SelectAll, window, cx);
 5832        assert_eq!(
 5833            editor.selections.display_ranges(cx),
 5834            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5835        );
 5836    });
 5837}
 5838
 5839#[gpui::test]
 5840fn test_select_line(cx: &mut TestAppContext) {
 5841    init_test(cx, |_| {});
 5842
 5843    let editor = cx.add_window(|window, cx| {
 5844        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5845        build_editor(buffer, window, cx)
 5846    });
 5847    _ = editor.update(cx, |editor, window, cx| {
 5848        editor.change_selections(None, window, cx, |s| {
 5849            s.select_display_ranges([
 5850                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5851                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5852                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5853                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5854            ])
 5855        });
 5856        editor.select_line(&SelectLine, window, cx);
 5857        assert_eq!(
 5858            editor.selections.display_ranges(cx),
 5859            vec![
 5860                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5861                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5862            ]
 5863        );
 5864    });
 5865
 5866    _ = editor.update(cx, |editor, window, cx| {
 5867        editor.select_line(&SelectLine, window, cx);
 5868        assert_eq!(
 5869            editor.selections.display_ranges(cx),
 5870            vec![
 5871                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5872                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5873            ]
 5874        );
 5875    });
 5876
 5877    _ = editor.update(cx, |editor, window, cx| {
 5878        editor.select_line(&SelectLine, window, cx);
 5879        assert_eq!(
 5880            editor.selections.display_ranges(cx),
 5881            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5882        );
 5883    });
 5884}
 5885
 5886#[gpui::test]
 5887async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5888    init_test(cx, |_| {});
 5889    let mut cx = EditorTestContext::new(cx).await;
 5890
 5891    #[track_caller]
 5892    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5893        cx.set_state(initial_state);
 5894        cx.update_editor(|e, window, cx| {
 5895            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5896        });
 5897        cx.assert_editor_state(expected_state);
 5898    }
 5899
 5900    // Selection starts and ends at the middle of lines, left-to-right
 5901    test(
 5902        &mut cx,
 5903        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5904        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5905    );
 5906    // Same thing, right-to-left
 5907    test(
 5908        &mut cx,
 5909        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5910        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5911    );
 5912
 5913    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5914    test(
 5915        &mut cx,
 5916        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5917        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5918    );
 5919    // Same thing, right-to-left
 5920    test(
 5921        &mut cx,
 5922        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5923        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5924    );
 5925
 5926    // Whole buffer, left-to-right, last line ends with newline
 5927    test(
 5928        &mut cx,
 5929        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5930        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5931    );
 5932    // Same thing, right-to-left
 5933    test(
 5934        &mut cx,
 5935        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5936        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5937    );
 5938
 5939    // Starts at the end of a line, ends at the start of another
 5940    test(
 5941        &mut cx,
 5942        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5943        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5944    );
 5945}
 5946
 5947#[gpui::test]
 5948async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5949    init_test(cx, |_| {});
 5950
 5951    let editor = cx.add_window(|window, cx| {
 5952        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5953        build_editor(buffer, window, cx)
 5954    });
 5955
 5956    // setup
 5957    _ = editor.update(cx, |editor, window, cx| {
 5958        editor.fold_creases(
 5959            vec![
 5960                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5961                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5962                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5963            ],
 5964            true,
 5965            window,
 5966            cx,
 5967        );
 5968        assert_eq!(
 5969            editor.display_text(cx),
 5970            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5971        );
 5972    });
 5973
 5974    _ = editor.update(cx, |editor, window, cx| {
 5975        editor.change_selections(None, window, cx, |s| {
 5976            s.select_display_ranges([
 5977                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5978                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5979                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5980                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5981            ])
 5982        });
 5983        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5984        assert_eq!(
 5985            editor.display_text(cx),
 5986            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5987        );
 5988    });
 5989    EditorTestContext::for_editor(editor, cx)
 5990        .await
 5991        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5992
 5993    _ = editor.update(cx, |editor, window, cx| {
 5994        editor.change_selections(None, window, cx, |s| {
 5995            s.select_display_ranges([
 5996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5997            ])
 5998        });
 5999        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6000        assert_eq!(
 6001            editor.display_text(cx),
 6002            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6003        );
 6004        assert_eq!(
 6005            editor.selections.display_ranges(cx),
 6006            [
 6007                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6008                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6009                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6010                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6011                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6012                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6013                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6014            ]
 6015        );
 6016    });
 6017    EditorTestContext::for_editor(editor, cx)
 6018        .await
 6019        .assert_editor_state(
 6020            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6021        );
 6022}
 6023
 6024#[gpui::test]
 6025async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6026    init_test(cx, |_| {});
 6027
 6028    let mut cx = EditorTestContext::new(cx).await;
 6029
 6030    cx.set_state(indoc!(
 6031        r#"abc
 6032           defˇghi
 6033
 6034           jk
 6035           nlmo
 6036           "#
 6037    ));
 6038
 6039    cx.update_editor(|editor, window, cx| {
 6040        editor.add_selection_above(&Default::default(), window, cx);
 6041    });
 6042
 6043    cx.assert_editor_state(indoc!(
 6044        r#"abcˇ
 6045           defˇghi
 6046
 6047           jk
 6048           nlmo
 6049           "#
 6050    ));
 6051
 6052    cx.update_editor(|editor, window, cx| {
 6053        editor.add_selection_above(&Default::default(), window, cx);
 6054    });
 6055
 6056    cx.assert_editor_state(indoc!(
 6057        r#"abcˇ
 6058            defˇghi
 6059
 6060            jk
 6061            nlmo
 6062            "#
 6063    ));
 6064
 6065    cx.update_editor(|editor, window, cx| {
 6066        editor.add_selection_below(&Default::default(), window, cx);
 6067    });
 6068
 6069    cx.assert_editor_state(indoc!(
 6070        r#"abc
 6071           defˇghi
 6072
 6073           jk
 6074           nlmo
 6075           "#
 6076    ));
 6077
 6078    cx.update_editor(|editor, window, cx| {
 6079        editor.undo_selection(&Default::default(), window, cx);
 6080    });
 6081
 6082    cx.assert_editor_state(indoc!(
 6083        r#"abcˇ
 6084           defˇghi
 6085
 6086           jk
 6087           nlmo
 6088           "#
 6089    ));
 6090
 6091    cx.update_editor(|editor, window, cx| {
 6092        editor.redo_selection(&Default::default(), window, cx);
 6093    });
 6094
 6095    cx.assert_editor_state(indoc!(
 6096        r#"abc
 6097           defˇghi
 6098
 6099           jk
 6100           nlmo
 6101           "#
 6102    ));
 6103
 6104    cx.update_editor(|editor, window, cx| {
 6105        editor.add_selection_below(&Default::default(), window, cx);
 6106    });
 6107
 6108    cx.assert_editor_state(indoc!(
 6109        r#"abc
 6110           defˇghi
 6111           ˇ
 6112           jk
 6113           nlmo
 6114           "#
 6115    ));
 6116
 6117    cx.update_editor(|editor, window, cx| {
 6118        editor.add_selection_below(&Default::default(), window, cx);
 6119    });
 6120
 6121    cx.assert_editor_state(indoc!(
 6122        r#"abc
 6123           defˇghi
 6124           ˇ
 6125           jkˇ
 6126           nlmo
 6127           "#
 6128    ));
 6129
 6130    cx.update_editor(|editor, window, cx| {
 6131        editor.add_selection_below(&Default::default(), window, cx);
 6132    });
 6133
 6134    cx.assert_editor_state(indoc!(
 6135        r#"abc
 6136           defˇghi
 6137           ˇ
 6138           jkˇ
 6139           nlmˇo
 6140           "#
 6141    ));
 6142
 6143    cx.update_editor(|editor, window, cx| {
 6144        editor.add_selection_below(&Default::default(), window, cx);
 6145    });
 6146
 6147    cx.assert_editor_state(indoc!(
 6148        r#"abc
 6149           defˇghi
 6150           ˇ
 6151           jkˇ
 6152           nlmˇo
 6153           ˇ"#
 6154    ));
 6155
 6156    // change selections
 6157    cx.set_state(indoc!(
 6158        r#"abc
 6159           def«ˇg»hi
 6160
 6161           jk
 6162           nlmo
 6163           "#
 6164    ));
 6165
 6166    cx.update_editor(|editor, window, cx| {
 6167        editor.add_selection_below(&Default::default(), window, cx);
 6168    });
 6169
 6170    cx.assert_editor_state(indoc!(
 6171        r#"abc
 6172           def«ˇg»hi
 6173
 6174           jk
 6175           nlm«ˇo»
 6176           "#
 6177    ));
 6178
 6179    cx.update_editor(|editor, window, cx| {
 6180        editor.add_selection_below(&Default::default(), window, cx);
 6181    });
 6182
 6183    cx.assert_editor_state(indoc!(
 6184        r#"abc
 6185           def«ˇg»hi
 6186
 6187           jk
 6188           nlm«ˇo»
 6189           "#
 6190    ));
 6191
 6192    cx.update_editor(|editor, window, cx| {
 6193        editor.add_selection_above(&Default::default(), window, cx);
 6194    });
 6195
 6196    cx.assert_editor_state(indoc!(
 6197        r#"abc
 6198           def«ˇg»hi
 6199
 6200           jk
 6201           nlmo
 6202           "#
 6203    ));
 6204
 6205    cx.update_editor(|editor, window, cx| {
 6206        editor.add_selection_above(&Default::default(), window, cx);
 6207    });
 6208
 6209    cx.assert_editor_state(indoc!(
 6210        r#"abc
 6211           def«ˇg»hi
 6212
 6213           jk
 6214           nlmo
 6215           "#
 6216    ));
 6217
 6218    // Change selections again
 6219    cx.set_state(indoc!(
 6220        r#"a«bc
 6221           defgˇ»hi
 6222
 6223           jk
 6224           nlmo
 6225           "#
 6226    ));
 6227
 6228    cx.update_editor(|editor, window, cx| {
 6229        editor.add_selection_below(&Default::default(), window, cx);
 6230    });
 6231
 6232    cx.assert_editor_state(indoc!(
 6233        r#"a«bcˇ»
 6234           d«efgˇ»hi
 6235
 6236           j«kˇ»
 6237           nlmo
 6238           "#
 6239    ));
 6240
 6241    cx.update_editor(|editor, window, cx| {
 6242        editor.add_selection_below(&Default::default(), window, cx);
 6243    });
 6244    cx.assert_editor_state(indoc!(
 6245        r#"a«bcˇ»
 6246           d«efgˇ»hi
 6247
 6248           j«kˇ»
 6249           n«lmoˇ»
 6250           "#
 6251    ));
 6252    cx.update_editor(|editor, window, cx| {
 6253        editor.add_selection_above(&Default::default(), window, cx);
 6254    });
 6255
 6256    cx.assert_editor_state(indoc!(
 6257        r#"a«bcˇ»
 6258           d«efgˇ»hi
 6259
 6260           j«kˇ»
 6261           nlmo
 6262           "#
 6263    ));
 6264
 6265    // Change selections again
 6266    cx.set_state(indoc!(
 6267        r#"abc
 6268           d«ˇefghi
 6269
 6270           jk
 6271           nlm»o
 6272           "#
 6273    ));
 6274
 6275    cx.update_editor(|editor, window, cx| {
 6276        editor.add_selection_above(&Default::default(), window, cx);
 6277    });
 6278
 6279    cx.assert_editor_state(indoc!(
 6280        r#"a«ˇbc»
 6281           d«ˇef»ghi
 6282
 6283           j«ˇk»
 6284           n«ˇlm»o
 6285           "#
 6286    ));
 6287
 6288    cx.update_editor(|editor, window, cx| {
 6289        editor.add_selection_below(&Default::default(), window, cx);
 6290    });
 6291
 6292    cx.assert_editor_state(indoc!(
 6293        r#"abc
 6294           d«ˇef»ghi
 6295
 6296           j«ˇk»
 6297           n«ˇlm»o
 6298           "#
 6299    ));
 6300}
 6301
 6302#[gpui::test]
 6303async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6304    init_test(cx, |_| {});
 6305    let mut cx = EditorTestContext::new(cx).await;
 6306
 6307    cx.set_state(indoc!(
 6308        r#"line onˇe
 6309           liˇne two
 6310           line three
 6311           line four"#
 6312    ));
 6313
 6314    cx.update_editor(|editor, window, cx| {
 6315        editor.add_selection_below(&Default::default(), window, cx);
 6316    });
 6317
 6318    // test multiple cursors expand in the same direction
 6319    cx.assert_editor_state(indoc!(
 6320        r#"line onˇe
 6321           liˇne twˇo
 6322           liˇne three
 6323           line four"#
 6324    ));
 6325
 6326    cx.update_editor(|editor, window, cx| {
 6327        editor.add_selection_below(&Default::default(), window, cx);
 6328    });
 6329
 6330    cx.update_editor(|editor, window, cx| {
 6331        editor.add_selection_below(&Default::default(), window, cx);
 6332    });
 6333
 6334    // test multiple cursors expand below overflow
 6335    cx.assert_editor_state(indoc!(
 6336        r#"line onˇe
 6337           liˇne twˇo
 6338           liˇne thˇree
 6339           liˇne foˇur"#
 6340    ));
 6341
 6342    cx.update_editor(|editor, window, cx| {
 6343        editor.add_selection_above(&Default::default(), window, cx);
 6344    });
 6345
 6346    // test multiple cursors retrieves back correctly
 6347    cx.assert_editor_state(indoc!(
 6348        r#"line onˇe
 6349           liˇne twˇo
 6350           liˇne thˇree
 6351           line four"#
 6352    ));
 6353
 6354    cx.update_editor(|editor, window, cx| {
 6355        editor.add_selection_above(&Default::default(), window, cx);
 6356    });
 6357
 6358    cx.update_editor(|editor, window, cx| {
 6359        editor.add_selection_above(&Default::default(), window, cx);
 6360    });
 6361
 6362    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6363    cx.assert_editor_state(indoc!(
 6364        r#"liˇne onˇe
 6365           liˇne two
 6366           line three
 6367           line four"#
 6368    ));
 6369
 6370    cx.update_editor(|editor, window, cx| {
 6371        editor.undo_selection(&Default::default(), window, cx);
 6372    });
 6373
 6374    // test undo
 6375    cx.assert_editor_state(indoc!(
 6376        r#"line onˇe
 6377           liˇne twˇo
 6378           line three
 6379           line four"#
 6380    ));
 6381
 6382    cx.update_editor(|editor, window, cx| {
 6383        editor.redo_selection(&Default::default(), window, cx);
 6384    });
 6385
 6386    // test redo
 6387    cx.assert_editor_state(indoc!(
 6388        r#"liˇne onˇe
 6389           liˇne two
 6390           line three
 6391           line four"#
 6392    ));
 6393
 6394    cx.set_state(indoc!(
 6395        r#"abcd
 6396           ef«ghˇ»
 6397           ijkl
 6398           «mˇ»nop"#
 6399    ));
 6400
 6401    cx.update_editor(|editor, window, cx| {
 6402        editor.add_selection_above(&Default::default(), window, cx);
 6403    });
 6404
 6405    // test multiple selections expand in the same direction
 6406    cx.assert_editor_state(indoc!(
 6407        r#"ab«cdˇ»
 6408           ef«ghˇ»
 6409           «iˇ»jkl
 6410           «mˇ»nop"#
 6411    ));
 6412
 6413    cx.update_editor(|editor, window, cx| {
 6414        editor.add_selection_above(&Default::default(), window, cx);
 6415    });
 6416
 6417    // test multiple selection upward overflow
 6418    cx.assert_editor_state(indoc!(
 6419        r#"ab«cdˇ»
 6420           «eˇ»f«ghˇ»
 6421           «iˇ»jkl
 6422           «mˇ»nop"#
 6423    ));
 6424
 6425    cx.update_editor(|editor, window, cx| {
 6426        editor.add_selection_below(&Default::default(), window, cx);
 6427    });
 6428
 6429    // test multiple selection retrieves back correctly
 6430    cx.assert_editor_state(indoc!(
 6431        r#"abcd
 6432           ef«ghˇ»
 6433           «iˇ»jkl
 6434           «mˇ»nop"#
 6435    ));
 6436
 6437    cx.update_editor(|editor, window, cx| {
 6438        editor.add_selection_below(&Default::default(), window, cx);
 6439    });
 6440
 6441    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6442    cx.assert_editor_state(indoc!(
 6443        r#"abcd
 6444           ef«ghˇ»
 6445           ij«klˇ»
 6446           «mˇ»nop"#
 6447    ));
 6448
 6449    cx.update_editor(|editor, window, cx| {
 6450        editor.undo_selection(&Default::default(), window, cx);
 6451    });
 6452
 6453    // test undo
 6454    cx.assert_editor_state(indoc!(
 6455        r#"abcd
 6456           ef«ghˇ»
 6457           «iˇ»jkl
 6458           «mˇ»nop"#
 6459    ));
 6460
 6461    cx.update_editor(|editor, window, cx| {
 6462        editor.redo_selection(&Default::default(), window, cx);
 6463    });
 6464
 6465    // test redo
 6466    cx.assert_editor_state(indoc!(
 6467        r#"abcd
 6468           ef«ghˇ»
 6469           ij«klˇ»
 6470           «mˇ»nop"#
 6471    ));
 6472}
 6473
 6474#[gpui::test]
 6475async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6476    init_test(cx, |_| {});
 6477    let mut cx = EditorTestContext::new(cx).await;
 6478
 6479    cx.set_state(indoc!(
 6480        r#"line onˇe
 6481           liˇne two
 6482           line three
 6483           line four"#
 6484    ));
 6485
 6486    cx.update_editor(|editor, window, cx| {
 6487        editor.add_selection_below(&Default::default(), window, cx);
 6488        editor.add_selection_below(&Default::default(), window, cx);
 6489        editor.add_selection_below(&Default::default(), window, cx);
 6490    });
 6491
 6492    // initial state with two multi cursor groups
 6493    cx.assert_editor_state(indoc!(
 6494        r#"line onˇe
 6495           liˇne twˇo
 6496           liˇne thˇree
 6497           liˇne foˇur"#
 6498    ));
 6499
 6500    // add single cursor in middle - simulate opt click
 6501    cx.update_editor(|editor, window, cx| {
 6502        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6503        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6504        editor.end_selection(window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"line onˇe
 6509           liˇne twˇo
 6510           liˇneˇ thˇree
 6511           liˇne foˇur"#
 6512    ));
 6513
 6514    cx.update_editor(|editor, window, cx| {
 6515        editor.add_selection_above(&Default::default(), window, cx);
 6516    });
 6517
 6518    // test new added selection expands above and existing selection shrinks
 6519    cx.assert_editor_state(indoc!(
 6520        r#"line onˇe
 6521           liˇneˇ twˇo
 6522           liˇneˇ thˇree
 6523           line four"#
 6524    ));
 6525
 6526    cx.update_editor(|editor, window, cx| {
 6527        editor.add_selection_above(&Default::default(), window, cx);
 6528    });
 6529
 6530    // test new added selection expands above and existing selection shrinks
 6531    cx.assert_editor_state(indoc!(
 6532        r#"lineˇ onˇe
 6533           liˇneˇ twˇo
 6534           lineˇ three
 6535           line four"#
 6536    ));
 6537
 6538    // intial state with two selection groups
 6539    cx.set_state(indoc!(
 6540        r#"abcd
 6541           ef«ghˇ»
 6542           ijkl
 6543           «mˇ»nop"#
 6544    ));
 6545
 6546    cx.update_editor(|editor, window, cx| {
 6547        editor.add_selection_above(&Default::default(), window, cx);
 6548        editor.add_selection_above(&Default::default(), window, cx);
 6549    });
 6550
 6551    cx.assert_editor_state(indoc!(
 6552        r#"ab«cdˇ»
 6553           «eˇ»f«ghˇ»
 6554           «iˇ»jkl
 6555           «mˇ»nop"#
 6556    ));
 6557
 6558    // add single selection in middle - simulate opt drag
 6559    cx.update_editor(|editor, window, cx| {
 6560        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6561        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6562        editor.update_selection(
 6563            DisplayPoint::new(DisplayRow(2), 4),
 6564            0,
 6565            gpui::Point::<f32>::default(),
 6566            window,
 6567            cx,
 6568        );
 6569        editor.end_selection(window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"ab«cdˇ»
 6574           «eˇ»f«ghˇ»
 6575           «iˇ»jk«lˇ»
 6576           «mˇ»nop"#
 6577    ));
 6578
 6579    cx.update_editor(|editor, window, cx| {
 6580        editor.add_selection_below(&Default::default(), window, cx);
 6581    });
 6582
 6583    // test new added selection expands below, others shrinks from above
 6584    cx.assert_editor_state(indoc!(
 6585        r#"abcd
 6586           ef«ghˇ»
 6587           «iˇ»jk«lˇ»
 6588           «mˇ»no«pˇ»"#
 6589    ));
 6590}
 6591
 6592#[gpui::test]
 6593async fn test_select_next(cx: &mut TestAppContext) {
 6594    init_test(cx, |_| {});
 6595
 6596    let mut cx = EditorTestContext::new(cx).await;
 6597    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6598
 6599    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6600        .unwrap();
 6601    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6602
 6603    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6604        .unwrap();
 6605    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6606
 6607    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6608    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6609
 6610    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6611    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6612
 6613    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6614        .unwrap();
 6615    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6616
 6617    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6618        .unwrap();
 6619    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6620
 6621    // Test selection direction should be preserved
 6622    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6623
 6624    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6625        .unwrap();
 6626    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6627}
 6628
 6629#[gpui::test]
 6630async fn test_select_all_matches(cx: &mut TestAppContext) {
 6631    init_test(cx, |_| {});
 6632
 6633    let mut cx = EditorTestContext::new(cx).await;
 6634
 6635    // Test caret-only selections
 6636    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6637    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6638        .unwrap();
 6639    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6640
 6641    // Test left-to-right selections
 6642    cx.set_state("abc\n«abcˇ»\nabc");
 6643    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6644        .unwrap();
 6645    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6646
 6647    // Test right-to-left selections
 6648    cx.set_state("abc\n«ˇabc»\nabc");
 6649    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6650        .unwrap();
 6651    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6652
 6653    // Test selecting whitespace with caret selection
 6654    cx.set_state("abc\nˇ   abc\nabc");
 6655    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6656        .unwrap();
 6657    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6658
 6659    // Test selecting whitespace with left-to-right selection
 6660    cx.set_state("abc\n«ˇ  »abc\nabc");
 6661    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6662        .unwrap();
 6663    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6664
 6665    // Test no matches with right-to-left selection
 6666    cx.set_state("abc\n«  ˇ»abc\nabc");
 6667    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6668        .unwrap();
 6669    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6670}
 6671
 6672#[gpui::test]
 6673async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6674    init_test(cx, |_| {});
 6675
 6676    let mut cx = EditorTestContext::new(cx).await;
 6677
 6678    let large_body_1 = "\nd".repeat(200);
 6679    let large_body_2 = "\ne".repeat(200);
 6680
 6681    cx.set_state(&format!(
 6682        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6683    ));
 6684    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6685        let scroll_position = editor.scroll_position(cx);
 6686        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6687        scroll_position
 6688    });
 6689
 6690    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6691        .unwrap();
 6692    cx.assert_editor_state(&format!(
 6693        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6694    ));
 6695    let scroll_position_after_selection =
 6696        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6697    assert_eq!(
 6698        initial_scroll_position, scroll_position_after_selection,
 6699        "Scroll position should not change after selecting all matches"
 6700    );
 6701}
 6702
 6703#[gpui::test]
 6704async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6705    init_test(cx, |_| {});
 6706
 6707    let mut cx = EditorLspTestContext::new_rust(
 6708        lsp::ServerCapabilities {
 6709            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6710            ..Default::default()
 6711        },
 6712        cx,
 6713    )
 6714    .await;
 6715
 6716    cx.set_state(indoc! {"
 6717        line 1
 6718        line 2
 6719        linˇe 3
 6720        line 4
 6721        line 5
 6722    "});
 6723
 6724    // Make an edit
 6725    cx.update_editor(|editor, window, cx| {
 6726        editor.handle_input("X", window, cx);
 6727    });
 6728
 6729    // Move cursor to a different position
 6730    cx.update_editor(|editor, window, cx| {
 6731        editor.change_selections(None, window, cx, |s| {
 6732            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6733        });
 6734    });
 6735
 6736    cx.assert_editor_state(indoc! {"
 6737        line 1
 6738        line 2
 6739        linXe 3
 6740        line 4
 6741        liˇne 5
 6742    "});
 6743
 6744    cx.lsp
 6745        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6746            Ok(Some(vec![lsp::TextEdit::new(
 6747                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6748                "PREFIX ".to_string(),
 6749            )]))
 6750        });
 6751
 6752    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6753        .unwrap()
 6754        .await
 6755        .unwrap();
 6756
 6757    cx.assert_editor_state(indoc! {"
 6758        PREFIX line 1
 6759        line 2
 6760        linXe 3
 6761        line 4
 6762        liˇne 5
 6763    "});
 6764
 6765    // Undo formatting
 6766    cx.update_editor(|editor, window, cx| {
 6767        editor.undo(&Default::default(), window, cx);
 6768    });
 6769
 6770    // Verify cursor moved back to position after edit
 6771    cx.assert_editor_state(indoc! {"
 6772        line 1
 6773        line 2
 6774        linXˇe 3
 6775        line 4
 6776        line 5
 6777    "});
 6778}
 6779
 6780#[gpui::test]
 6781async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6782    init_test(cx, |_| {});
 6783
 6784    let mut cx = EditorTestContext::new(cx).await;
 6785
 6786    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6787    cx.update_editor(|editor, window, cx| {
 6788        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6789    });
 6790
 6791    cx.set_state(indoc! {"
 6792        line 1
 6793        line 2
 6794        linˇe 3
 6795        line 4
 6796        line 5
 6797        line 6
 6798        line 7
 6799        line 8
 6800        line 9
 6801        line 10
 6802    "});
 6803
 6804    let snapshot = cx.buffer_snapshot();
 6805    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6806
 6807    cx.update(|_, cx| {
 6808        provider.update(cx, |provider, _| {
 6809            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6810                id: None,
 6811                edits: vec![(edit_position..edit_position, "X".into())],
 6812                edit_preview: None,
 6813            }))
 6814        })
 6815    });
 6816
 6817    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6820    });
 6821
 6822    cx.assert_editor_state(indoc! {"
 6823        line 1
 6824        line 2
 6825        lineXˇ 3
 6826        line 4
 6827        line 5
 6828        line 6
 6829        line 7
 6830        line 8
 6831        line 9
 6832        line 10
 6833    "});
 6834
 6835    cx.update_editor(|editor, window, cx| {
 6836        editor.change_selections(None, window, cx, |s| {
 6837            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6838        });
 6839    });
 6840
 6841    cx.assert_editor_state(indoc! {"
 6842        line 1
 6843        line 2
 6844        lineX 3
 6845        line 4
 6846        line 5
 6847        line 6
 6848        line 7
 6849        line 8
 6850        line 9
 6851        liˇne 10
 6852    "});
 6853
 6854    cx.update_editor(|editor, window, cx| {
 6855        editor.undo(&Default::default(), window, cx);
 6856    });
 6857
 6858    cx.assert_editor_state(indoc! {"
 6859        line 1
 6860        line 2
 6861        lineˇ 3
 6862        line 4
 6863        line 5
 6864        line 6
 6865        line 7
 6866        line 8
 6867        line 9
 6868        line 10
 6869    "});
 6870}
 6871
 6872#[gpui::test]
 6873async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6874    init_test(cx, |_| {});
 6875
 6876    let mut cx = EditorTestContext::new(cx).await;
 6877    cx.set_state(
 6878        r#"let foo = 2;
 6879lˇet foo = 2;
 6880let fooˇ = 2;
 6881let foo = 2;
 6882let foo = ˇ2;"#,
 6883    );
 6884
 6885    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6886        .unwrap();
 6887    cx.assert_editor_state(
 6888        r#"let foo = 2;
 6889«letˇ» foo = 2;
 6890let «fooˇ» = 2;
 6891let foo = 2;
 6892let foo = «2ˇ»;"#,
 6893    );
 6894
 6895    // noop for multiple selections with different contents
 6896    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6897        .unwrap();
 6898    cx.assert_editor_state(
 6899        r#"let foo = 2;
 6900«letˇ» foo = 2;
 6901let «fooˇ» = 2;
 6902let foo = 2;
 6903let foo = «2ˇ»;"#,
 6904    );
 6905
 6906    // Test last selection direction should be preserved
 6907    cx.set_state(
 6908        r#"let foo = 2;
 6909let foo = 2;
 6910let «fooˇ» = 2;
 6911let «ˇfoo» = 2;
 6912let foo = 2;"#,
 6913    );
 6914
 6915    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6916        .unwrap();
 6917    cx.assert_editor_state(
 6918        r#"let foo = 2;
 6919let foo = 2;
 6920let «fooˇ» = 2;
 6921let «ˇfoo» = 2;
 6922let «ˇfoo» = 2;"#,
 6923    );
 6924}
 6925
 6926#[gpui::test]
 6927async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6928    init_test(cx, |_| {});
 6929
 6930    let mut cx =
 6931        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6932
 6933    cx.assert_editor_state(indoc! {"
 6934        ˇbbb
 6935        ccc
 6936
 6937        bbb
 6938        ccc
 6939        "});
 6940    cx.dispatch_action(SelectPrevious::default());
 6941    cx.assert_editor_state(indoc! {"
 6942                «bbbˇ»
 6943                ccc
 6944
 6945                bbb
 6946                ccc
 6947                "});
 6948    cx.dispatch_action(SelectPrevious::default());
 6949    cx.assert_editor_state(indoc! {"
 6950                «bbbˇ»
 6951                ccc
 6952
 6953                «bbbˇ»
 6954                ccc
 6955                "});
 6956}
 6957
 6958#[gpui::test]
 6959async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6960    init_test(cx, |_| {});
 6961
 6962    let mut cx = EditorTestContext::new(cx).await;
 6963    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6964
 6965    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6966        .unwrap();
 6967    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6968
 6969    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6970        .unwrap();
 6971    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6972
 6973    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6974    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6975
 6976    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6977    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6978
 6979    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6980        .unwrap();
 6981    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6982
 6983    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6984        .unwrap();
 6985    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6986}
 6987
 6988#[gpui::test]
 6989async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6990    init_test(cx, |_| {});
 6991
 6992    let mut cx = EditorTestContext::new(cx).await;
 6993    cx.set_state("");
 6994
 6995    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6996        .unwrap();
 6997    cx.assert_editor_state("«aˇ»");
 6998    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6999        .unwrap();
 7000    cx.assert_editor_state("«aˇ»");
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorTestContext::new(cx).await;
 7008    cx.set_state(
 7009        r#"let foo = 2;
 7010lˇet foo = 2;
 7011let fooˇ = 2;
 7012let foo = 2;
 7013let foo = ˇ2;"#,
 7014    );
 7015
 7016    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7017        .unwrap();
 7018    cx.assert_editor_state(
 7019        r#"let foo = 2;
 7020«letˇ» foo = 2;
 7021let «fooˇ» = 2;
 7022let foo = 2;
 7023let foo = «2ˇ»;"#,
 7024    );
 7025
 7026    // noop for multiple selections with different contents
 7027    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7028        .unwrap();
 7029    cx.assert_editor_state(
 7030        r#"let foo = 2;
 7031«letˇ» foo = 2;
 7032let «fooˇ» = 2;
 7033let foo = 2;
 7034let foo = «2ˇ»;"#,
 7035    );
 7036}
 7037
 7038#[gpui::test]
 7039async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7040    init_test(cx, |_| {});
 7041
 7042    let mut cx = EditorTestContext::new(cx).await;
 7043    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7044
 7045    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7046        .unwrap();
 7047    // selection direction is preserved
 7048    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7049
 7050    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7051        .unwrap();
 7052    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7053
 7054    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7055    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7056
 7057    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7058    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7059
 7060    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7061        .unwrap();
 7062    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7063
 7064    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7065        .unwrap();
 7066    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7067}
 7068
 7069#[gpui::test]
 7070async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7071    init_test(cx, |_| {});
 7072
 7073    let language = Arc::new(Language::new(
 7074        LanguageConfig::default(),
 7075        Some(tree_sitter_rust::LANGUAGE.into()),
 7076    ));
 7077
 7078    let text = r#"
 7079        use mod1::mod2::{mod3, mod4};
 7080
 7081        fn fn_1(param1: bool, param2: &str) {
 7082            let var1 = "text";
 7083        }
 7084    "#
 7085    .unindent();
 7086
 7087    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7088    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7089    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7090
 7091    editor
 7092        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7093        .await;
 7094
 7095    editor.update_in(cx, |editor, window, cx| {
 7096        editor.change_selections(None, window, cx, |s| {
 7097            s.select_display_ranges([
 7098                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7099                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7100                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7101            ]);
 7102        });
 7103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7104    });
 7105    editor.update(cx, |editor, cx| {
 7106        assert_text_with_selections(
 7107            editor,
 7108            indoc! {r#"
 7109                use mod1::mod2::{mod3, «mod4ˇ»};
 7110
 7111                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7112                    let var1 = "«ˇtext»";
 7113                }
 7114            "#},
 7115            cx,
 7116        );
 7117    });
 7118
 7119    editor.update_in(cx, |editor, window, cx| {
 7120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7121    });
 7122    editor.update(cx, |editor, cx| {
 7123        assert_text_with_selections(
 7124            editor,
 7125            indoc! {r#"
 7126                use mod1::mod2::«{mod3, mod4}ˇ»;
 7127
 7128                «ˇfn fn_1(param1: bool, param2: &str) {
 7129                    let var1 = "text";
 7130 7131            "#},
 7132            cx,
 7133        );
 7134    });
 7135
 7136    editor.update_in(cx, |editor, window, cx| {
 7137        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7138    });
 7139    assert_eq!(
 7140        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7141        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7142    );
 7143
 7144    // Trying to expand the selected syntax node one more time has no effect.
 7145    editor.update_in(cx, |editor, window, cx| {
 7146        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7147    });
 7148    assert_eq!(
 7149        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7150        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7151    );
 7152
 7153    editor.update_in(cx, |editor, window, cx| {
 7154        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7155    });
 7156    editor.update(cx, |editor, cx| {
 7157        assert_text_with_selections(
 7158            editor,
 7159            indoc! {r#"
 7160                use mod1::mod2::«{mod3, mod4}ˇ»;
 7161
 7162                «ˇfn fn_1(param1: bool, param2: &str) {
 7163                    let var1 = "text";
 7164 7165            "#},
 7166            cx,
 7167        );
 7168    });
 7169
 7170    editor.update_in(cx, |editor, window, cx| {
 7171        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7172    });
 7173    editor.update(cx, |editor, cx| {
 7174        assert_text_with_selections(
 7175            editor,
 7176            indoc! {r#"
 7177                use mod1::mod2::{mod3, «mod4ˇ»};
 7178
 7179                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7180                    let var1 = "«ˇtext»";
 7181                }
 7182            "#},
 7183            cx,
 7184        );
 7185    });
 7186
 7187    editor.update_in(cx, |editor, window, cx| {
 7188        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7189    });
 7190    editor.update(cx, |editor, cx| {
 7191        assert_text_with_selections(
 7192            editor,
 7193            indoc! {r#"
 7194                use mod1::mod2::{mod3, mo«ˇ»d4};
 7195
 7196                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7197                    let var1 = "te«ˇ»xt";
 7198                }
 7199            "#},
 7200            cx,
 7201        );
 7202    });
 7203
 7204    // Trying to shrink the selected syntax node one more time has no effect.
 7205    editor.update_in(cx, |editor, window, cx| {
 7206        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7207    });
 7208    editor.update_in(cx, |editor, _, cx| {
 7209        assert_text_with_selections(
 7210            editor,
 7211            indoc! {r#"
 7212                use mod1::mod2::{mod3, mo«ˇ»d4};
 7213
 7214                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7215                    let var1 = "te«ˇ»xt";
 7216                }
 7217            "#},
 7218            cx,
 7219        );
 7220    });
 7221
 7222    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7223    // a fold.
 7224    editor.update_in(cx, |editor, window, cx| {
 7225        editor.fold_creases(
 7226            vec![
 7227                Crease::simple(
 7228                    Point::new(0, 21)..Point::new(0, 24),
 7229                    FoldPlaceholder::test(),
 7230                ),
 7231                Crease::simple(
 7232                    Point::new(3, 20)..Point::new(3, 22),
 7233                    FoldPlaceholder::test(),
 7234                ),
 7235            ],
 7236            true,
 7237            window,
 7238            cx,
 7239        );
 7240        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7241    });
 7242    editor.update(cx, |editor, cx| {
 7243        assert_text_with_selections(
 7244            editor,
 7245            indoc! {r#"
 7246                use mod1::mod2::«{mod3, mod4}ˇ»;
 7247
 7248                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7249                    let var1 = "«ˇtext»";
 7250                }
 7251            "#},
 7252            cx,
 7253        );
 7254    });
 7255}
 7256
 7257#[gpui::test]
 7258async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7259    init_test(cx, |_| {});
 7260
 7261    let language = Arc::new(Language::new(
 7262        LanguageConfig::default(),
 7263        Some(tree_sitter_rust::LANGUAGE.into()),
 7264    ));
 7265
 7266    let text = "let a = 2;";
 7267
 7268    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7269    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7270    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7271
 7272    editor
 7273        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7274        .await;
 7275
 7276    // Test case 1: Cursor at end of word
 7277    editor.update_in(cx, |editor, window, cx| {
 7278        editor.change_selections(None, window, cx, |s| {
 7279            s.select_display_ranges([
 7280                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7281            ]);
 7282        });
 7283    });
 7284    editor.update(cx, |editor, cx| {
 7285        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7286    });
 7287    editor.update_in(cx, |editor, window, cx| {
 7288        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7289    });
 7290    editor.update(cx, |editor, cx| {
 7291        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7292    });
 7293    editor.update_in(cx, |editor, window, cx| {
 7294        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7295    });
 7296    editor.update(cx, |editor, cx| {
 7297        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7298    });
 7299
 7300    // Test case 2: Cursor at end of statement
 7301    editor.update_in(cx, |editor, window, cx| {
 7302        editor.change_selections(None, window, cx, |s| {
 7303            s.select_display_ranges([
 7304                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7305            ]);
 7306        });
 7307    });
 7308    editor.update(cx, |editor, cx| {
 7309        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7310    });
 7311    editor.update_in(cx, |editor, window, cx| {
 7312        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7313    });
 7314    editor.update(cx, |editor, cx| {
 7315        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7316    });
 7317}
 7318
 7319#[gpui::test]
 7320async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7321    init_test(cx, |_| {});
 7322
 7323    let language = Arc::new(Language::new(
 7324        LanguageConfig::default(),
 7325        Some(tree_sitter_rust::LANGUAGE.into()),
 7326    ));
 7327
 7328    let text = r#"
 7329        use mod1::mod2::{mod3, mod4};
 7330
 7331        fn fn_1(param1: bool, param2: &str) {
 7332            let var1 = "hello world";
 7333        }
 7334    "#
 7335    .unindent();
 7336
 7337    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7338    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7339    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7340
 7341    editor
 7342        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7343        .await;
 7344
 7345    // Test 1: Cursor on a letter of a string word
 7346    editor.update_in(cx, |editor, window, cx| {
 7347        editor.change_selections(None, window, cx, |s| {
 7348            s.select_display_ranges([
 7349                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7350            ]);
 7351        });
 7352    });
 7353    editor.update_in(cx, |editor, window, cx| {
 7354        assert_text_with_selections(
 7355            editor,
 7356            indoc! {r#"
 7357                use mod1::mod2::{mod3, mod4};
 7358
 7359                fn fn_1(param1: bool, param2: &str) {
 7360                    let var1 = "hˇello world";
 7361                }
 7362            "#},
 7363            cx,
 7364        );
 7365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7366        assert_text_with_selections(
 7367            editor,
 7368            indoc! {r#"
 7369                use mod1::mod2::{mod3, mod4};
 7370
 7371                fn fn_1(param1: bool, param2: &str) {
 7372                    let var1 = "«ˇhello» world";
 7373                }
 7374            "#},
 7375            cx,
 7376        );
 7377    });
 7378
 7379    // Test 2: Partial selection within a word
 7380    editor.update_in(cx, |editor, window, cx| {
 7381        editor.change_selections(None, window, cx, |s| {
 7382            s.select_display_ranges([
 7383                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7384            ]);
 7385        });
 7386    });
 7387    editor.update_in(cx, |editor, window, cx| {
 7388        assert_text_with_selections(
 7389            editor,
 7390            indoc! {r#"
 7391                use mod1::mod2::{mod3, mod4};
 7392
 7393                fn fn_1(param1: bool, param2: &str) {
 7394                    let var1 = "h«elˇ»lo world";
 7395                }
 7396            "#},
 7397            cx,
 7398        );
 7399        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7400        assert_text_with_selections(
 7401            editor,
 7402            indoc! {r#"
 7403                use mod1::mod2::{mod3, mod4};
 7404
 7405                fn fn_1(param1: bool, param2: &str) {
 7406                    let var1 = "«ˇhello» world";
 7407                }
 7408            "#},
 7409            cx,
 7410        );
 7411    });
 7412
 7413    // Test 3: Complete word already selected
 7414    editor.update_in(cx, |editor, window, cx| {
 7415        editor.change_selections(None, window, cx, |s| {
 7416            s.select_display_ranges([
 7417                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7418            ]);
 7419        });
 7420    });
 7421    editor.update_in(cx, |editor, window, cx| {
 7422        assert_text_with_selections(
 7423            editor,
 7424            indoc! {r#"
 7425                use mod1::mod2::{mod3, mod4};
 7426
 7427                fn fn_1(param1: bool, param2: &str) {
 7428                    let var1 = "«helloˇ» world";
 7429                }
 7430            "#},
 7431            cx,
 7432        );
 7433        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7434        assert_text_with_selections(
 7435            editor,
 7436            indoc! {r#"
 7437                use mod1::mod2::{mod3, mod4};
 7438
 7439                fn fn_1(param1: bool, param2: &str) {
 7440                    let var1 = "«hello worldˇ»";
 7441                }
 7442            "#},
 7443            cx,
 7444        );
 7445    });
 7446
 7447    // Test 4: Selection spanning across words
 7448    editor.update_in(cx, |editor, window, cx| {
 7449        editor.change_selections(None, window, cx, |s| {
 7450            s.select_display_ranges([
 7451                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7452            ]);
 7453        });
 7454    });
 7455    editor.update_in(cx, |editor, window, cx| {
 7456        assert_text_with_selections(
 7457            editor,
 7458            indoc! {r#"
 7459                use mod1::mod2::{mod3, mod4};
 7460
 7461                fn fn_1(param1: bool, param2: &str) {
 7462                    let var1 = "hel«lo woˇ»rld";
 7463                }
 7464            "#},
 7465            cx,
 7466        );
 7467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7468        assert_text_with_selections(
 7469            editor,
 7470            indoc! {r#"
 7471                use mod1::mod2::{mod3, mod4};
 7472
 7473                fn fn_1(param1: bool, param2: &str) {
 7474                    let var1 = "«ˇhello world»";
 7475                }
 7476            "#},
 7477            cx,
 7478        );
 7479    });
 7480
 7481    // Test 5: Expansion beyond string
 7482    editor.update_in(cx, |editor, window, cx| {
 7483        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7485        assert_text_with_selections(
 7486            editor,
 7487            indoc! {r#"
 7488                use mod1::mod2::{mod3, mod4};
 7489
 7490                fn fn_1(param1: bool, param2: &str) {
 7491                    «ˇlet var1 = "hello world";»
 7492                }
 7493            "#},
 7494            cx,
 7495        );
 7496    });
 7497}
 7498
 7499#[gpui::test]
 7500async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7501    init_test(cx, |_| {});
 7502
 7503    let base_text = r#"
 7504        impl A {
 7505            // this is an uncommitted comment
 7506
 7507            fn b() {
 7508                c();
 7509            }
 7510
 7511            // this is another uncommitted comment
 7512
 7513            fn d() {
 7514                // e
 7515                // f
 7516            }
 7517        }
 7518
 7519        fn g() {
 7520            // h
 7521        }
 7522    "#
 7523    .unindent();
 7524
 7525    let text = r#"
 7526        ˇimpl A {
 7527
 7528            fn b() {
 7529                c();
 7530            }
 7531
 7532            fn d() {
 7533                // e
 7534                // f
 7535            }
 7536        }
 7537
 7538        fn g() {
 7539            // h
 7540        }
 7541    "#
 7542    .unindent();
 7543
 7544    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7545    cx.set_state(&text);
 7546    cx.set_head_text(&base_text);
 7547    cx.update_editor(|editor, window, cx| {
 7548        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7549    });
 7550
 7551    cx.assert_state_with_diff(
 7552        "
 7553        ˇimpl A {
 7554      -     // this is an uncommitted comment
 7555
 7556            fn b() {
 7557                c();
 7558            }
 7559
 7560      -     // this is another uncommitted comment
 7561      -
 7562            fn d() {
 7563                // e
 7564                // f
 7565            }
 7566        }
 7567
 7568        fn g() {
 7569            // h
 7570        }
 7571    "
 7572        .unindent(),
 7573    );
 7574
 7575    let expected_display_text = "
 7576        impl A {
 7577            // this is an uncommitted comment
 7578
 7579            fn b() {
 7580 7581            }
 7582
 7583            // this is another uncommitted comment
 7584
 7585            fn d() {
 7586 7587            }
 7588        }
 7589
 7590        fn g() {
 7591 7592        }
 7593        "
 7594    .unindent();
 7595
 7596    cx.update_editor(|editor, window, cx| {
 7597        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7598        assert_eq!(editor.display_text(cx), expected_display_text);
 7599    });
 7600}
 7601
 7602#[gpui::test]
 7603async fn test_autoindent(cx: &mut TestAppContext) {
 7604    init_test(cx, |_| {});
 7605
 7606    let language = Arc::new(
 7607        Language::new(
 7608            LanguageConfig {
 7609                brackets: BracketPairConfig {
 7610                    pairs: vec![
 7611                        BracketPair {
 7612                            start: "{".to_string(),
 7613                            end: "}".to_string(),
 7614                            close: false,
 7615                            surround: false,
 7616                            newline: true,
 7617                        },
 7618                        BracketPair {
 7619                            start: "(".to_string(),
 7620                            end: ")".to_string(),
 7621                            close: false,
 7622                            surround: false,
 7623                            newline: true,
 7624                        },
 7625                    ],
 7626                    ..Default::default()
 7627                },
 7628                ..Default::default()
 7629            },
 7630            Some(tree_sitter_rust::LANGUAGE.into()),
 7631        )
 7632        .with_indents_query(
 7633            r#"
 7634                (_ "(" ")" @end) @indent
 7635                (_ "{" "}" @end) @indent
 7636            "#,
 7637        )
 7638        .unwrap(),
 7639    );
 7640
 7641    let text = "fn a() {}";
 7642
 7643    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7644    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7645    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7646    editor
 7647        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7648        .await;
 7649
 7650    editor.update_in(cx, |editor, window, cx| {
 7651        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7652        editor.newline(&Newline, window, cx);
 7653        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7654        assert_eq!(
 7655            editor.selections.ranges(cx),
 7656            &[
 7657                Point::new(1, 4)..Point::new(1, 4),
 7658                Point::new(3, 4)..Point::new(3, 4),
 7659                Point::new(5, 0)..Point::new(5, 0)
 7660            ]
 7661        );
 7662    });
 7663}
 7664
 7665#[gpui::test]
 7666async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7667    init_test(cx, |_| {});
 7668
 7669    {
 7670        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7671        cx.set_state(indoc! {"
 7672            impl A {
 7673
 7674                fn b() {}
 7675
 7676            «fn c() {
 7677
 7678            }ˇ»
 7679            }
 7680        "});
 7681
 7682        cx.update_editor(|editor, window, cx| {
 7683            editor.autoindent(&Default::default(), window, cx);
 7684        });
 7685
 7686        cx.assert_editor_state(indoc! {"
 7687            impl A {
 7688
 7689                fn b() {}
 7690
 7691                «fn c() {
 7692
 7693                }ˇ»
 7694            }
 7695        "});
 7696    }
 7697
 7698    {
 7699        let mut cx = EditorTestContext::new_multibuffer(
 7700            cx,
 7701            [indoc! { "
 7702                impl A {
 7703                «
 7704                // a
 7705                fn b(){}
 7706                »
 7707                «
 7708                    }
 7709                    fn c(){}
 7710                »
 7711            "}],
 7712        );
 7713
 7714        let buffer = cx.update_editor(|editor, _, cx| {
 7715            let buffer = editor.buffer().update(cx, |buffer, _| {
 7716                buffer.all_buffers().iter().next().unwrap().clone()
 7717            });
 7718            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7719            buffer
 7720        });
 7721
 7722        cx.run_until_parked();
 7723        cx.update_editor(|editor, window, cx| {
 7724            editor.select_all(&Default::default(), window, cx);
 7725            editor.autoindent(&Default::default(), window, cx)
 7726        });
 7727        cx.run_until_parked();
 7728
 7729        cx.update(|_, cx| {
 7730            assert_eq!(
 7731                buffer.read(cx).text(),
 7732                indoc! { "
 7733                    impl A {
 7734
 7735                        // a
 7736                        fn b(){}
 7737
 7738
 7739                    }
 7740                    fn c(){}
 7741
 7742                " }
 7743            )
 7744        });
 7745    }
 7746}
 7747
 7748#[gpui::test]
 7749async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7750    init_test(cx, |_| {});
 7751
 7752    let mut cx = EditorTestContext::new(cx).await;
 7753
 7754    let language = Arc::new(Language::new(
 7755        LanguageConfig {
 7756            brackets: BracketPairConfig {
 7757                pairs: vec![
 7758                    BracketPair {
 7759                        start: "{".to_string(),
 7760                        end: "}".to_string(),
 7761                        close: true,
 7762                        surround: true,
 7763                        newline: true,
 7764                    },
 7765                    BracketPair {
 7766                        start: "(".to_string(),
 7767                        end: ")".to_string(),
 7768                        close: true,
 7769                        surround: true,
 7770                        newline: true,
 7771                    },
 7772                    BracketPair {
 7773                        start: "/*".to_string(),
 7774                        end: " */".to_string(),
 7775                        close: true,
 7776                        surround: true,
 7777                        newline: true,
 7778                    },
 7779                    BracketPair {
 7780                        start: "[".to_string(),
 7781                        end: "]".to_string(),
 7782                        close: false,
 7783                        surround: false,
 7784                        newline: true,
 7785                    },
 7786                    BracketPair {
 7787                        start: "\"".to_string(),
 7788                        end: "\"".to_string(),
 7789                        close: true,
 7790                        surround: true,
 7791                        newline: false,
 7792                    },
 7793                    BracketPair {
 7794                        start: "<".to_string(),
 7795                        end: ">".to_string(),
 7796                        close: false,
 7797                        surround: true,
 7798                        newline: true,
 7799                    },
 7800                ],
 7801                ..Default::default()
 7802            },
 7803            autoclose_before: "})]".to_string(),
 7804            ..Default::default()
 7805        },
 7806        Some(tree_sitter_rust::LANGUAGE.into()),
 7807    ));
 7808
 7809    cx.language_registry().add(language.clone());
 7810    cx.update_buffer(|buffer, cx| {
 7811        buffer.set_language(Some(language), cx);
 7812    });
 7813
 7814    cx.set_state(
 7815        &r#"
 7816            🏀ˇ
 7817            εˇ
 7818            ❤️ˇ
 7819        "#
 7820        .unindent(),
 7821    );
 7822
 7823    // autoclose multiple nested brackets at multiple cursors
 7824    cx.update_editor(|editor, window, cx| {
 7825        editor.handle_input("{", window, cx);
 7826        editor.handle_input("{", window, cx);
 7827        editor.handle_input("{", window, cx);
 7828    });
 7829    cx.assert_editor_state(
 7830        &"
 7831            🏀{{{ˇ}}}
 7832            ε{{{ˇ}}}
 7833            ❤️{{{ˇ}}}
 7834        "
 7835        .unindent(),
 7836    );
 7837
 7838    // insert a different closing bracket
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.handle_input(")", window, cx);
 7841    });
 7842    cx.assert_editor_state(
 7843        &"
 7844            🏀{{{)ˇ}}}
 7845            ε{{{)ˇ}}}
 7846            ❤️{{{)ˇ}}}
 7847        "
 7848        .unindent(),
 7849    );
 7850
 7851    // skip over the auto-closed brackets when typing a closing bracket
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.move_right(&MoveRight, window, cx);
 7854        editor.handle_input("}", window, cx);
 7855        editor.handle_input("}", window, cx);
 7856        editor.handle_input("}", window, cx);
 7857    });
 7858    cx.assert_editor_state(
 7859        &"
 7860            🏀{{{)}}}}ˇ
 7861            ε{{{)}}}}ˇ
 7862            ❤️{{{)}}}}ˇ
 7863        "
 7864        .unindent(),
 7865    );
 7866
 7867    // autoclose multi-character pairs
 7868    cx.set_state(
 7869        &"
 7870            ˇ
 7871            ˇ
 7872        "
 7873        .unindent(),
 7874    );
 7875    cx.update_editor(|editor, window, cx| {
 7876        editor.handle_input("/", window, cx);
 7877        editor.handle_input("*", window, cx);
 7878    });
 7879    cx.assert_editor_state(
 7880        &"
 7881            /*ˇ */
 7882            /*ˇ */
 7883        "
 7884        .unindent(),
 7885    );
 7886
 7887    // one cursor autocloses a multi-character pair, one cursor
 7888    // does not autoclose.
 7889    cx.set_state(
 7890        &"
 7891 7892            ˇ
 7893        "
 7894        .unindent(),
 7895    );
 7896    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7897    cx.assert_editor_state(
 7898        &"
 7899            /*ˇ */
 7900 7901        "
 7902        .unindent(),
 7903    );
 7904
 7905    // Don't autoclose if the next character isn't whitespace and isn't
 7906    // listed in the language's "autoclose_before" section.
 7907    cx.set_state("ˇa b");
 7908    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7909    cx.assert_editor_state("{ˇa b");
 7910
 7911    // Don't autoclose if `close` is false for the bracket pair
 7912    cx.set_state("ˇ");
 7913    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7914    cx.assert_editor_state("");
 7915
 7916    // Surround with brackets if text is selected
 7917    cx.set_state("«aˇ» b");
 7918    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7919    cx.assert_editor_state("{«aˇ»} b");
 7920
 7921    // Autoclose when not immediately after a word character
 7922    cx.set_state("a ˇ");
 7923    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7924    cx.assert_editor_state("a \"ˇ\"");
 7925
 7926    // Autoclose pair where the start and end characters are the same
 7927    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7928    cx.assert_editor_state("a \"\"ˇ");
 7929
 7930    // Don't autoclose when immediately after a word character
 7931    cx.set_state("");
 7932    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7933    cx.assert_editor_state("a\"ˇ");
 7934
 7935    // Do autoclose when after a non-word character
 7936    cx.set_state("");
 7937    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7938    cx.assert_editor_state("{\"ˇ\"");
 7939
 7940    // Non identical pairs autoclose regardless of preceding character
 7941    cx.set_state("");
 7942    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7943    cx.assert_editor_state("a{ˇ}");
 7944
 7945    // Don't autoclose pair if autoclose is disabled
 7946    cx.set_state("ˇ");
 7947    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7948    cx.assert_editor_state("");
 7949
 7950    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7951    cx.set_state("«aˇ» b");
 7952    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7953    cx.assert_editor_state("<«aˇ»> b");
 7954}
 7955
 7956#[gpui::test]
 7957async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7958    init_test(cx, |settings| {
 7959        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7960    });
 7961
 7962    let mut cx = EditorTestContext::new(cx).await;
 7963
 7964    let language = Arc::new(Language::new(
 7965        LanguageConfig {
 7966            brackets: BracketPairConfig {
 7967                pairs: vec![
 7968                    BracketPair {
 7969                        start: "{".to_string(),
 7970                        end: "}".to_string(),
 7971                        close: true,
 7972                        surround: true,
 7973                        newline: true,
 7974                    },
 7975                    BracketPair {
 7976                        start: "(".to_string(),
 7977                        end: ")".to_string(),
 7978                        close: true,
 7979                        surround: true,
 7980                        newline: true,
 7981                    },
 7982                    BracketPair {
 7983                        start: "[".to_string(),
 7984                        end: "]".to_string(),
 7985                        close: false,
 7986                        surround: false,
 7987                        newline: true,
 7988                    },
 7989                ],
 7990                ..Default::default()
 7991            },
 7992            autoclose_before: "})]".to_string(),
 7993            ..Default::default()
 7994        },
 7995        Some(tree_sitter_rust::LANGUAGE.into()),
 7996    ));
 7997
 7998    cx.language_registry().add(language.clone());
 7999    cx.update_buffer(|buffer, cx| {
 8000        buffer.set_language(Some(language), cx);
 8001    });
 8002
 8003    cx.set_state(
 8004        &"
 8005            ˇ
 8006            ˇ
 8007            ˇ
 8008        "
 8009        .unindent(),
 8010    );
 8011
 8012    // ensure only matching closing brackets are skipped over
 8013    cx.update_editor(|editor, window, cx| {
 8014        editor.handle_input("}", window, cx);
 8015        editor.move_left(&MoveLeft, window, cx);
 8016        editor.handle_input(")", window, cx);
 8017        editor.move_left(&MoveLeft, window, cx);
 8018    });
 8019    cx.assert_editor_state(
 8020        &"
 8021            ˇ)}
 8022            ˇ)}
 8023            ˇ)}
 8024        "
 8025        .unindent(),
 8026    );
 8027
 8028    // skip-over closing brackets at multiple cursors
 8029    cx.update_editor(|editor, window, cx| {
 8030        editor.handle_input(")", window, cx);
 8031        editor.handle_input("}", window, cx);
 8032    });
 8033    cx.assert_editor_state(
 8034        &"
 8035            )}ˇ
 8036            )}ˇ
 8037            )}ˇ
 8038        "
 8039        .unindent(),
 8040    );
 8041
 8042    // ignore non-close brackets
 8043    cx.update_editor(|editor, window, cx| {
 8044        editor.handle_input("]", window, cx);
 8045        editor.move_left(&MoveLeft, window, cx);
 8046        editor.handle_input("]", window, cx);
 8047    });
 8048    cx.assert_editor_state(
 8049        &"
 8050            )}]ˇ]
 8051            )}]ˇ]
 8052            )}]ˇ]
 8053        "
 8054        .unindent(),
 8055    );
 8056}
 8057
 8058#[gpui::test]
 8059async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8060    init_test(cx, |_| {});
 8061
 8062    let mut cx = EditorTestContext::new(cx).await;
 8063
 8064    let html_language = Arc::new(
 8065        Language::new(
 8066            LanguageConfig {
 8067                name: "HTML".into(),
 8068                brackets: BracketPairConfig {
 8069                    pairs: vec![
 8070                        BracketPair {
 8071                            start: "<".into(),
 8072                            end: ">".into(),
 8073                            close: true,
 8074                            ..Default::default()
 8075                        },
 8076                        BracketPair {
 8077                            start: "{".into(),
 8078                            end: "}".into(),
 8079                            close: true,
 8080                            ..Default::default()
 8081                        },
 8082                        BracketPair {
 8083                            start: "(".into(),
 8084                            end: ")".into(),
 8085                            close: true,
 8086                            ..Default::default()
 8087                        },
 8088                    ],
 8089                    ..Default::default()
 8090                },
 8091                autoclose_before: "})]>".into(),
 8092                ..Default::default()
 8093            },
 8094            Some(tree_sitter_html::LANGUAGE.into()),
 8095        )
 8096        .with_injection_query(
 8097            r#"
 8098            (script_element
 8099                (raw_text) @injection.content
 8100                (#set! injection.language "javascript"))
 8101            "#,
 8102        )
 8103        .unwrap(),
 8104    );
 8105
 8106    let javascript_language = Arc::new(Language::new(
 8107        LanguageConfig {
 8108            name: "JavaScript".into(),
 8109            brackets: BracketPairConfig {
 8110                pairs: vec![
 8111                    BracketPair {
 8112                        start: "/*".into(),
 8113                        end: " */".into(),
 8114                        close: true,
 8115                        ..Default::default()
 8116                    },
 8117                    BracketPair {
 8118                        start: "{".into(),
 8119                        end: "}".into(),
 8120                        close: true,
 8121                        ..Default::default()
 8122                    },
 8123                    BracketPair {
 8124                        start: "(".into(),
 8125                        end: ")".into(),
 8126                        close: true,
 8127                        ..Default::default()
 8128                    },
 8129                ],
 8130                ..Default::default()
 8131            },
 8132            autoclose_before: "})]>".into(),
 8133            ..Default::default()
 8134        },
 8135        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8136    ));
 8137
 8138    cx.language_registry().add(html_language.clone());
 8139    cx.language_registry().add(javascript_language.clone());
 8140
 8141    cx.update_buffer(|buffer, cx| {
 8142        buffer.set_language(Some(html_language), cx);
 8143    });
 8144
 8145    cx.set_state(
 8146        &r#"
 8147            <body>ˇ
 8148                <script>
 8149                    var x = 1;ˇ
 8150                </script>
 8151            </body>ˇ
 8152        "#
 8153        .unindent(),
 8154    );
 8155
 8156    // Precondition: different languages are active at different locations.
 8157    cx.update_editor(|editor, window, cx| {
 8158        let snapshot = editor.snapshot(window, cx);
 8159        let cursors = editor.selections.ranges::<usize>(cx);
 8160        let languages = cursors
 8161            .iter()
 8162            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8163            .collect::<Vec<_>>();
 8164        assert_eq!(
 8165            languages,
 8166            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8167        );
 8168    });
 8169
 8170    // Angle brackets autoclose in HTML, but not JavaScript.
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.handle_input("<", window, cx);
 8173        editor.handle_input("a", window, cx);
 8174    });
 8175    cx.assert_editor_state(
 8176        &r#"
 8177            <body><aˇ>
 8178                <script>
 8179                    var x = 1;<aˇ
 8180                </script>
 8181            </body><aˇ>
 8182        "#
 8183        .unindent(),
 8184    );
 8185
 8186    // Curly braces and parens autoclose in both HTML and JavaScript.
 8187    cx.update_editor(|editor, window, cx| {
 8188        editor.handle_input(" b=", window, cx);
 8189        editor.handle_input("{", window, cx);
 8190        editor.handle_input("c", window, cx);
 8191        editor.handle_input("(", window, cx);
 8192    });
 8193    cx.assert_editor_state(
 8194        &r#"
 8195            <body><a b={c(ˇ)}>
 8196                <script>
 8197                    var x = 1;<a b={c(ˇ)}
 8198                </script>
 8199            </body><a b={c(ˇ)}>
 8200        "#
 8201        .unindent(),
 8202    );
 8203
 8204    // Brackets that were already autoclosed are skipped.
 8205    cx.update_editor(|editor, window, cx| {
 8206        editor.handle_input(")", window, cx);
 8207        editor.handle_input("d", window, cx);
 8208        editor.handle_input("}", window, cx);
 8209    });
 8210    cx.assert_editor_state(
 8211        &r#"
 8212            <body><a b={c()d}ˇ>
 8213                <script>
 8214                    var x = 1;<a b={c()d}ˇ
 8215                </script>
 8216            </body><a b={c()d}ˇ>
 8217        "#
 8218        .unindent(),
 8219    );
 8220    cx.update_editor(|editor, window, cx| {
 8221        editor.handle_input(">", window, cx);
 8222    });
 8223    cx.assert_editor_state(
 8224        &r#"
 8225            <body><a b={c()d}>ˇ
 8226                <script>
 8227                    var x = 1;<a b={c()d}>ˇ
 8228                </script>
 8229            </body><a b={c()d}>ˇ
 8230        "#
 8231        .unindent(),
 8232    );
 8233
 8234    // Reset
 8235    cx.set_state(
 8236        &r#"
 8237            <body>ˇ
 8238                <script>
 8239                    var x = 1;ˇ
 8240                </script>
 8241            </body>ˇ
 8242        "#
 8243        .unindent(),
 8244    );
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.handle_input("<", window, cx);
 8248    });
 8249    cx.assert_editor_state(
 8250        &r#"
 8251            <body><ˇ>
 8252                <script>
 8253                    var x = 1;<ˇ
 8254                </script>
 8255            </body><ˇ>
 8256        "#
 8257        .unindent(),
 8258    );
 8259
 8260    // When backspacing, the closing angle brackets are removed.
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.backspace(&Backspace, window, cx);
 8263    });
 8264    cx.assert_editor_state(
 8265        &r#"
 8266            <body>ˇ
 8267                <script>
 8268                    var x = 1;ˇ
 8269                </script>
 8270            </body>ˇ
 8271        "#
 8272        .unindent(),
 8273    );
 8274
 8275    // Block comments autoclose in JavaScript, but not HTML.
 8276    cx.update_editor(|editor, window, cx| {
 8277        editor.handle_input("/", window, cx);
 8278        editor.handle_input("*", window, cx);
 8279    });
 8280    cx.assert_editor_state(
 8281        &r#"
 8282            <body>/*ˇ
 8283                <script>
 8284                    var x = 1;/*ˇ */
 8285                </script>
 8286            </body>/*ˇ
 8287        "#
 8288        .unindent(),
 8289    );
 8290}
 8291
 8292#[gpui::test]
 8293async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8294    init_test(cx, |_| {});
 8295
 8296    let mut cx = EditorTestContext::new(cx).await;
 8297
 8298    let rust_language = Arc::new(
 8299        Language::new(
 8300            LanguageConfig {
 8301                name: "Rust".into(),
 8302                brackets: serde_json::from_value(json!([
 8303                    { "start": "{", "end": "}", "close": true, "newline": true },
 8304                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8305                ]))
 8306                .unwrap(),
 8307                autoclose_before: "})]>".into(),
 8308                ..Default::default()
 8309            },
 8310            Some(tree_sitter_rust::LANGUAGE.into()),
 8311        )
 8312        .with_override_query("(string_literal) @string")
 8313        .unwrap(),
 8314    );
 8315
 8316    cx.language_registry().add(rust_language.clone());
 8317    cx.update_buffer(|buffer, cx| {
 8318        buffer.set_language(Some(rust_language), cx);
 8319    });
 8320
 8321    cx.set_state(
 8322        &r#"
 8323            let x = ˇ
 8324        "#
 8325        .unindent(),
 8326    );
 8327
 8328    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8329    cx.update_editor(|editor, window, cx| {
 8330        editor.handle_input("\"", window, cx);
 8331    });
 8332    cx.assert_editor_state(
 8333        &r#"
 8334            let x = "ˇ"
 8335        "#
 8336        .unindent(),
 8337    );
 8338
 8339    // Inserting another quotation mark. The cursor moves across the existing
 8340    // automatically-inserted quotation mark.
 8341    cx.update_editor(|editor, window, cx| {
 8342        editor.handle_input("\"", window, cx);
 8343    });
 8344    cx.assert_editor_state(
 8345        &r#"
 8346            let x = ""ˇ
 8347        "#
 8348        .unindent(),
 8349    );
 8350
 8351    // Reset
 8352    cx.set_state(
 8353        &r#"
 8354            let x = ˇ
 8355        "#
 8356        .unindent(),
 8357    );
 8358
 8359    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8360    cx.update_editor(|editor, window, cx| {
 8361        editor.handle_input("\"", window, cx);
 8362        editor.handle_input(" ", window, cx);
 8363        editor.move_left(&Default::default(), window, cx);
 8364        editor.handle_input("\\", window, cx);
 8365        editor.handle_input("\"", window, cx);
 8366    });
 8367    cx.assert_editor_state(
 8368        &r#"
 8369            let x = "\"ˇ "
 8370        "#
 8371        .unindent(),
 8372    );
 8373
 8374    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8375    // mark. Nothing is inserted.
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.move_right(&Default::default(), window, cx);
 8378        editor.handle_input("\"", window, cx);
 8379    });
 8380    cx.assert_editor_state(
 8381        &r#"
 8382            let x = "\" "ˇ
 8383        "#
 8384        .unindent(),
 8385    );
 8386}
 8387
 8388#[gpui::test]
 8389async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8390    init_test(cx, |_| {});
 8391
 8392    let language = Arc::new(Language::new(
 8393        LanguageConfig {
 8394            brackets: BracketPairConfig {
 8395                pairs: vec![
 8396                    BracketPair {
 8397                        start: "{".to_string(),
 8398                        end: "}".to_string(),
 8399                        close: true,
 8400                        surround: true,
 8401                        newline: true,
 8402                    },
 8403                    BracketPair {
 8404                        start: "/* ".to_string(),
 8405                        end: "*/".to_string(),
 8406                        close: true,
 8407                        surround: true,
 8408                        ..Default::default()
 8409                    },
 8410                ],
 8411                ..Default::default()
 8412            },
 8413            ..Default::default()
 8414        },
 8415        Some(tree_sitter_rust::LANGUAGE.into()),
 8416    ));
 8417
 8418    let text = r#"
 8419        a
 8420        b
 8421        c
 8422    "#
 8423    .unindent();
 8424
 8425    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8426    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8428    editor
 8429        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8430        .await;
 8431
 8432    editor.update_in(cx, |editor, window, cx| {
 8433        editor.change_selections(None, window, cx, |s| {
 8434            s.select_display_ranges([
 8435                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8436                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8437                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8438            ])
 8439        });
 8440
 8441        editor.handle_input("{", window, cx);
 8442        editor.handle_input("{", window, cx);
 8443        editor.handle_input("{", window, cx);
 8444        assert_eq!(
 8445            editor.text(cx),
 8446            "
 8447                {{{a}}}
 8448                {{{b}}}
 8449                {{{c}}}
 8450            "
 8451            .unindent()
 8452        );
 8453        assert_eq!(
 8454            editor.selections.display_ranges(cx),
 8455            [
 8456                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8457                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8458                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8459            ]
 8460        );
 8461
 8462        editor.undo(&Undo, window, cx);
 8463        editor.undo(&Undo, window, cx);
 8464        editor.undo(&Undo, window, cx);
 8465        assert_eq!(
 8466            editor.text(cx),
 8467            "
 8468                a
 8469                b
 8470                c
 8471            "
 8472            .unindent()
 8473        );
 8474        assert_eq!(
 8475            editor.selections.display_ranges(cx),
 8476            [
 8477                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8478                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8479                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8480            ]
 8481        );
 8482
 8483        // Ensure inserting the first character of a multi-byte bracket pair
 8484        // doesn't surround the selections with the bracket.
 8485        editor.handle_input("/", window, cx);
 8486        assert_eq!(
 8487            editor.text(cx),
 8488            "
 8489                /
 8490                /
 8491                /
 8492            "
 8493            .unindent()
 8494        );
 8495        assert_eq!(
 8496            editor.selections.display_ranges(cx),
 8497            [
 8498                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8499                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8500                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8501            ]
 8502        );
 8503
 8504        editor.undo(&Undo, window, cx);
 8505        assert_eq!(
 8506            editor.text(cx),
 8507            "
 8508                a
 8509                b
 8510                c
 8511            "
 8512            .unindent()
 8513        );
 8514        assert_eq!(
 8515            editor.selections.display_ranges(cx),
 8516            [
 8517                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8518                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8519                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8520            ]
 8521        );
 8522
 8523        // Ensure inserting the last character of a multi-byte bracket pair
 8524        // doesn't surround the selections with the bracket.
 8525        editor.handle_input("*", window, cx);
 8526        assert_eq!(
 8527            editor.text(cx),
 8528            "
 8529                *
 8530                *
 8531                *
 8532            "
 8533            .unindent()
 8534        );
 8535        assert_eq!(
 8536            editor.selections.display_ranges(cx),
 8537            [
 8538                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8539                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8540                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8541            ]
 8542        );
 8543    });
 8544}
 8545
 8546#[gpui::test]
 8547async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8548    init_test(cx, |_| {});
 8549
 8550    let language = Arc::new(Language::new(
 8551        LanguageConfig {
 8552            brackets: BracketPairConfig {
 8553                pairs: vec![BracketPair {
 8554                    start: "{".to_string(),
 8555                    end: "}".to_string(),
 8556                    close: true,
 8557                    surround: true,
 8558                    newline: true,
 8559                }],
 8560                ..Default::default()
 8561            },
 8562            autoclose_before: "}".to_string(),
 8563            ..Default::default()
 8564        },
 8565        Some(tree_sitter_rust::LANGUAGE.into()),
 8566    ));
 8567
 8568    let text = r#"
 8569        a
 8570        b
 8571        c
 8572    "#
 8573    .unindent();
 8574
 8575    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8577    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8578    editor
 8579        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8580        .await;
 8581
 8582    editor.update_in(cx, |editor, window, cx| {
 8583        editor.change_selections(None, window, cx, |s| {
 8584            s.select_ranges([
 8585                Point::new(0, 1)..Point::new(0, 1),
 8586                Point::new(1, 1)..Point::new(1, 1),
 8587                Point::new(2, 1)..Point::new(2, 1),
 8588            ])
 8589        });
 8590
 8591        editor.handle_input("{", window, cx);
 8592        editor.handle_input("{", window, cx);
 8593        editor.handle_input("_", window, cx);
 8594        assert_eq!(
 8595            editor.text(cx),
 8596            "
 8597                a{{_}}
 8598                b{{_}}
 8599                c{{_}}
 8600            "
 8601            .unindent()
 8602        );
 8603        assert_eq!(
 8604            editor.selections.ranges::<Point>(cx),
 8605            [
 8606                Point::new(0, 4)..Point::new(0, 4),
 8607                Point::new(1, 4)..Point::new(1, 4),
 8608                Point::new(2, 4)..Point::new(2, 4)
 8609            ]
 8610        );
 8611
 8612        editor.backspace(&Default::default(), window, cx);
 8613        editor.backspace(&Default::default(), window, cx);
 8614        assert_eq!(
 8615            editor.text(cx),
 8616            "
 8617                a{}
 8618                b{}
 8619                c{}
 8620            "
 8621            .unindent()
 8622        );
 8623        assert_eq!(
 8624            editor.selections.ranges::<Point>(cx),
 8625            [
 8626                Point::new(0, 2)..Point::new(0, 2),
 8627                Point::new(1, 2)..Point::new(1, 2),
 8628                Point::new(2, 2)..Point::new(2, 2)
 8629            ]
 8630        );
 8631
 8632        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8633        assert_eq!(
 8634            editor.text(cx),
 8635            "
 8636                a
 8637                b
 8638                c
 8639            "
 8640            .unindent()
 8641        );
 8642        assert_eq!(
 8643            editor.selections.ranges::<Point>(cx),
 8644            [
 8645                Point::new(0, 1)..Point::new(0, 1),
 8646                Point::new(1, 1)..Point::new(1, 1),
 8647                Point::new(2, 1)..Point::new(2, 1)
 8648            ]
 8649        );
 8650    });
 8651}
 8652
 8653#[gpui::test]
 8654async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8655    init_test(cx, |settings| {
 8656        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8657    });
 8658
 8659    let mut cx = EditorTestContext::new(cx).await;
 8660
 8661    let language = Arc::new(Language::new(
 8662        LanguageConfig {
 8663            brackets: BracketPairConfig {
 8664                pairs: vec![
 8665                    BracketPair {
 8666                        start: "{".to_string(),
 8667                        end: "}".to_string(),
 8668                        close: true,
 8669                        surround: true,
 8670                        newline: true,
 8671                    },
 8672                    BracketPair {
 8673                        start: "(".to_string(),
 8674                        end: ")".to_string(),
 8675                        close: true,
 8676                        surround: true,
 8677                        newline: true,
 8678                    },
 8679                    BracketPair {
 8680                        start: "[".to_string(),
 8681                        end: "]".to_string(),
 8682                        close: false,
 8683                        surround: true,
 8684                        newline: true,
 8685                    },
 8686                ],
 8687                ..Default::default()
 8688            },
 8689            autoclose_before: "})]".to_string(),
 8690            ..Default::default()
 8691        },
 8692        Some(tree_sitter_rust::LANGUAGE.into()),
 8693    ));
 8694
 8695    cx.language_registry().add(language.clone());
 8696    cx.update_buffer(|buffer, cx| {
 8697        buffer.set_language(Some(language), cx);
 8698    });
 8699
 8700    cx.set_state(
 8701        &"
 8702            {(ˇ)}
 8703            [[ˇ]]
 8704            {(ˇ)}
 8705        "
 8706        .unindent(),
 8707    );
 8708
 8709    cx.update_editor(|editor, window, cx| {
 8710        editor.backspace(&Default::default(), window, cx);
 8711        editor.backspace(&Default::default(), window, cx);
 8712    });
 8713
 8714    cx.assert_editor_state(
 8715        &"
 8716            ˇ
 8717            ˇ]]
 8718            ˇ
 8719        "
 8720        .unindent(),
 8721    );
 8722
 8723    cx.update_editor(|editor, window, cx| {
 8724        editor.handle_input("{", window, cx);
 8725        editor.handle_input("{", window, cx);
 8726        editor.move_right(&MoveRight, window, cx);
 8727        editor.move_right(&MoveRight, window, cx);
 8728        editor.move_left(&MoveLeft, window, cx);
 8729        editor.move_left(&MoveLeft, window, cx);
 8730        editor.backspace(&Default::default(), window, cx);
 8731    });
 8732
 8733    cx.assert_editor_state(
 8734        &"
 8735            {ˇ}
 8736            {ˇ}]]
 8737            {ˇ}
 8738        "
 8739        .unindent(),
 8740    );
 8741
 8742    cx.update_editor(|editor, window, cx| {
 8743        editor.backspace(&Default::default(), window, cx);
 8744    });
 8745
 8746    cx.assert_editor_state(
 8747        &"
 8748            ˇ
 8749            ˇ]]
 8750            ˇ
 8751        "
 8752        .unindent(),
 8753    );
 8754}
 8755
 8756#[gpui::test]
 8757async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8758    init_test(cx, |_| {});
 8759
 8760    let language = Arc::new(Language::new(
 8761        LanguageConfig::default(),
 8762        Some(tree_sitter_rust::LANGUAGE.into()),
 8763    ));
 8764
 8765    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8766    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8767    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8768    editor
 8769        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8770        .await;
 8771
 8772    editor.update_in(cx, |editor, window, cx| {
 8773        editor.set_auto_replace_emoji_shortcode(true);
 8774
 8775        editor.handle_input("Hello ", window, cx);
 8776        editor.handle_input(":wave", window, cx);
 8777        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8778
 8779        editor.handle_input(":", window, cx);
 8780        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8781
 8782        editor.handle_input(" :smile", window, cx);
 8783        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8784
 8785        editor.handle_input(":", window, cx);
 8786        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8787
 8788        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8789        editor.handle_input(":wave", window, cx);
 8790        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8791
 8792        editor.handle_input(":", window, cx);
 8793        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8794
 8795        editor.handle_input(":1", window, cx);
 8796        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8797
 8798        editor.handle_input(":", window, cx);
 8799        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8800
 8801        // Ensure shortcode does not get replaced when it is part of a word
 8802        editor.handle_input(" Test:wave", window, cx);
 8803        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8804
 8805        editor.handle_input(":", window, cx);
 8806        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8807
 8808        editor.set_auto_replace_emoji_shortcode(false);
 8809
 8810        // Ensure shortcode does not get replaced when auto replace is off
 8811        editor.handle_input(" :wave", window, cx);
 8812        assert_eq!(
 8813            editor.text(cx),
 8814            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8815        );
 8816
 8817        editor.handle_input(":", window, cx);
 8818        assert_eq!(
 8819            editor.text(cx),
 8820            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8821        );
 8822    });
 8823}
 8824
 8825#[gpui::test]
 8826async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8827    init_test(cx, |_| {});
 8828
 8829    let (text, insertion_ranges) = marked_text_ranges(
 8830        indoc! {"
 8831            ˇ
 8832        "},
 8833        false,
 8834    );
 8835
 8836    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8837    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8838
 8839    _ = editor.update_in(cx, |editor, window, cx| {
 8840        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8841
 8842        editor
 8843            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8844            .unwrap();
 8845
 8846        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8847            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8848            assert_eq!(editor.text(cx), expected_text);
 8849            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8850        }
 8851
 8852        assert(
 8853            editor,
 8854            cx,
 8855            indoc! {"
 8856            type «» =•
 8857            "},
 8858        );
 8859
 8860        assert!(editor.context_menu_visible(), "There should be a matches");
 8861    });
 8862}
 8863
 8864#[gpui::test]
 8865async fn test_snippets(cx: &mut TestAppContext) {
 8866    init_test(cx, |_| {});
 8867
 8868    let mut cx = EditorTestContext::new(cx).await;
 8869
 8870    cx.set_state(indoc! {"
 8871        a.ˇ b
 8872        a.ˇ b
 8873        a.ˇ b
 8874    "});
 8875
 8876    cx.update_editor(|editor, window, cx| {
 8877        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8878        let insertion_ranges = editor
 8879            .selections
 8880            .all(cx)
 8881            .iter()
 8882            .map(|s| s.range().clone())
 8883            .collect::<Vec<_>>();
 8884        editor
 8885            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8886            .unwrap();
 8887    });
 8888
 8889    cx.assert_editor_state(indoc! {"
 8890        a.f(«oneˇ», two, «threeˇ») b
 8891        a.f(«oneˇ», two, «threeˇ») b
 8892        a.f(«oneˇ», two, «threeˇ») b
 8893    "});
 8894
 8895    // Can't move earlier than the first tab stop
 8896    cx.update_editor(|editor, window, cx| {
 8897        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8898    });
 8899    cx.assert_editor_state(indoc! {"
 8900        a.f(«oneˇ», two, «threeˇ») b
 8901        a.f(«oneˇ», two, «threeˇ») b
 8902        a.f(«oneˇ», two, «threeˇ») b
 8903    "});
 8904
 8905    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8906    cx.assert_editor_state(indoc! {"
 8907        a.f(one, «twoˇ», three) b
 8908        a.f(one, «twoˇ», three) b
 8909        a.f(one, «twoˇ», three) b
 8910    "});
 8911
 8912    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 8913    cx.assert_editor_state(indoc! {"
 8914        a.f(«oneˇ», two, «threeˇ») b
 8915        a.f(«oneˇ», two, «threeˇ») b
 8916        a.f(«oneˇ», two, «threeˇ») b
 8917    "});
 8918
 8919    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8920    cx.assert_editor_state(indoc! {"
 8921        a.f(one, «twoˇ», three) b
 8922        a.f(one, «twoˇ», three) b
 8923        a.f(one, «twoˇ», three) b
 8924    "});
 8925    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8926    cx.assert_editor_state(indoc! {"
 8927        a.f(one, two, three)ˇ b
 8928        a.f(one, two, three)ˇ b
 8929        a.f(one, two, three)ˇ b
 8930    "});
 8931
 8932    // As soon as the last tab stop is reached, snippet state is gone
 8933    cx.update_editor(|editor, window, cx| {
 8934        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8935    });
 8936    cx.assert_editor_state(indoc! {"
 8937        a.f(one, two, three)ˇ b
 8938        a.f(one, two, three)ˇ b
 8939        a.f(one, two, three)ˇ b
 8940    "});
 8941}
 8942
 8943#[gpui::test]
 8944async fn test_snippet_indentation(cx: &mut TestAppContext) {
 8945    init_test(cx, |_| {});
 8946
 8947    let mut cx = EditorTestContext::new(cx).await;
 8948
 8949    cx.update_editor(|editor, window, cx| {
 8950        let snippet = Snippet::parse(indoc! {"
 8951            /*
 8952             * Multiline comment with leading indentation
 8953             *
 8954             * $1
 8955             */
 8956            $0"})
 8957        .unwrap();
 8958        let insertion_ranges = editor
 8959            .selections
 8960            .all(cx)
 8961            .iter()
 8962            .map(|s| s.range().clone())
 8963            .collect::<Vec<_>>();
 8964        editor
 8965            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8966            .unwrap();
 8967    });
 8968
 8969    cx.assert_editor_state(indoc! {"
 8970        /*
 8971         * Multiline comment with leading indentation
 8972         *
 8973         * ˇ
 8974         */
 8975    "});
 8976
 8977    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8978    cx.assert_editor_state(indoc! {"
 8979        /*
 8980         * Multiline comment with leading indentation
 8981         *
 8982         *•
 8983         */
 8984        ˇ"});
 8985}
 8986
 8987#[gpui::test]
 8988async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8989    init_test(cx, |_| {});
 8990
 8991    let fs = FakeFs::new(cx.executor());
 8992    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8993
 8994    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8995
 8996    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8997    language_registry.add(rust_lang());
 8998    let mut fake_servers = language_registry.register_fake_lsp(
 8999        "Rust",
 9000        FakeLspAdapter {
 9001            capabilities: lsp::ServerCapabilities {
 9002                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9003                ..Default::default()
 9004            },
 9005            ..Default::default()
 9006        },
 9007    );
 9008
 9009    let buffer = project
 9010        .update(cx, |project, cx| {
 9011            project.open_local_buffer(path!("/file.rs"), cx)
 9012        })
 9013        .await
 9014        .unwrap();
 9015
 9016    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9017    let (editor, cx) = cx.add_window_view(|window, cx| {
 9018        build_editor_with_project(project.clone(), buffer, window, cx)
 9019    });
 9020    editor.update_in(cx, |editor, window, cx| {
 9021        editor.set_text("one\ntwo\nthree\n", window, cx)
 9022    });
 9023    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9024
 9025    cx.executor().start_waiting();
 9026    let fake_server = fake_servers.next().await.unwrap();
 9027
 9028    {
 9029        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9030            move |params, _| async move {
 9031                assert_eq!(
 9032                    params.text_document.uri,
 9033                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9034                );
 9035                assert_eq!(params.options.tab_size, 4);
 9036                Ok(Some(vec![lsp::TextEdit::new(
 9037                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9038                    ", ".to_string(),
 9039                )]))
 9040            },
 9041        );
 9042        let save = editor
 9043            .update_in(cx, |editor, window, cx| {
 9044                editor.save(
 9045                    SaveOptions {
 9046                        format: true,
 9047                        autosave: false,
 9048                    },
 9049                    project.clone(),
 9050                    window,
 9051                    cx,
 9052                )
 9053            })
 9054            .unwrap();
 9055        cx.executor().start_waiting();
 9056        save.await;
 9057
 9058        assert_eq!(
 9059            editor.update(cx, |editor, cx| editor.text(cx)),
 9060            "one, two\nthree\n"
 9061        );
 9062        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9063    }
 9064
 9065    {
 9066        editor.update_in(cx, |editor, window, cx| {
 9067            editor.set_text("one\ntwo\nthree\n", window, cx)
 9068        });
 9069        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9070
 9071        // Ensure we can still save even if formatting hangs.
 9072        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9073            move |params, _| async move {
 9074                assert_eq!(
 9075                    params.text_document.uri,
 9076                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9077                );
 9078                futures::future::pending::<()>().await;
 9079                unreachable!()
 9080            },
 9081        );
 9082        let save = editor
 9083            .update_in(cx, |editor, window, cx| {
 9084                editor.save(
 9085                    SaveOptions {
 9086                        format: true,
 9087                        autosave: false,
 9088                    },
 9089                    project.clone(),
 9090                    window,
 9091                    cx,
 9092                )
 9093            })
 9094            .unwrap();
 9095        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9096        cx.executor().start_waiting();
 9097        save.await;
 9098        assert_eq!(
 9099            editor.update(cx, |editor, cx| editor.text(cx)),
 9100            "one\ntwo\nthree\n"
 9101        );
 9102    }
 9103
 9104    // Set rust language override and assert overridden tabsize is sent to language server
 9105    update_test_language_settings(cx, |settings| {
 9106        settings.languages.insert(
 9107            "Rust".into(),
 9108            LanguageSettingsContent {
 9109                tab_size: NonZeroU32::new(8),
 9110                ..Default::default()
 9111            },
 9112        );
 9113    });
 9114
 9115    {
 9116        editor.update_in(cx, |editor, window, cx| {
 9117            editor.set_text("somehting_new\n", window, cx)
 9118        });
 9119        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9120        let _formatting_request_signal = fake_server
 9121            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9122                assert_eq!(
 9123                    params.text_document.uri,
 9124                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9125                );
 9126                assert_eq!(params.options.tab_size, 8);
 9127                Ok(Some(vec![]))
 9128            });
 9129        let save = editor
 9130            .update_in(cx, |editor, window, cx| {
 9131                editor.save(
 9132                    SaveOptions {
 9133                        format: true,
 9134                        autosave: false,
 9135                    },
 9136                    project.clone(),
 9137                    window,
 9138                    cx,
 9139                )
 9140            })
 9141            .unwrap();
 9142        cx.executor().start_waiting();
 9143        save.await;
 9144    }
 9145}
 9146
 9147#[gpui::test]
 9148async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9149    init_test(cx, |_| {});
 9150
 9151    let cols = 4;
 9152    let rows = 10;
 9153    let sample_text_1 = sample_text(rows, cols, 'a');
 9154    assert_eq!(
 9155        sample_text_1,
 9156        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9157    );
 9158    let sample_text_2 = sample_text(rows, cols, 'l');
 9159    assert_eq!(
 9160        sample_text_2,
 9161        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9162    );
 9163    let sample_text_3 = sample_text(rows, cols, 'v');
 9164    assert_eq!(
 9165        sample_text_3,
 9166        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9167    );
 9168
 9169    let fs = FakeFs::new(cx.executor());
 9170    fs.insert_tree(
 9171        path!("/a"),
 9172        json!({
 9173            "main.rs": sample_text_1,
 9174            "other.rs": sample_text_2,
 9175            "lib.rs": sample_text_3,
 9176        }),
 9177    )
 9178    .await;
 9179
 9180    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9181    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9182    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9183
 9184    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9185    language_registry.add(rust_lang());
 9186    let mut fake_servers = language_registry.register_fake_lsp(
 9187        "Rust",
 9188        FakeLspAdapter {
 9189            capabilities: lsp::ServerCapabilities {
 9190                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9191                ..Default::default()
 9192            },
 9193            ..Default::default()
 9194        },
 9195    );
 9196
 9197    let worktree = project.update(cx, |project, cx| {
 9198        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9199        assert_eq!(worktrees.len(), 1);
 9200        worktrees.pop().unwrap()
 9201    });
 9202    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9203
 9204    let buffer_1 = project
 9205        .update(cx, |project, cx| {
 9206            project.open_buffer((worktree_id, "main.rs"), cx)
 9207        })
 9208        .await
 9209        .unwrap();
 9210    let buffer_2 = project
 9211        .update(cx, |project, cx| {
 9212            project.open_buffer((worktree_id, "other.rs"), cx)
 9213        })
 9214        .await
 9215        .unwrap();
 9216    let buffer_3 = project
 9217        .update(cx, |project, cx| {
 9218            project.open_buffer((worktree_id, "lib.rs"), cx)
 9219        })
 9220        .await
 9221        .unwrap();
 9222
 9223    let multi_buffer = cx.new(|cx| {
 9224        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9225        multi_buffer.push_excerpts(
 9226            buffer_1.clone(),
 9227            [
 9228                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9229                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9230                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9231            ],
 9232            cx,
 9233        );
 9234        multi_buffer.push_excerpts(
 9235            buffer_2.clone(),
 9236            [
 9237                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9238                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9239                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9240            ],
 9241            cx,
 9242        );
 9243        multi_buffer.push_excerpts(
 9244            buffer_3.clone(),
 9245            [
 9246                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9247                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9248                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9249            ],
 9250            cx,
 9251        );
 9252        multi_buffer
 9253    });
 9254    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9255        Editor::new(
 9256            EditorMode::full(),
 9257            multi_buffer,
 9258            Some(project.clone()),
 9259            window,
 9260            cx,
 9261        )
 9262    });
 9263
 9264    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9265        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9266            s.select_ranges(Some(1..2))
 9267        });
 9268        editor.insert("|one|two|three|", window, cx);
 9269    });
 9270    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9271    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9272        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9273            s.select_ranges(Some(60..70))
 9274        });
 9275        editor.insert("|four|five|six|", window, cx);
 9276    });
 9277    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9278
 9279    // First two buffers should be edited, but not the third one.
 9280    assert_eq!(
 9281        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9282        "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}",
 9283    );
 9284    buffer_1.update(cx, |buffer, _| {
 9285        assert!(buffer.is_dirty());
 9286        assert_eq!(
 9287            buffer.text(),
 9288            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9289        )
 9290    });
 9291    buffer_2.update(cx, |buffer, _| {
 9292        assert!(buffer.is_dirty());
 9293        assert_eq!(
 9294            buffer.text(),
 9295            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9296        )
 9297    });
 9298    buffer_3.update(cx, |buffer, _| {
 9299        assert!(!buffer.is_dirty());
 9300        assert_eq!(buffer.text(), sample_text_3,)
 9301    });
 9302    cx.executor().run_until_parked();
 9303
 9304    cx.executor().start_waiting();
 9305    let save = multi_buffer_editor
 9306        .update_in(cx, |editor, window, cx| {
 9307            editor.save(
 9308                SaveOptions {
 9309                    format: true,
 9310                    autosave: false,
 9311                },
 9312                project.clone(),
 9313                window,
 9314                cx,
 9315            )
 9316        })
 9317        .unwrap();
 9318
 9319    let fake_server = fake_servers.next().await.unwrap();
 9320    fake_server
 9321        .server
 9322        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9323            Ok(Some(vec![lsp::TextEdit::new(
 9324                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9325                format!("[{} formatted]", params.text_document.uri),
 9326            )]))
 9327        })
 9328        .detach();
 9329    save.await;
 9330
 9331    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9332    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9333    assert_eq!(
 9334        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9335        uri!(
 9336            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9337        ),
 9338    );
 9339    buffer_1.update(cx, |buffer, _| {
 9340        assert!(!buffer.is_dirty());
 9341        assert_eq!(
 9342            buffer.text(),
 9343            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9344        )
 9345    });
 9346    buffer_2.update(cx, |buffer, _| {
 9347        assert!(!buffer.is_dirty());
 9348        assert_eq!(
 9349            buffer.text(),
 9350            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9351        )
 9352    });
 9353    buffer_3.update(cx, |buffer, _| {
 9354        assert!(!buffer.is_dirty());
 9355        assert_eq!(buffer.text(), sample_text_3,)
 9356    });
 9357}
 9358
 9359#[gpui::test]
 9360async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9361    init_test(cx, |_| {});
 9362
 9363    let fs = FakeFs::new(cx.executor());
 9364    fs.insert_tree(
 9365        path!("/dir"),
 9366        json!({
 9367            "file1.rs": "fn main() { println!(\"hello\"); }",
 9368            "file2.rs": "fn test() { println!(\"test\"); }",
 9369            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9370        }),
 9371    )
 9372    .await;
 9373
 9374    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9375    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9376    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9377
 9378    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9379    language_registry.add(rust_lang());
 9380
 9381    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9382    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9383
 9384    // Open three buffers
 9385    let buffer_1 = project
 9386        .update(cx, |project, cx| {
 9387            project.open_buffer((worktree_id, "file1.rs"), cx)
 9388        })
 9389        .await
 9390        .unwrap();
 9391    let buffer_2 = project
 9392        .update(cx, |project, cx| {
 9393            project.open_buffer((worktree_id, "file2.rs"), cx)
 9394        })
 9395        .await
 9396        .unwrap();
 9397    let buffer_3 = project
 9398        .update(cx, |project, cx| {
 9399            project.open_buffer((worktree_id, "file3.rs"), cx)
 9400        })
 9401        .await
 9402        .unwrap();
 9403
 9404    // Create a multi-buffer with all three buffers
 9405    let multi_buffer = cx.new(|cx| {
 9406        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9407        multi_buffer.push_excerpts(
 9408            buffer_1.clone(),
 9409            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9410            cx,
 9411        );
 9412        multi_buffer.push_excerpts(
 9413            buffer_2.clone(),
 9414            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9415            cx,
 9416        );
 9417        multi_buffer.push_excerpts(
 9418            buffer_3.clone(),
 9419            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9420            cx,
 9421        );
 9422        multi_buffer
 9423    });
 9424
 9425    let editor = cx.new_window_entity(|window, cx| {
 9426        Editor::new(
 9427            EditorMode::full(),
 9428            multi_buffer,
 9429            Some(project.clone()),
 9430            window,
 9431            cx,
 9432        )
 9433    });
 9434
 9435    // Edit only the first buffer
 9436    editor.update_in(cx, |editor, window, cx| {
 9437        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9438            s.select_ranges(Some(10..10))
 9439        });
 9440        editor.insert("// edited", window, cx);
 9441    });
 9442
 9443    // Verify that only buffer 1 is dirty
 9444    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9445    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9446    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9447
 9448    // Get write counts after file creation (files were created with initial content)
 9449    // We expect each file to have been written once during creation
 9450    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9451    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9452    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9453
 9454    // Perform autosave
 9455    let save_task = editor.update_in(cx, |editor, window, cx| {
 9456        editor.save(
 9457            SaveOptions {
 9458                format: true,
 9459                autosave: true,
 9460            },
 9461            project.clone(),
 9462            window,
 9463            cx,
 9464        )
 9465    });
 9466    save_task.await.unwrap();
 9467
 9468    // Only the dirty buffer should have been saved
 9469    assert_eq!(
 9470        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9471        1,
 9472        "Buffer 1 was dirty, so it should have been written once during autosave"
 9473    );
 9474    assert_eq!(
 9475        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9476        0,
 9477        "Buffer 2 was clean, so it should not have been written during autosave"
 9478    );
 9479    assert_eq!(
 9480        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9481        0,
 9482        "Buffer 3 was clean, so it should not have been written during autosave"
 9483    );
 9484
 9485    // Verify buffer states after autosave
 9486    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9487    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9488    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9489
 9490    // Now perform a manual save (format = true)
 9491    let save_task = editor.update_in(cx, |editor, window, cx| {
 9492        editor.save(
 9493            SaveOptions {
 9494                format: true,
 9495                autosave: false,
 9496            },
 9497            project.clone(),
 9498            window,
 9499            cx,
 9500        )
 9501    });
 9502    save_task.await.unwrap();
 9503
 9504    // During manual save, clean buffers don't get written to disk
 9505    // They just get did_save called for language server notifications
 9506    assert_eq!(
 9507        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9508        1,
 9509        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9510    );
 9511    assert_eq!(
 9512        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9513        0,
 9514        "Buffer 2 should not have been written at all"
 9515    );
 9516    assert_eq!(
 9517        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9518        0,
 9519        "Buffer 3 should not have been written at all"
 9520    );
 9521}
 9522
 9523#[gpui::test]
 9524async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9525    init_test(cx, |_| {});
 9526
 9527    let fs = FakeFs::new(cx.executor());
 9528    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9529
 9530    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9531
 9532    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9533    language_registry.add(rust_lang());
 9534    let mut fake_servers = language_registry.register_fake_lsp(
 9535        "Rust",
 9536        FakeLspAdapter {
 9537            capabilities: lsp::ServerCapabilities {
 9538                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9539                ..Default::default()
 9540            },
 9541            ..Default::default()
 9542        },
 9543    );
 9544
 9545    let buffer = project
 9546        .update(cx, |project, cx| {
 9547            project.open_local_buffer(path!("/file.rs"), cx)
 9548        })
 9549        .await
 9550        .unwrap();
 9551
 9552    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9553    let (editor, cx) = cx.add_window_view(|window, cx| {
 9554        build_editor_with_project(project.clone(), buffer, window, cx)
 9555    });
 9556    editor.update_in(cx, |editor, window, cx| {
 9557        editor.set_text("one\ntwo\nthree\n", window, cx)
 9558    });
 9559    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9560
 9561    cx.executor().start_waiting();
 9562    let fake_server = fake_servers.next().await.unwrap();
 9563
 9564    let save = editor
 9565        .update_in(cx, |editor, window, cx| {
 9566            editor.save(
 9567                SaveOptions {
 9568                    format: true,
 9569                    autosave: false,
 9570                },
 9571                project.clone(),
 9572                window,
 9573                cx,
 9574            )
 9575        })
 9576        .unwrap();
 9577    fake_server
 9578        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9579            assert_eq!(
 9580                params.text_document.uri,
 9581                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9582            );
 9583            assert_eq!(params.options.tab_size, 4);
 9584            Ok(Some(vec![lsp::TextEdit::new(
 9585                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9586                ", ".to_string(),
 9587            )]))
 9588        })
 9589        .next()
 9590        .await;
 9591    cx.executor().start_waiting();
 9592    save.await;
 9593    assert_eq!(
 9594        editor.update(cx, |editor, cx| editor.text(cx)),
 9595        "one, two\nthree\n"
 9596    );
 9597    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9598
 9599    editor.update_in(cx, |editor, window, cx| {
 9600        editor.set_text("one\ntwo\nthree\n", window, cx)
 9601    });
 9602    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9603
 9604    // Ensure we can still save even if formatting hangs.
 9605    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9606        move |params, _| async move {
 9607            assert_eq!(
 9608                params.text_document.uri,
 9609                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9610            );
 9611            futures::future::pending::<()>().await;
 9612            unreachable!()
 9613        },
 9614    );
 9615    let save = editor
 9616        .update_in(cx, |editor, window, cx| {
 9617            editor.save(
 9618                SaveOptions {
 9619                    format: true,
 9620                    autosave: false,
 9621                },
 9622                project.clone(),
 9623                window,
 9624                cx,
 9625            )
 9626        })
 9627        .unwrap();
 9628    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9629    cx.executor().start_waiting();
 9630    save.await;
 9631    assert_eq!(
 9632        editor.update(cx, |editor, cx| editor.text(cx)),
 9633        "one\ntwo\nthree\n"
 9634    );
 9635    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9636
 9637    // For non-dirty buffer, no formatting request should be sent
 9638    let save = editor
 9639        .update_in(cx, |editor, window, cx| {
 9640            editor.save(
 9641                SaveOptions {
 9642                    format: false,
 9643                    autosave: false,
 9644                },
 9645                project.clone(),
 9646                window,
 9647                cx,
 9648            )
 9649        })
 9650        .unwrap();
 9651    let _pending_format_request = fake_server
 9652        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9653            panic!("Should not be invoked");
 9654        })
 9655        .next();
 9656    cx.executor().start_waiting();
 9657    save.await;
 9658
 9659    // Set Rust language override and assert overridden tabsize is sent to language server
 9660    update_test_language_settings(cx, |settings| {
 9661        settings.languages.insert(
 9662            "Rust".into(),
 9663            LanguageSettingsContent {
 9664                tab_size: NonZeroU32::new(8),
 9665                ..Default::default()
 9666            },
 9667        );
 9668    });
 9669
 9670    editor.update_in(cx, |editor, window, cx| {
 9671        editor.set_text("somehting_new\n", window, cx)
 9672    });
 9673    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9674    let save = editor
 9675        .update_in(cx, |editor, window, cx| {
 9676            editor.save(
 9677                SaveOptions {
 9678                    format: true,
 9679                    autosave: false,
 9680                },
 9681                project.clone(),
 9682                window,
 9683                cx,
 9684            )
 9685        })
 9686        .unwrap();
 9687    fake_server
 9688        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9689            assert_eq!(
 9690                params.text_document.uri,
 9691                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9692            );
 9693            assert_eq!(params.options.tab_size, 8);
 9694            Ok(Some(Vec::new()))
 9695        })
 9696        .next()
 9697        .await;
 9698    save.await;
 9699}
 9700
 9701#[gpui::test]
 9702async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9703    init_test(cx, |settings| {
 9704        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9705            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9706        ))
 9707    });
 9708
 9709    let fs = FakeFs::new(cx.executor());
 9710    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9711
 9712    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9713
 9714    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9715    language_registry.add(Arc::new(Language::new(
 9716        LanguageConfig {
 9717            name: "Rust".into(),
 9718            matcher: LanguageMatcher {
 9719                path_suffixes: vec!["rs".to_string()],
 9720                ..Default::default()
 9721            },
 9722            ..LanguageConfig::default()
 9723        },
 9724        Some(tree_sitter_rust::LANGUAGE.into()),
 9725    )));
 9726    update_test_language_settings(cx, |settings| {
 9727        // Enable Prettier formatting for the same buffer, and ensure
 9728        // LSP is called instead of Prettier.
 9729        settings.defaults.prettier = Some(PrettierSettings {
 9730            allowed: true,
 9731            ..PrettierSettings::default()
 9732        });
 9733    });
 9734    let mut fake_servers = language_registry.register_fake_lsp(
 9735        "Rust",
 9736        FakeLspAdapter {
 9737            capabilities: lsp::ServerCapabilities {
 9738                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9739                ..Default::default()
 9740            },
 9741            ..Default::default()
 9742        },
 9743    );
 9744
 9745    let buffer = project
 9746        .update(cx, |project, cx| {
 9747            project.open_local_buffer(path!("/file.rs"), cx)
 9748        })
 9749        .await
 9750        .unwrap();
 9751
 9752    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9753    let (editor, cx) = cx.add_window_view(|window, cx| {
 9754        build_editor_with_project(project.clone(), buffer, window, cx)
 9755    });
 9756    editor.update_in(cx, |editor, window, cx| {
 9757        editor.set_text("one\ntwo\nthree\n", window, cx)
 9758    });
 9759
 9760    cx.executor().start_waiting();
 9761    let fake_server = fake_servers.next().await.unwrap();
 9762
 9763    let format = editor
 9764        .update_in(cx, |editor, window, cx| {
 9765            editor.perform_format(
 9766                project.clone(),
 9767                FormatTrigger::Manual,
 9768                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9769                window,
 9770                cx,
 9771            )
 9772        })
 9773        .unwrap();
 9774    fake_server
 9775        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9776            assert_eq!(
 9777                params.text_document.uri,
 9778                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9779            );
 9780            assert_eq!(params.options.tab_size, 4);
 9781            Ok(Some(vec![lsp::TextEdit::new(
 9782                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9783                ", ".to_string(),
 9784            )]))
 9785        })
 9786        .next()
 9787        .await;
 9788    cx.executor().start_waiting();
 9789    format.await;
 9790    assert_eq!(
 9791        editor.update(cx, |editor, cx| editor.text(cx)),
 9792        "one, two\nthree\n"
 9793    );
 9794
 9795    editor.update_in(cx, |editor, window, cx| {
 9796        editor.set_text("one\ntwo\nthree\n", window, cx)
 9797    });
 9798    // Ensure we don't lock if formatting hangs.
 9799    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9800        move |params, _| async move {
 9801            assert_eq!(
 9802                params.text_document.uri,
 9803                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9804            );
 9805            futures::future::pending::<()>().await;
 9806            unreachable!()
 9807        },
 9808    );
 9809    let format = editor
 9810        .update_in(cx, |editor, window, cx| {
 9811            editor.perform_format(
 9812                project,
 9813                FormatTrigger::Manual,
 9814                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9815                window,
 9816                cx,
 9817            )
 9818        })
 9819        .unwrap();
 9820    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9821    cx.executor().start_waiting();
 9822    format.await;
 9823    assert_eq!(
 9824        editor.update(cx, |editor, cx| editor.text(cx)),
 9825        "one\ntwo\nthree\n"
 9826    );
 9827}
 9828
 9829#[gpui::test]
 9830async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9831    init_test(cx, |settings| {
 9832        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9833        settings.defaults.formatter =
 9834            Some(language_settings::SelectedFormatter::List(FormatterList(
 9835                vec![
 9836                    Formatter::LanguageServer { name: None },
 9837                    Formatter::CodeActions(
 9838                        [
 9839                            ("code-action-1".into(), true),
 9840                            ("code-action-2".into(), true),
 9841                        ]
 9842                        .into_iter()
 9843                        .collect(),
 9844                    ),
 9845                ]
 9846                .into(),
 9847            )))
 9848    });
 9849
 9850    let fs = FakeFs::new(cx.executor());
 9851    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9852        .await;
 9853
 9854    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9855    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9856    language_registry.add(rust_lang());
 9857
 9858    let mut fake_servers = language_registry.register_fake_lsp(
 9859        "Rust",
 9860        FakeLspAdapter {
 9861            capabilities: lsp::ServerCapabilities {
 9862                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9863                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9864                    commands: vec!["the-command-for-code-action-1".into()],
 9865                    ..Default::default()
 9866                }),
 9867                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9868                ..Default::default()
 9869            },
 9870            ..Default::default()
 9871        },
 9872    );
 9873
 9874    let buffer = project
 9875        .update(cx, |project, cx| {
 9876            project.open_local_buffer(path!("/file.rs"), cx)
 9877        })
 9878        .await
 9879        .unwrap();
 9880
 9881    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9882    let (editor, cx) = cx.add_window_view(|window, cx| {
 9883        build_editor_with_project(project.clone(), buffer, window, cx)
 9884    });
 9885
 9886    cx.executor().start_waiting();
 9887
 9888    let fake_server = fake_servers.next().await.unwrap();
 9889    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9890        move |_params, _| async move {
 9891            Ok(Some(vec![lsp::TextEdit::new(
 9892                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9893                "applied-formatting\n".to_string(),
 9894            )]))
 9895        },
 9896    );
 9897    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9898        move |params, _| async move {
 9899            assert_eq!(
 9900                params.context.only,
 9901                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9902            );
 9903            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9904            Ok(Some(vec![
 9905                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9906                    kind: Some("code-action-1".into()),
 9907                    edit: Some(lsp::WorkspaceEdit::new(
 9908                        [(
 9909                            uri.clone(),
 9910                            vec![lsp::TextEdit::new(
 9911                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9912                                "applied-code-action-1-edit\n".to_string(),
 9913                            )],
 9914                        )]
 9915                        .into_iter()
 9916                        .collect(),
 9917                    )),
 9918                    command: Some(lsp::Command {
 9919                        command: "the-command-for-code-action-1".into(),
 9920                        ..Default::default()
 9921                    }),
 9922                    ..Default::default()
 9923                }),
 9924                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9925                    kind: Some("code-action-2".into()),
 9926                    edit: Some(lsp::WorkspaceEdit::new(
 9927                        [(
 9928                            uri.clone(),
 9929                            vec![lsp::TextEdit::new(
 9930                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9931                                "applied-code-action-2-edit\n".to_string(),
 9932                            )],
 9933                        )]
 9934                        .into_iter()
 9935                        .collect(),
 9936                    )),
 9937                    ..Default::default()
 9938                }),
 9939            ]))
 9940        },
 9941    );
 9942
 9943    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9944        move |params, _| async move { Ok(params) }
 9945    });
 9946
 9947    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9948    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9949        let fake = fake_server.clone();
 9950        let lock = command_lock.clone();
 9951        move |params, _| {
 9952            assert_eq!(params.command, "the-command-for-code-action-1");
 9953            let fake = fake.clone();
 9954            let lock = lock.clone();
 9955            async move {
 9956                lock.lock().await;
 9957                fake.server
 9958                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9959                        label: None,
 9960                        edit: lsp::WorkspaceEdit {
 9961                            changes: Some(
 9962                                [(
 9963                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9964                                    vec![lsp::TextEdit {
 9965                                        range: lsp::Range::new(
 9966                                            lsp::Position::new(0, 0),
 9967                                            lsp::Position::new(0, 0),
 9968                                        ),
 9969                                        new_text: "applied-code-action-1-command\n".into(),
 9970                                    }],
 9971                                )]
 9972                                .into_iter()
 9973                                .collect(),
 9974                            ),
 9975                            ..Default::default()
 9976                        },
 9977                    })
 9978                    .await
 9979                    .into_response()
 9980                    .unwrap();
 9981                Ok(Some(json!(null)))
 9982            }
 9983        }
 9984    });
 9985
 9986    cx.executor().start_waiting();
 9987    editor
 9988        .update_in(cx, |editor, window, cx| {
 9989            editor.perform_format(
 9990                project.clone(),
 9991                FormatTrigger::Manual,
 9992                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9993                window,
 9994                cx,
 9995            )
 9996        })
 9997        .unwrap()
 9998        .await;
 9999    editor.update(cx, |editor, cx| {
10000        assert_eq!(
10001            editor.text(cx),
10002            r#"
10003                applied-code-action-2-edit
10004                applied-code-action-1-command
10005                applied-code-action-1-edit
10006                applied-formatting
10007                one
10008                two
10009                three
10010            "#
10011            .unindent()
10012        );
10013    });
10014
10015    editor.update_in(cx, |editor, window, cx| {
10016        editor.undo(&Default::default(), window, cx);
10017        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10018    });
10019
10020    // Perform a manual edit while waiting for an LSP command
10021    // that's being run as part of a formatting code action.
10022    let lock_guard = command_lock.lock().await;
10023    let format = editor
10024        .update_in(cx, |editor, window, cx| {
10025            editor.perform_format(
10026                project.clone(),
10027                FormatTrigger::Manual,
10028                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10029                window,
10030                cx,
10031            )
10032        })
10033        .unwrap();
10034    cx.run_until_parked();
10035    editor.update(cx, |editor, cx| {
10036        assert_eq!(
10037            editor.text(cx),
10038            r#"
10039                applied-code-action-1-edit
10040                applied-formatting
10041                one
10042                two
10043                three
10044            "#
10045            .unindent()
10046        );
10047
10048        editor.buffer.update(cx, |buffer, cx| {
10049            let ix = buffer.len(cx);
10050            buffer.edit([(ix..ix, "edited\n")], None, cx);
10051        });
10052    });
10053
10054    // Allow the LSP command to proceed. Because the buffer was edited,
10055    // the second code action will not be run.
10056    drop(lock_guard);
10057    format.await;
10058    editor.update_in(cx, |editor, window, cx| {
10059        assert_eq!(
10060            editor.text(cx),
10061            r#"
10062                applied-code-action-1-command
10063                applied-code-action-1-edit
10064                applied-formatting
10065                one
10066                two
10067                three
10068                edited
10069            "#
10070            .unindent()
10071        );
10072
10073        // The manual edit is undone first, because it is the last thing the user did
10074        // (even though the command completed afterwards).
10075        editor.undo(&Default::default(), window, cx);
10076        assert_eq!(
10077            editor.text(cx),
10078            r#"
10079                applied-code-action-1-command
10080                applied-code-action-1-edit
10081                applied-formatting
10082                one
10083                two
10084                three
10085            "#
10086            .unindent()
10087        );
10088
10089        // All the formatting (including the command, which completed after the manual edit)
10090        // is undone together.
10091        editor.undo(&Default::default(), window, cx);
10092        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10093    });
10094}
10095
10096#[gpui::test]
10097async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10098    init_test(cx, |settings| {
10099        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10100            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10101        ))
10102    });
10103
10104    let fs = FakeFs::new(cx.executor());
10105    fs.insert_file(path!("/file.ts"), Default::default()).await;
10106
10107    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10108
10109    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10110    language_registry.add(Arc::new(Language::new(
10111        LanguageConfig {
10112            name: "TypeScript".into(),
10113            matcher: LanguageMatcher {
10114                path_suffixes: vec!["ts".to_string()],
10115                ..Default::default()
10116            },
10117            ..LanguageConfig::default()
10118        },
10119        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10120    )));
10121    update_test_language_settings(cx, |settings| {
10122        settings.defaults.prettier = Some(PrettierSettings {
10123            allowed: true,
10124            ..PrettierSettings::default()
10125        });
10126    });
10127    let mut fake_servers = language_registry.register_fake_lsp(
10128        "TypeScript",
10129        FakeLspAdapter {
10130            capabilities: lsp::ServerCapabilities {
10131                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10132                ..Default::default()
10133            },
10134            ..Default::default()
10135        },
10136    );
10137
10138    let buffer = project
10139        .update(cx, |project, cx| {
10140            project.open_local_buffer(path!("/file.ts"), cx)
10141        })
10142        .await
10143        .unwrap();
10144
10145    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10146    let (editor, cx) = cx.add_window_view(|window, cx| {
10147        build_editor_with_project(project.clone(), buffer, window, cx)
10148    });
10149    editor.update_in(cx, |editor, window, cx| {
10150        editor.set_text(
10151            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10152            window,
10153            cx,
10154        )
10155    });
10156
10157    cx.executor().start_waiting();
10158    let fake_server = fake_servers.next().await.unwrap();
10159
10160    let format = editor
10161        .update_in(cx, |editor, window, cx| {
10162            editor.perform_code_action_kind(
10163                project.clone(),
10164                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10165                window,
10166                cx,
10167            )
10168        })
10169        .unwrap();
10170    fake_server
10171        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10172            assert_eq!(
10173                params.text_document.uri,
10174                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10175            );
10176            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10177                lsp::CodeAction {
10178                    title: "Organize Imports".to_string(),
10179                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10180                    edit: Some(lsp::WorkspaceEdit {
10181                        changes: Some(
10182                            [(
10183                                params.text_document.uri.clone(),
10184                                vec![lsp::TextEdit::new(
10185                                    lsp::Range::new(
10186                                        lsp::Position::new(1, 0),
10187                                        lsp::Position::new(2, 0),
10188                                    ),
10189                                    "".to_string(),
10190                                )],
10191                            )]
10192                            .into_iter()
10193                            .collect(),
10194                        ),
10195                        ..Default::default()
10196                    }),
10197                    ..Default::default()
10198                },
10199            )]))
10200        })
10201        .next()
10202        .await;
10203    cx.executor().start_waiting();
10204    format.await;
10205    assert_eq!(
10206        editor.update(cx, |editor, cx| editor.text(cx)),
10207        "import { a } from 'module';\n\nconst x = a;\n"
10208    );
10209
10210    editor.update_in(cx, |editor, window, cx| {
10211        editor.set_text(
10212            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10213            window,
10214            cx,
10215        )
10216    });
10217    // Ensure we don't lock if code action hangs.
10218    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10219        move |params, _| async move {
10220            assert_eq!(
10221                params.text_document.uri,
10222                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10223            );
10224            futures::future::pending::<()>().await;
10225            unreachable!()
10226        },
10227    );
10228    let format = editor
10229        .update_in(cx, |editor, window, cx| {
10230            editor.perform_code_action_kind(
10231                project,
10232                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10233                window,
10234                cx,
10235            )
10236        })
10237        .unwrap();
10238    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10239    cx.executor().start_waiting();
10240    format.await;
10241    assert_eq!(
10242        editor.update(cx, |editor, cx| editor.text(cx)),
10243        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10244    );
10245}
10246
10247#[gpui::test]
10248async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10249    init_test(cx, |_| {});
10250
10251    let mut cx = EditorLspTestContext::new_rust(
10252        lsp::ServerCapabilities {
10253            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10254            ..Default::default()
10255        },
10256        cx,
10257    )
10258    .await;
10259
10260    cx.set_state(indoc! {"
10261        one.twoˇ
10262    "});
10263
10264    // The format request takes a long time. When it completes, it inserts
10265    // a newline and an indent before the `.`
10266    cx.lsp
10267        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10268            let executor = cx.background_executor().clone();
10269            async move {
10270                executor.timer(Duration::from_millis(100)).await;
10271                Ok(Some(vec![lsp::TextEdit {
10272                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10273                    new_text: "\n    ".into(),
10274                }]))
10275            }
10276        });
10277
10278    // Submit a format request.
10279    let format_1 = cx
10280        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10281        .unwrap();
10282    cx.executor().run_until_parked();
10283
10284    // Submit a second format request.
10285    let format_2 = cx
10286        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10287        .unwrap();
10288    cx.executor().run_until_parked();
10289
10290    // Wait for both format requests to complete
10291    cx.executor().advance_clock(Duration::from_millis(200));
10292    cx.executor().start_waiting();
10293    format_1.await.unwrap();
10294    cx.executor().start_waiting();
10295    format_2.await.unwrap();
10296
10297    // The formatting edits only happens once.
10298    cx.assert_editor_state(indoc! {"
10299        one
10300            .twoˇ
10301    "});
10302}
10303
10304#[gpui::test]
10305async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10306    init_test(cx, |settings| {
10307        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10308    });
10309
10310    let mut cx = EditorLspTestContext::new_rust(
10311        lsp::ServerCapabilities {
10312            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10313            ..Default::default()
10314        },
10315        cx,
10316    )
10317    .await;
10318
10319    // Set up a buffer white some trailing whitespace and no trailing newline.
10320    cx.set_state(
10321        &[
10322            "one ",   //
10323            "twoˇ",   //
10324            "three ", //
10325            "four",   //
10326        ]
10327        .join("\n"),
10328    );
10329
10330    // Submit a format request.
10331    let format = cx
10332        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10333        .unwrap();
10334
10335    // Record which buffer changes have been sent to the language server
10336    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10337    cx.lsp
10338        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10339            let buffer_changes = buffer_changes.clone();
10340            move |params, _| {
10341                buffer_changes.lock().extend(
10342                    params
10343                        .content_changes
10344                        .into_iter()
10345                        .map(|e| (e.range.unwrap(), e.text)),
10346                );
10347            }
10348        });
10349
10350    // Handle formatting requests to the language server.
10351    cx.lsp
10352        .set_request_handler::<lsp::request::Formatting, _, _>({
10353            let buffer_changes = buffer_changes.clone();
10354            move |_, _| {
10355                // When formatting is requested, trailing whitespace has already been stripped,
10356                // and the trailing newline has already been added.
10357                assert_eq!(
10358                    &buffer_changes.lock()[1..],
10359                    &[
10360                        (
10361                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10362                            "".into()
10363                        ),
10364                        (
10365                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10366                            "".into()
10367                        ),
10368                        (
10369                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10370                            "\n".into()
10371                        ),
10372                    ]
10373                );
10374
10375                // Insert blank lines between each line of the buffer.
10376                async move {
10377                    Ok(Some(vec![
10378                        lsp::TextEdit {
10379                            range: lsp::Range::new(
10380                                lsp::Position::new(1, 0),
10381                                lsp::Position::new(1, 0),
10382                            ),
10383                            new_text: "\n".into(),
10384                        },
10385                        lsp::TextEdit {
10386                            range: lsp::Range::new(
10387                                lsp::Position::new(2, 0),
10388                                lsp::Position::new(2, 0),
10389                            ),
10390                            new_text: "\n".into(),
10391                        },
10392                    ]))
10393                }
10394            }
10395        });
10396
10397    // After formatting the buffer, the trailing whitespace is stripped,
10398    // a newline is appended, and the edits provided by the language server
10399    // have been applied.
10400    format.await.unwrap();
10401    cx.assert_editor_state(
10402        &[
10403            "one",   //
10404            "",      //
10405            "twoˇ",  //
10406            "",      //
10407            "three", //
10408            "four",  //
10409            "",      //
10410        ]
10411        .join("\n"),
10412    );
10413
10414    // Undoing the formatting undoes the trailing whitespace removal, the
10415    // trailing newline, and the LSP edits.
10416    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10417    cx.assert_editor_state(
10418        &[
10419            "one ",   //
10420            "twoˇ",   //
10421            "three ", //
10422            "four",   //
10423        ]
10424        .join("\n"),
10425    );
10426}
10427
10428#[gpui::test]
10429async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10430    cx: &mut TestAppContext,
10431) {
10432    init_test(cx, |_| {});
10433
10434    cx.update(|cx| {
10435        cx.update_global::<SettingsStore, _>(|settings, cx| {
10436            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10437                settings.auto_signature_help = Some(true);
10438            });
10439        });
10440    });
10441
10442    let mut cx = EditorLspTestContext::new_rust(
10443        lsp::ServerCapabilities {
10444            signature_help_provider: Some(lsp::SignatureHelpOptions {
10445                ..Default::default()
10446            }),
10447            ..Default::default()
10448        },
10449        cx,
10450    )
10451    .await;
10452
10453    let language = Language::new(
10454        LanguageConfig {
10455            name: "Rust".into(),
10456            brackets: BracketPairConfig {
10457                pairs: vec![
10458                    BracketPair {
10459                        start: "{".to_string(),
10460                        end: "}".to_string(),
10461                        close: true,
10462                        surround: true,
10463                        newline: true,
10464                    },
10465                    BracketPair {
10466                        start: "(".to_string(),
10467                        end: ")".to_string(),
10468                        close: true,
10469                        surround: true,
10470                        newline: true,
10471                    },
10472                    BracketPair {
10473                        start: "/*".to_string(),
10474                        end: " */".to_string(),
10475                        close: true,
10476                        surround: true,
10477                        newline: true,
10478                    },
10479                    BracketPair {
10480                        start: "[".to_string(),
10481                        end: "]".to_string(),
10482                        close: false,
10483                        surround: false,
10484                        newline: true,
10485                    },
10486                    BracketPair {
10487                        start: "\"".to_string(),
10488                        end: "\"".to_string(),
10489                        close: true,
10490                        surround: true,
10491                        newline: false,
10492                    },
10493                    BracketPair {
10494                        start: "<".to_string(),
10495                        end: ">".to_string(),
10496                        close: false,
10497                        surround: true,
10498                        newline: true,
10499                    },
10500                ],
10501                ..Default::default()
10502            },
10503            autoclose_before: "})]".to_string(),
10504            ..Default::default()
10505        },
10506        Some(tree_sitter_rust::LANGUAGE.into()),
10507    );
10508    let language = Arc::new(language);
10509
10510    cx.language_registry().add(language.clone());
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            fn main() {
10518                sampleˇ
10519            }
10520        "#
10521        .unindent(),
10522    );
10523
10524    cx.update_editor(|editor, window, cx| {
10525        editor.handle_input("(", window, cx);
10526    });
10527    cx.assert_editor_state(
10528        &"
10529            fn main() {
10530                sample(ˇ)
10531            }
10532        "
10533        .unindent(),
10534    );
10535
10536    let mocked_response = lsp::SignatureHelp {
10537        signatures: vec![lsp::SignatureInformation {
10538            label: "fn sample(param1: u8, param2: u8)".to_string(),
10539            documentation: None,
10540            parameters: Some(vec![
10541                lsp::ParameterInformation {
10542                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10543                    documentation: None,
10544                },
10545                lsp::ParameterInformation {
10546                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10547                    documentation: None,
10548                },
10549            ]),
10550            active_parameter: None,
10551        }],
10552        active_signature: Some(0),
10553        active_parameter: Some(0),
10554    };
10555    handle_signature_help_request(&mut cx, mocked_response).await;
10556
10557    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10558        .await;
10559
10560    cx.editor(|editor, _, _| {
10561        let signature_help_state = editor.signature_help_state.popover().cloned();
10562        assert_eq!(
10563            signature_help_state.unwrap().label,
10564            "param1: u8, param2: u8"
10565        );
10566    });
10567}
10568
10569#[gpui::test]
10570async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10571    init_test(cx, |_| {});
10572
10573    cx.update(|cx| {
10574        cx.update_global::<SettingsStore, _>(|settings, cx| {
10575            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10576                settings.auto_signature_help = Some(false);
10577                settings.show_signature_help_after_edits = Some(false);
10578            });
10579        });
10580    });
10581
10582    let mut cx = EditorLspTestContext::new_rust(
10583        lsp::ServerCapabilities {
10584            signature_help_provider: Some(lsp::SignatureHelpOptions {
10585                ..Default::default()
10586            }),
10587            ..Default::default()
10588        },
10589        cx,
10590    )
10591    .await;
10592
10593    let language = Language::new(
10594        LanguageConfig {
10595            name: "Rust".into(),
10596            brackets: BracketPairConfig {
10597                pairs: vec![
10598                    BracketPair {
10599                        start: "{".to_string(),
10600                        end: "}".to_string(),
10601                        close: true,
10602                        surround: true,
10603                        newline: true,
10604                    },
10605                    BracketPair {
10606                        start: "(".to_string(),
10607                        end: ")".to_string(),
10608                        close: true,
10609                        surround: true,
10610                        newline: true,
10611                    },
10612                    BracketPair {
10613                        start: "/*".to_string(),
10614                        end: " */".to_string(),
10615                        close: true,
10616                        surround: true,
10617                        newline: true,
10618                    },
10619                    BracketPair {
10620                        start: "[".to_string(),
10621                        end: "]".to_string(),
10622                        close: false,
10623                        surround: false,
10624                        newline: true,
10625                    },
10626                    BracketPair {
10627                        start: "\"".to_string(),
10628                        end: "\"".to_string(),
10629                        close: true,
10630                        surround: true,
10631                        newline: false,
10632                    },
10633                    BracketPair {
10634                        start: "<".to_string(),
10635                        end: ">".to_string(),
10636                        close: false,
10637                        surround: true,
10638                        newline: true,
10639                    },
10640                ],
10641                ..Default::default()
10642            },
10643            autoclose_before: "})]".to_string(),
10644            ..Default::default()
10645        },
10646        Some(tree_sitter_rust::LANGUAGE.into()),
10647    );
10648    let language = Arc::new(language);
10649
10650    cx.language_registry().add(language.clone());
10651    cx.update_buffer(|buffer, cx| {
10652        buffer.set_language(Some(language), cx);
10653    });
10654
10655    // Ensure that signature_help is not called when no signature help is enabled.
10656    cx.set_state(
10657        &r#"
10658            fn main() {
10659                sampleˇ
10660            }
10661        "#
10662        .unindent(),
10663    );
10664    cx.update_editor(|editor, window, cx| {
10665        editor.handle_input("(", window, cx);
10666    });
10667    cx.assert_editor_state(
10668        &"
10669            fn main() {
10670                sample(ˇ)
10671            }
10672        "
10673        .unindent(),
10674    );
10675    cx.editor(|editor, _, _| {
10676        assert!(editor.signature_help_state.task().is_none());
10677    });
10678
10679    let mocked_response = lsp::SignatureHelp {
10680        signatures: vec![lsp::SignatureInformation {
10681            label: "fn sample(param1: u8, param2: u8)".to_string(),
10682            documentation: None,
10683            parameters: Some(vec![
10684                lsp::ParameterInformation {
10685                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10686                    documentation: None,
10687                },
10688                lsp::ParameterInformation {
10689                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10690                    documentation: None,
10691                },
10692            ]),
10693            active_parameter: None,
10694        }],
10695        active_signature: Some(0),
10696        active_parameter: Some(0),
10697    };
10698
10699    // Ensure that signature_help is called when enabled afte edits
10700    cx.update(|_, cx| {
10701        cx.update_global::<SettingsStore, _>(|settings, cx| {
10702            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10703                settings.auto_signature_help = Some(false);
10704                settings.show_signature_help_after_edits = Some(true);
10705            });
10706        });
10707    });
10708    cx.set_state(
10709        &r#"
10710            fn main() {
10711                sampleˇ
10712            }
10713        "#
10714        .unindent(),
10715    );
10716    cx.update_editor(|editor, window, cx| {
10717        editor.handle_input("(", window, cx);
10718    });
10719    cx.assert_editor_state(
10720        &"
10721            fn main() {
10722                sample(ˇ)
10723            }
10724        "
10725        .unindent(),
10726    );
10727    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10728    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10729        .await;
10730    cx.update_editor(|editor, _, _| {
10731        let signature_help_state = editor.signature_help_state.popover().cloned();
10732        assert!(signature_help_state.is_some());
10733        assert_eq!(
10734            signature_help_state.unwrap().label,
10735            "param1: u8, param2: u8"
10736        );
10737        editor.signature_help_state = SignatureHelpState::default();
10738    });
10739
10740    // Ensure that signature_help is called when auto signature help override is enabled
10741    cx.update(|_, cx| {
10742        cx.update_global::<SettingsStore, _>(|settings, cx| {
10743            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10744                settings.auto_signature_help = Some(true);
10745                settings.show_signature_help_after_edits = Some(false);
10746            });
10747        });
10748    });
10749    cx.set_state(
10750        &r#"
10751            fn main() {
10752                sampleˇ
10753            }
10754        "#
10755        .unindent(),
10756    );
10757    cx.update_editor(|editor, window, cx| {
10758        editor.handle_input("(", window, cx);
10759    });
10760    cx.assert_editor_state(
10761        &"
10762            fn main() {
10763                sample(ˇ)
10764            }
10765        "
10766        .unindent(),
10767    );
10768    handle_signature_help_request(&mut cx, mocked_response).await;
10769    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10770        .await;
10771    cx.editor(|editor, _, _| {
10772        let signature_help_state = editor.signature_help_state.popover().cloned();
10773        assert!(signature_help_state.is_some());
10774        assert_eq!(
10775            signature_help_state.unwrap().label,
10776            "param1: u8, param2: u8"
10777        );
10778    });
10779}
10780
10781#[gpui::test]
10782async fn test_signature_help(cx: &mut TestAppContext) {
10783    init_test(cx, |_| {});
10784    cx.update(|cx| {
10785        cx.update_global::<SettingsStore, _>(|settings, cx| {
10786            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10787                settings.auto_signature_help = Some(true);
10788            });
10789        });
10790    });
10791
10792    let mut cx = EditorLspTestContext::new_rust(
10793        lsp::ServerCapabilities {
10794            signature_help_provider: Some(lsp::SignatureHelpOptions {
10795                ..Default::default()
10796            }),
10797            ..Default::default()
10798        },
10799        cx,
10800    )
10801    .await;
10802
10803    // A test that directly calls `show_signature_help`
10804    cx.update_editor(|editor, window, cx| {
10805        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10806    });
10807
10808    let mocked_response = lsp::SignatureHelp {
10809        signatures: vec![lsp::SignatureInformation {
10810            label: "fn sample(param1: u8, param2: u8)".to_string(),
10811            documentation: None,
10812            parameters: Some(vec![
10813                lsp::ParameterInformation {
10814                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10815                    documentation: None,
10816                },
10817                lsp::ParameterInformation {
10818                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10819                    documentation: None,
10820                },
10821            ]),
10822            active_parameter: None,
10823        }],
10824        active_signature: Some(0),
10825        active_parameter: Some(0),
10826    };
10827    handle_signature_help_request(&mut cx, mocked_response).await;
10828
10829    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10830        .await;
10831
10832    cx.editor(|editor, _, _| {
10833        let signature_help_state = editor.signature_help_state.popover().cloned();
10834        assert!(signature_help_state.is_some());
10835        assert_eq!(
10836            signature_help_state.unwrap().label,
10837            "param1: u8, param2: u8"
10838        );
10839    });
10840
10841    // When exiting outside from inside the brackets, `signature_help` is closed.
10842    cx.set_state(indoc! {"
10843        fn main() {
10844            sample(ˇ);
10845        }
10846
10847        fn sample(param1: u8, param2: u8) {}
10848    "});
10849
10850    cx.update_editor(|editor, window, cx| {
10851        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10852    });
10853
10854    let mocked_response = lsp::SignatureHelp {
10855        signatures: Vec::new(),
10856        active_signature: None,
10857        active_parameter: None,
10858    };
10859    handle_signature_help_request(&mut cx, mocked_response).await;
10860
10861    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10862        .await;
10863
10864    cx.editor(|editor, _, _| {
10865        assert!(!editor.signature_help_state.is_shown());
10866    });
10867
10868    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10869    cx.set_state(indoc! {"
10870        fn main() {
10871            sample(ˇ);
10872        }
10873
10874        fn sample(param1: u8, param2: u8) {}
10875    "});
10876
10877    let mocked_response = lsp::SignatureHelp {
10878        signatures: vec![lsp::SignatureInformation {
10879            label: "fn sample(param1: u8, param2: u8)".to_string(),
10880            documentation: None,
10881            parameters: Some(vec![
10882                lsp::ParameterInformation {
10883                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10884                    documentation: None,
10885                },
10886                lsp::ParameterInformation {
10887                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10888                    documentation: None,
10889                },
10890            ]),
10891            active_parameter: None,
10892        }],
10893        active_signature: Some(0),
10894        active_parameter: Some(0),
10895    };
10896    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10897    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10898        .await;
10899    cx.editor(|editor, _, _| {
10900        assert!(editor.signature_help_state.is_shown());
10901    });
10902
10903    // Restore the popover with more parameter input
10904    cx.set_state(indoc! {"
10905        fn main() {
10906            sample(param1, param2ˇ);
10907        }
10908
10909        fn sample(param1: u8, param2: u8) {}
10910    "});
10911
10912    let mocked_response = lsp::SignatureHelp {
10913        signatures: vec![lsp::SignatureInformation {
10914            label: "fn sample(param1: u8, param2: u8)".to_string(),
10915            documentation: None,
10916            parameters: Some(vec![
10917                lsp::ParameterInformation {
10918                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10919                    documentation: None,
10920                },
10921                lsp::ParameterInformation {
10922                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10923                    documentation: None,
10924                },
10925            ]),
10926            active_parameter: None,
10927        }],
10928        active_signature: Some(0),
10929        active_parameter: Some(1),
10930    };
10931    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10932    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10933        .await;
10934
10935    // When selecting a range, the popover is gone.
10936    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10937    cx.update_editor(|editor, window, cx| {
10938        editor.change_selections(None, window, cx, |s| {
10939            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10940        })
10941    });
10942    cx.assert_editor_state(indoc! {"
10943        fn main() {
10944            sample(param1, «ˇparam2»);
10945        }
10946
10947        fn sample(param1: u8, param2: u8) {}
10948    "});
10949    cx.editor(|editor, _, _| {
10950        assert!(!editor.signature_help_state.is_shown());
10951    });
10952
10953    // When unselecting again, the popover is back if within the brackets.
10954    cx.update_editor(|editor, window, cx| {
10955        editor.change_selections(None, window, cx, |s| {
10956            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10957        })
10958    });
10959    cx.assert_editor_state(indoc! {"
10960        fn main() {
10961            sample(param1, ˇparam2);
10962        }
10963
10964        fn sample(param1: u8, param2: u8) {}
10965    "});
10966    handle_signature_help_request(&mut cx, mocked_response).await;
10967    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10968        .await;
10969    cx.editor(|editor, _, _| {
10970        assert!(editor.signature_help_state.is_shown());
10971    });
10972
10973    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10974    cx.update_editor(|editor, window, cx| {
10975        editor.change_selections(None, window, cx, |s| {
10976            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10977            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10978        })
10979    });
10980    cx.assert_editor_state(indoc! {"
10981        fn main() {
10982            sample(param1, ˇparam2);
10983        }
10984
10985        fn sample(param1: u8, param2: u8) {}
10986    "});
10987
10988    let mocked_response = lsp::SignatureHelp {
10989        signatures: vec![lsp::SignatureInformation {
10990            label: "fn sample(param1: u8, param2: u8)".to_string(),
10991            documentation: None,
10992            parameters: Some(vec![
10993                lsp::ParameterInformation {
10994                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10995                    documentation: None,
10996                },
10997                lsp::ParameterInformation {
10998                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10999                    documentation: None,
11000                },
11001            ]),
11002            active_parameter: None,
11003        }],
11004        active_signature: Some(0),
11005        active_parameter: Some(1),
11006    };
11007    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11008    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11009        .await;
11010    cx.update_editor(|editor, _, cx| {
11011        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11012    });
11013    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11014        .await;
11015    cx.update_editor(|editor, window, cx| {
11016        editor.change_selections(None, window, cx, |s| {
11017            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11018        })
11019    });
11020    cx.assert_editor_state(indoc! {"
11021        fn main() {
11022            sample(param1, «ˇparam2»);
11023        }
11024
11025        fn sample(param1: u8, param2: u8) {}
11026    "});
11027    cx.update_editor(|editor, window, cx| {
11028        editor.change_selections(None, window, cx, |s| {
11029            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11030        })
11031    });
11032    cx.assert_editor_state(indoc! {"
11033        fn main() {
11034            sample(param1, ˇparam2);
11035        }
11036
11037        fn sample(param1: u8, param2: u8) {}
11038    "});
11039    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11040        .await;
11041}
11042
11043#[gpui::test]
11044async fn test_completion_mode(cx: &mut TestAppContext) {
11045    init_test(cx, |_| {});
11046    let mut cx = EditorLspTestContext::new_rust(
11047        lsp::ServerCapabilities {
11048            completion_provider: Some(lsp::CompletionOptions {
11049                resolve_provider: Some(true),
11050                ..Default::default()
11051            }),
11052            ..Default::default()
11053        },
11054        cx,
11055    )
11056    .await;
11057
11058    struct Run {
11059        run_description: &'static str,
11060        initial_state: String,
11061        buffer_marked_text: String,
11062        completion_label: &'static str,
11063        completion_text: &'static str,
11064        expected_with_insert_mode: String,
11065        expected_with_replace_mode: String,
11066        expected_with_replace_subsequence_mode: String,
11067        expected_with_replace_suffix_mode: String,
11068    }
11069
11070    let runs = [
11071        Run {
11072            run_description: "Start of word matches completion text",
11073            initial_state: "before ediˇ after".into(),
11074            buffer_marked_text: "before <edi|> after".into(),
11075            completion_label: "editor",
11076            completion_text: "editor",
11077            expected_with_insert_mode: "before editorˇ after".into(),
11078            expected_with_replace_mode: "before editorˇ after".into(),
11079            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11080            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11081        },
11082        Run {
11083            run_description: "Accept same text at the middle of the word",
11084            initial_state: "before ediˇtor after".into(),
11085            buffer_marked_text: "before <edi|tor> after".into(),
11086            completion_label: "editor",
11087            completion_text: "editor",
11088            expected_with_insert_mode: "before editorˇtor after".into(),
11089            expected_with_replace_mode: "before editorˇ after".into(),
11090            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11091            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11092        },
11093        Run {
11094            run_description: "End of word matches completion text -- cursor at end",
11095            initial_state: "before torˇ after".into(),
11096            buffer_marked_text: "before <tor|> after".into(),
11097            completion_label: "editor",
11098            completion_text: "editor",
11099            expected_with_insert_mode: "before editorˇ after".into(),
11100            expected_with_replace_mode: "before editorˇ after".into(),
11101            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11102            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11103        },
11104        Run {
11105            run_description: "End of word matches completion text -- cursor at start",
11106            initial_state: "before ˇtor after".into(),
11107            buffer_marked_text: "before <|tor> after".into(),
11108            completion_label: "editor",
11109            completion_text: "editor",
11110            expected_with_insert_mode: "before editorˇtor after".into(),
11111            expected_with_replace_mode: "before editorˇ after".into(),
11112            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11113            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11114        },
11115        Run {
11116            run_description: "Prepend text containing whitespace",
11117            initial_state: "pˇfield: bool".into(),
11118            buffer_marked_text: "<p|field>: bool".into(),
11119            completion_label: "pub ",
11120            completion_text: "pub ",
11121            expected_with_insert_mode: "pub ˇfield: bool".into(),
11122            expected_with_replace_mode: "pub ˇ: bool".into(),
11123            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11124            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11125        },
11126        Run {
11127            run_description: "Add element to start of list",
11128            initial_state: "[element_ˇelement_2]".into(),
11129            buffer_marked_text: "[<element_|element_2>]".into(),
11130            completion_label: "element_1",
11131            completion_text: "element_1",
11132            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11133            expected_with_replace_mode: "[element_1ˇ]".into(),
11134            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11135            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11136        },
11137        Run {
11138            run_description: "Add element to start of list -- first and second elements are equal",
11139            initial_state: "[elˇelement]".into(),
11140            buffer_marked_text: "[<el|element>]".into(),
11141            completion_label: "element",
11142            completion_text: "element",
11143            expected_with_insert_mode: "[elementˇelement]".into(),
11144            expected_with_replace_mode: "[elementˇ]".into(),
11145            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11146            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11147        },
11148        Run {
11149            run_description: "Ends with matching suffix",
11150            initial_state: "SubˇError".into(),
11151            buffer_marked_text: "<Sub|Error>".into(),
11152            completion_label: "SubscriptionError",
11153            completion_text: "SubscriptionError",
11154            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11155            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11156            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11157            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11158        },
11159        Run {
11160            run_description: "Suffix is a subsequence -- contiguous",
11161            initial_state: "SubˇErr".into(),
11162            buffer_marked_text: "<Sub|Err>".into(),
11163            completion_label: "SubscriptionError",
11164            completion_text: "SubscriptionError",
11165            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11166            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11167            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11168            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11169        },
11170        Run {
11171            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11172            initial_state: "Suˇscrirr".into(),
11173            buffer_marked_text: "<Su|scrirr>".into(),
11174            completion_label: "SubscriptionError",
11175            completion_text: "SubscriptionError",
11176            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11177            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11178            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11179            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11180        },
11181        Run {
11182            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11183            initial_state: "foo(indˇix)".into(),
11184            buffer_marked_text: "foo(<ind|ix>)".into(),
11185            completion_label: "node_index",
11186            completion_text: "node_index",
11187            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11188            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11189            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11190            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11191        },
11192        Run {
11193            run_description: "Replace range ends before cursor - should extend to cursor",
11194            initial_state: "before editˇo after".into(),
11195            buffer_marked_text: "before <{ed}>it|o after".into(),
11196            completion_label: "editor",
11197            completion_text: "editor",
11198            expected_with_insert_mode: "before editorˇo after".into(),
11199            expected_with_replace_mode: "before editorˇo after".into(),
11200            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11201            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11202        },
11203        Run {
11204            run_description: "Uses label for suffix matching",
11205            initial_state: "before ediˇtor after".into(),
11206            buffer_marked_text: "before <edi|tor> after".into(),
11207            completion_label: "editor",
11208            completion_text: "editor()",
11209            expected_with_insert_mode: "before editor()ˇtor after".into(),
11210            expected_with_replace_mode: "before editor()ˇ after".into(),
11211            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11212            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11213        },
11214        Run {
11215            run_description: "Case insensitive subsequence and suffix matching",
11216            initial_state: "before EDiˇtoR after".into(),
11217            buffer_marked_text: "before <EDi|toR> after".into(),
11218            completion_label: "editor",
11219            completion_text: "editor",
11220            expected_with_insert_mode: "before editorˇtoR after".into(),
11221            expected_with_replace_mode: "before editorˇ after".into(),
11222            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11223            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11224        },
11225    ];
11226
11227    for run in runs {
11228        let run_variations = [
11229            (LspInsertMode::Insert, run.expected_with_insert_mode),
11230            (LspInsertMode::Replace, run.expected_with_replace_mode),
11231            (
11232                LspInsertMode::ReplaceSubsequence,
11233                run.expected_with_replace_subsequence_mode,
11234            ),
11235            (
11236                LspInsertMode::ReplaceSuffix,
11237                run.expected_with_replace_suffix_mode,
11238            ),
11239        ];
11240
11241        for (lsp_insert_mode, expected_text) in run_variations {
11242            eprintln!(
11243                "run = {:?}, mode = {lsp_insert_mode:.?}",
11244                run.run_description,
11245            );
11246
11247            update_test_language_settings(&mut cx, |settings| {
11248                settings.defaults.completions = Some(CompletionSettings {
11249                    lsp_insert_mode,
11250                    words: WordsCompletionMode::Disabled,
11251                    lsp: true,
11252                    lsp_fetch_timeout_ms: 0,
11253                });
11254            });
11255
11256            cx.set_state(&run.initial_state);
11257            cx.update_editor(|editor, window, cx| {
11258                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11259            });
11260
11261            let counter = Arc::new(AtomicUsize::new(0));
11262            handle_completion_request_with_insert_and_replace(
11263                &mut cx,
11264                &run.buffer_marked_text,
11265                vec![(run.completion_label, run.completion_text)],
11266                counter.clone(),
11267            )
11268            .await;
11269            cx.condition(|editor, _| editor.context_menu_visible())
11270                .await;
11271            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11272
11273            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11274                editor
11275                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11276                    .unwrap()
11277            });
11278            cx.assert_editor_state(&expected_text);
11279            handle_resolve_completion_request(&mut cx, None).await;
11280            apply_additional_edits.await.unwrap();
11281        }
11282    }
11283}
11284
11285#[gpui::test]
11286async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11287    init_test(cx, |_| {});
11288    let mut cx = EditorLspTestContext::new_rust(
11289        lsp::ServerCapabilities {
11290            completion_provider: Some(lsp::CompletionOptions {
11291                resolve_provider: Some(true),
11292                ..Default::default()
11293            }),
11294            ..Default::default()
11295        },
11296        cx,
11297    )
11298    .await;
11299
11300    let initial_state = "SubˇError";
11301    let buffer_marked_text = "<Sub|Error>";
11302    let completion_text = "SubscriptionError";
11303    let expected_with_insert_mode = "SubscriptionErrorˇError";
11304    let expected_with_replace_mode = "SubscriptionErrorˇ";
11305
11306    update_test_language_settings(&mut cx, |settings| {
11307        settings.defaults.completions = Some(CompletionSettings {
11308            words: WordsCompletionMode::Disabled,
11309            // set the opposite here to ensure that the action is overriding the default behavior
11310            lsp_insert_mode: LspInsertMode::Insert,
11311            lsp: true,
11312            lsp_fetch_timeout_ms: 0,
11313        });
11314    });
11315
11316    cx.set_state(initial_state);
11317    cx.update_editor(|editor, window, cx| {
11318        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11319    });
11320
11321    let counter = Arc::new(AtomicUsize::new(0));
11322    handle_completion_request_with_insert_and_replace(
11323        &mut cx,
11324        &buffer_marked_text,
11325        vec![(completion_text, completion_text)],
11326        counter.clone(),
11327    )
11328    .await;
11329    cx.condition(|editor, _| editor.context_menu_visible())
11330        .await;
11331    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11332
11333    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11334        editor
11335            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11336            .unwrap()
11337    });
11338    cx.assert_editor_state(&expected_with_replace_mode);
11339    handle_resolve_completion_request(&mut cx, None).await;
11340    apply_additional_edits.await.unwrap();
11341
11342    update_test_language_settings(&mut cx, |settings| {
11343        settings.defaults.completions = Some(CompletionSettings {
11344            words: WordsCompletionMode::Disabled,
11345            // set the opposite here to ensure that the action is overriding the default behavior
11346            lsp_insert_mode: LspInsertMode::Replace,
11347            lsp: true,
11348            lsp_fetch_timeout_ms: 0,
11349        });
11350    });
11351
11352    cx.set_state(initial_state);
11353    cx.update_editor(|editor, window, cx| {
11354        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11355    });
11356    handle_completion_request_with_insert_and_replace(
11357        &mut cx,
11358        &buffer_marked_text,
11359        vec![(completion_text, completion_text)],
11360        counter.clone(),
11361    )
11362    .await;
11363    cx.condition(|editor, _| editor.context_menu_visible())
11364        .await;
11365    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11366
11367    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11368        editor
11369            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11370            .unwrap()
11371    });
11372    cx.assert_editor_state(&expected_with_insert_mode);
11373    handle_resolve_completion_request(&mut cx, None).await;
11374    apply_additional_edits.await.unwrap();
11375}
11376
11377#[gpui::test]
11378async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11379    init_test(cx, |_| {});
11380    let mut cx = EditorLspTestContext::new_rust(
11381        lsp::ServerCapabilities {
11382            completion_provider: Some(lsp::CompletionOptions {
11383                resolve_provider: Some(true),
11384                ..Default::default()
11385            }),
11386            ..Default::default()
11387        },
11388        cx,
11389    )
11390    .await;
11391
11392    // scenario: surrounding text matches completion text
11393    let completion_text = "to_offset";
11394    let initial_state = indoc! {"
11395        1. buf.to_offˇsuffix
11396        2. buf.to_offˇsuf
11397        3. buf.to_offˇfix
11398        4. buf.to_offˇ
11399        5. into_offˇensive
11400        6. ˇsuffix
11401        7. let ˇ //
11402        8. aaˇzz
11403        9. buf.to_off«zzzzzˇ»suffix
11404        10. buf.«ˇzzzzz»suffix
11405        11. to_off«ˇzzzzz»
11406
11407        buf.to_offˇsuffix  // newest cursor
11408    "};
11409    let completion_marked_buffer = indoc! {"
11410        1. buf.to_offsuffix
11411        2. buf.to_offsuf
11412        3. buf.to_offfix
11413        4. buf.to_off
11414        5. into_offensive
11415        6. suffix
11416        7. let  //
11417        8. aazz
11418        9. buf.to_offzzzzzsuffix
11419        10. buf.zzzzzsuffix
11420        11. to_offzzzzz
11421
11422        buf.<to_off|suffix>  // newest cursor
11423    "};
11424    let expected = indoc! {"
11425        1. buf.to_offsetˇ
11426        2. buf.to_offsetˇsuf
11427        3. buf.to_offsetˇfix
11428        4. buf.to_offsetˇ
11429        5. into_offsetˇensive
11430        6. to_offsetˇsuffix
11431        7. let to_offsetˇ //
11432        8. aato_offsetˇzz
11433        9. buf.to_offsetˇ
11434        10. buf.to_offsetˇsuffix
11435        11. to_offsetˇ
11436
11437        buf.to_offsetˇ  // newest cursor
11438    "};
11439    cx.set_state(initial_state);
11440    cx.update_editor(|editor, window, cx| {
11441        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11442    });
11443    handle_completion_request_with_insert_and_replace(
11444        &mut cx,
11445        completion_marked_buffer,
11446        vec![(completion_text, completion_text)],
11447        Arc::new(AtomicUsize::new(0)),
11448    )
11449    .await;
11450    cx.condition(|editor, _| editor.context_menu_visible())
11451        .await;
11452    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11453        editor
11454            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11455            .unwrap()
11456    });
11457    cx.assert_editor_state(expected);
11458    handle_resolve_completion_request(&mut cx, None).await;
11459    apply_additional_edits.await.unwrap();
11460
11461    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11462    let completion_text = "foo_and_bar";
11463    let initial_state = indoc! {"
11464        1. ooanbˇ
11465        2. zooanbˇ
11466        3. ooanbˇz
11467        4. zooanbˇz
11468        5. ooanˇ
11469        6. oanbˇ
11470
11471        ooanbˇ
11472    "};
11473    let completion_marked_buffer = indoc! {"
11474        1. ooanb
11475        2. zooanb
11476        3. ooanbz
11477        4. zooanbz
11478        5. ooan
11479        6. oanb
11480
11481        <ooanb|>
11482    "};
11483    let expected = indoc! {"
11484        1. foo_and_barˇ
11485        2. zfoo_and_barˇ
11486        3. foo_and_barˇz
11487        4. zfoo_and_barˇz
11488        5. ooanfoo_and_barˇ
11489        6. oanbfoo_and_barˇ
11490
11491        foo_and_barˇ
11492    "};
11493    cx.set_state(initial_state);
11494    cx.update_editor(|editor, window, cx| {
11495        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11496    });
11497    handle_completion_request_with_insert_and_replace(
11498        &mut cx,
11499        completion_marked_buffer,
11500        vec![(completion_text, completion_text)],
11501        Arc::new(AtomicUsize::new(0)),
11502    )
11503    .await;
11504    cx.condition(|editor, _| editor.context_menu_visible())
11505        .await;
11506    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11507        editor
11508            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11509            .unwrap()
11510    });
11511    cx.assert_editor_state(expected);
11512    handle_resolve_completion_request(&mut cx, None).await;
11513    apply_additional_edits.await.unwrap();
11514
11515    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11516    // (expects the same as if it was inserted at the end)
11517    let completion_text = "foo_and_bar";
11518    let initial_state = indoc! {"
11519        1. ooˇanb
11520        2. zooˇanb
11521        3. ooˇanbz
11522        4. zooˇanbz
11523
11524        ooˇanb
11525    "};
11526    let completion_marked_buffer = indoc! {"
11527        1. ooanb
11528        2. zooanb
11529        3. ooanbz
11530        4. zooanbz
11531
11532        <oo|anb>
11533    "};
11534    let expected = indoc! {"
11535        1. foo_and_barˇ
11536        2. zfoo_and_barˇ
11537        3. foo_and_barˇz
11538        4. zfoo_and_barˇz
11539
11540        foo_and_barˇ
11541    "};
11542    cx.set_state(initial_state);
11543    cx.update_editor(|editor, window, cx| {
11544        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11545    });
11546    handle_completion_request_with_insert_and_replace(
11547        &mut cx,
11548        completion_marked_buffer,
11549        vec![(completion_text, completion_text)],
11550        Arc::new(AtomicUsize::new(0)),
11551    )
11552    .await;
11553    cx.condition(|editor, _| editor.context_menu_visible())
11554        .await;
11555    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11556        editor
11557            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11558            .unwrap()
11559    });
11560    cx.assert_editor_state(expected);
11561    handle_resolve_completion_request(&mut cx, None).await;
11562    apply_additional_edits.await.unwrap();
11563}
11564
11565// This used to crash
11566#[gpui::test]
11567async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11568    init_test(cx, |_| {});
11569
11570    let buffer_text = indoc! {"
11571        fn main() {
11572            10.satu;
11573
11574            //
11575            // separate cursors so they open in different excerpts (manually reproducible)
11576            //
11577
11578            10.satu20;
11579        }
11580    "};
11581    let multibuffer_text_with_selections = indoc! {"
11582        fn main() {
11583            10.satuˇ;
11584
11585            //
11586
11587            //
11588
11589            10.satuˇ20;
11590        }
11591    "};
11592    let expected_multibuffer = indoc! {"
11593        fn main() {
11594            10.saturating_sub()ˇ;
11595
11596            //
11597
11598            //
11599
11600            10.saturating_sub()ˇ;
11601        }
11602    "};
11603
11604    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11605    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11606
11607    let fs = FakeFs::new(cx.executor());
11608    fs.insert_tree(
11609        path!("/a"),
11610        json!({
11611            "main.rs": buffer_text,
11612        }),
11613    )
11614    .await;
11615
11616    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11617    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11618    language_registry.add(rust_lang());
11619    let mut fake_servers = language_registry.register_fake_lsp(
11620        "Rust",
11621        FakeLspAdapter {
11622            capabilities: lsp::ServerCapabilities {
11623                completion_provider: Some(lsp::CompletionOptions {
11624                    resolve_provider: None,
11625                    ..lsp::CompletionOptions::default()
11626                }),
11627                ..lsp::ServerCapabilities::default()
11628            },
11629            ..FakeLspAdapter::default()
11630        },
11631    );
11632    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11633    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11634    let buffer = project
11635        .update(cx, |project, cx| {
11636            project.open_local_buffer(path!("/a/main.rs"), cx)
11637        })
11638        .await
11639        .unwrap();
11640
11641    let multi_buffer = cx.new(|cx| {
11642        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11643        multi_buffer.push_excerpts(
11644            buffer.clone(),
11645            [ExcerptRange::new(0..first_excerpt_end)],
11646            cx,
11647        );
11648        multi_buffer.push_excerpts(
11649            buffer.clone(),
11650            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11651            cx,
11652        );
11653        multi_buffer
11654    });
11655
11656    let editor = workspace
11657        .update(cx, |_, window, cx| {
11658            cx.new(|cx| {
11659                Editor::new(
11660                    EditorMode::Full {
11661                        scale_ui_elements_with_buffer_font_size: false,
11662                        show_active_line_background: false,
11663                        sized_by_content: false,
11664                    },
11665                    multi_buffer.clone(),
11666                    Some(project.clone()),
11667                    window,
11668                    cx,
11669                )
11670            })
11671        })
11672        .unwrap();
11673
11674    let pane = workspace
11675        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11676        .unwrap();
11677    pane.update_in(cx, |pane, window, cx| {
11678        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11679    });
11680
11681    let fake_server = fake_servers.next().await.unwrap();
11682
11683    editor.update_in(cx, |editor, window, cx| {
11684        editor.change_selections(None, window, cx, |s| {
11685            s.select_ranges([
11686                Point::new(1, 11)..Point::new(1, 11),
11687                Point::new(7, 11)..Point::new(7, 11),
11688            ])
11689        });
11690
11691        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11692    });
11693
11694    editor.update_in(cx, |editor, window, cx| {
11695        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11696    });
11697
11698    fake_server
11699        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11700            let completion_item = lsp::CompletionItem {
11701                label: "saturating_sub()".into(),
11702                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11703                    lsp::InsertReplaceEdit {
11704                        new_text: "saturating_sub()".to_owned(),
11705                        insert: lsp::Range::new(
11706                            lsp::Position::new(7, 7),
11707                            lsp::Position::new(7, 11),
11708                        ),
11709                        replace: lsp::Range::new(
11710                            lsp::Position::new(7, 7),
11711                            lsp::Position::new(7, 13),
11712                        ),
11713                    },
11714                )),
11715                ..lsp::CompletionItem::default()
11716            };
11717
11718            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11719        })
11720        .next()
11721        .await
11722        .unwrap();
11723
11724    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11725        .await;
11726
11727    editor
11728        .update_in(cx, |editor, window, cx| {
11729            editor
11730                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11731                .unwrap()
11732        })
11733        .await
11734        .unwrap();
11735
11736    editor.update(cx, |editor, cx| {
11737        assert_text_with_selections(editor, expected_multibuffer, cx);
11738    })
11739}
11740
11741#[gpui::test]
11742async fn test_completion(cx: &mut TestAppContext) {
11743    init_test(cx, |_| {});
11744
11745    let mut cx = EditorLspTestContext::new_rust(
11746        lsp::ServerCapabilities {
11747            completion_provider: Some(lsp::CompletionOptions {
11748                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11749                resolve_provider: Some(true),
11750                ..Default::default()
11751            }),
11752            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11753            ..Default::default()
11754        },
11755        cx,
11756    )
11757    .await;
11758    let counter = Arc::new(AtomicUsize::new(0));
11759
11760    cx.set_state(indoc! {"
11761        oneˇ
11762        two
11763        three
11764    "});
11765    cx.simulate_keystroke(".");
11766    handle_completion_request(
11767        indoc! {"
11768            one.|<>
11769            two
11770            three
11771        "},
11772        vec!["first_completion", "second_completion"],
11773        true,
11774        counter.clone(),
11775        &mut cx,
11776    )
11777    .await;
11778    cx.condition(|editor, _| editor.context_menu_visible())
11779        .await;
11780    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11781
11782    let _handler = handle_signature_help_request(
11783        &mut cx,
11784        lsp::SignatureHelp {
11785            signatures: vec![lsp::SignatureInformation {
11786                label: "test signature".to_string(),
11787                documentation: None,
11788                parameters: Some(vec![lsp::ParameterInformation {
11789                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11790                    documentation: None,
11791                }]),
11792                active_parameter: None,
11793            }],
11794            active_signature: None,
11795            active_parameter: None,
11796        },
11797    );
11798    cx.update_editor(|editor, window, cx| {
11799        assert!(
11800            !editor.signature_help_state.is_shown(),
11801            "No signature help was called for"
11802        );
11803        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11804    });
11805    cx.run_until_parked();
11806    cx.update_editor(|editor, _, _| {
11807        assert!(
11808            !editor.signature_help_state.is_shown(),
11809            "No signature help should be shown when completions menu is open"
11810        );
11811    });
11812
11813    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11814        editor.context_menu_next(&Default::default(), window, cx);
11815        editor
11816            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11817            .unwrap()
11818    });
11819    cx.assert_editor_state(indoc! {"
11820        one.second_completionˇ
11821        two
11822        three
11823    "});
11824
11825    handle_resolve_completion_request(
11826        &mut cx,
11827        Some(vec![
11828            (
11829                //This overlaps with the primary completion edit which is
11830                //misbehavior from the LSP spec, test that we filter it out
11831                indoc! {"
11832                    one.second_ˇcompletion
11833                    two
11834                    threeˇ
11835                "},
11836                "overlapping additional edit",
11837            ),
11838            (
11839                indoc! {"
11840                    one.second_completion
11841                    two
11842                    threeˇ
11843                "},
11844                "\nadditional edit",
11845            ),
11846        ]),
11847    )
11848    .await;
11849    apply_additional_edits.await.unwrap();
11850    cx.assert_editor_state(indoc! {"
11851        one.second_completionˇ
11852        two
11853        three
11854        additional edit
11855    "});
11856
11857    cx.set_state(indoc! {"
11858        one.second_completion
11859        twoˇ
11860        threeˇ
11861        additional edit
11862    "});
11863    cx.simulate_keystroke(" ");
11864    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11865    cx.simulate_keystroke("s");
11866    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11867
11868    cx.assert_editor_state(indoc! {"
11869        one.second_completion
11870        two sˇ
11871        three sˇ
11872        additional edit
11873    "});
11874    handle_completion_request(
11875        indoc! {"
11876            one.second_completion
11877            two s
11878            three <s|>
11879            additional edit
11880        "},
11881        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11882        true,
11883        counter.clone(),
11884        &mut cx,
11885    )
11886    .await;
11887    cx.condition(|editor, _| editor.context_menu_visible())
11888        .await;
11889    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11890
11891    cx.simulate_keystroke("i");
11892
11893    handle_completion_request(
11894        indoc! {"
11895            one.second_completion
11896            two si
11897            three <si|>
11898            additional edit
11899        "},
11900        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11901        true,
11902        counter.clone(),
11903        &mut cx,
11904    )
11905    .await;
11906    cx.condition(|editor, _| editor.context_menu_visible())
11907        .await;
11908    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11909
11910    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11911        editor
11912            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11913            .unwrap()
11914    });
11915    cx.assert_editor_state(indoc! {"
11916        one.second_completion
11917        two sixth_completionˇ
11918        three sixth_completionˇ
11919        additional edit
11920    "});
11921
11922    apply_additional_edits.await.unwrap();
11923
11924    update_test_language_settings(&mut cx, |settings| {
11925        settings.defaults.show_completions_on_input = Some(false);
11926    });
11927    cx.set_state("editorˇ");
11928    cx.simulate_keystroke(".");
11929    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11930    cx.simulate_keystrokes("c l o");
11931    cx.assert_editor_state("editor.cloˇ");
11932    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11933    cx.update_editor(|editor, window, cx| {
11934        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11935    });
11936    handle_completion_request(
11937        "editor.<clo|>",
11938        vec!["close", "clobber"],
11939        true,
11940        counter.clone(),
11941        &mut cx,
11942    )
11943    .await;
11944    cx.condition(|editor, _| editor.context_menu_visible())
11945        .await;
11946    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11947
11948    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11949        editor
11950            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11951            .unwrap()
11952    });
11953    cx.assert_editor_state("editor.clobberˇ");
11954    handle_resolve_completion_request(&mut cx, None).await;
11955    apply_additional_edits.await.unwrap();
11956}
11957
11958#[gpui::test]
11959async fn test_completion_reuse(cx: &mut TestAppContext) {
11960    init_test(cx, |_| {});
11961
11962    let mut cx = EditorLspTestContext::new_rust(
11963        lsp::ServerCapabilities {
11964            completion_provider: Some(lsp::CompletionOptions {
11965                trigger_characters: Some(vec![".".to_string()]),
11966                ..Default::default()
11967            }),
11968            ..Default::default()
11969        },
11970        cx,
11971    )
11972    .await;
11973
11974    let counter = Arc::new(AtomicUsize::new(0));
11975    cx.set_state("objˇ");
11976    cx.simulate_keystroke(".");
11977
11978    // Initial completion request returns complete results
11979    let is_incomplete = false;
11980    handle_completion_request(
11981        "obj.|<>",
11982        vec!["a", "ab", "abc"],
11983        is_incomplete,
11984        counter.clone(),
11985        &mut cx,
11986    )
11987    .await;
11988    cx.run_until_parked();
11989    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11990    cx.assert_editor_state("obj.ˇ");
11991    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11992
11993    // Type "a" - filters existing completions
11994    cx.simulate_keystroke("a");
11995    cx.run_until_parked();
11996    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11997    cx.assert_editor_state("obj.aˇ");
11998    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11999
12000    // Type "b" - filters existing completions
12001    cx.simulate_keystroke("b");
12002    cx.run_until_parked();
12003    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12004    cx.assert_editor_state("obj.abˇ");
12005    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12006
12007    // Type "c" - filters existing completions
12008    cx.simulate_keystroke("c");
12009    cx.run_until_parked();
12010    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12011    cx.assert_editor_state("obj.abcˇ");
12012    check_displayed_completions(vec!["abc"], &mut cx);
12013
12014    // Backspace to delete "c" - filters existing completions
12015    cx.update_editor(|editor, window, cx| {
12016        editor.backspace(&Backspace, window, cx);
12017    });
12018    cx.run_until_parked();
12019    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12020    cx.assert_editor_state("obj.abˇ");
12021    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12022
12023    // Moving cursor to the left dismisses menu.
12024    cx.update_editor(|editor, window, cx| {
12025        editor.move_left(&MoveLeft, window, cx);
12026    });
12027    cx.run_until_parked();
12028    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12029    cx.assert_editor_state("obj.aˇb");
12030    cx.update_editor(|editor, _, _| {
12031        assert_eq!(editor.context_menu_visible(), false);
12032    });
12033
12034    // Type "b" - new request
12035    cx.simulate_keystroke("b");
12036    let is_incomplete = false;
12037    handle_completion_request(
12038        "obj.<ab|>a",
12039        vec!["ab", "abc"],
12040        is_incomplete,
12041        counter.clone(),
12042        &mut cx,
12043    )
12044    .await;
12045    cx.run_until_parked();
12046    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12047    cx.assert_editor_state("obj.abˇb");
12048    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12049
12050    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12051    cx.update_editor(|editor, window, cx| {
12052        editor.backspace(&Backspace, window, cx);
12053    });
12054    let is_incomplete = false;
12055    handle_completion_request(
12056        "obj.<a|>b",
12057        vec!["a", "ab", "abc"],
12058        is_incomplete,
12059        counter.clone(),
12060        &mut cx,
12061    )
12062    .await;
12063    cx.run_until_parked();
12064    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12065    cx.assert_editor_state("obj.aˇb");
12066    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12067
12068    // Backspace to delete "a" - dismisses menu.
12069    cx.update_editor(|editor, window, cx| {
12070        editor.backspace(&Backspace, window, cx);
12071    });
12072    cx.run_until_parked();
12073    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12074    cx.assert_editor_state("obj.ˇb");
12075    cx.update_editor(|editor, _, _| {
12076        assert_eq!(editor.context_menu_visible(), false);
12077    });
12078}
12079
12080#[gpui::test]
12081async fn test_word_completion(cx: &mut TestAppContext) {
12082    let lsp_fetch_timeout_ms = 10;
12083    init_test(cx, |language_settings| {
12084        language_settings.defaults.completions = Some(CompletionSettings {
12085            words: WordsCompletionMode::Fallback,
12086            lsp: true,
12087            lsp_fetch_timeout_ms: 10,
12088            lsp_insert_mode: LspInsertMode::Insert,
12089        });
12090    });
12091
12092    let mut cx = EditorLspTestContext::new_rust(
12093        lsp::ServerCapabilities {
12094            completion_provider: Some(lsp::CompletionOptions {
12095                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12096                ..lsp::CompletionOptions::default()
12097            }),
12098            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12099            ..lsp::ServerCapabilities::default()
12100        },
12101        cx,
12102    )
12103    .await;
12104
12105    let throttle_completions = Arc::new(AtomicBool::new(false));
12106
12107    let lsp_throttle_completions = throttle_completions.clone();
12108    let _completion_requests_handler =
12109        cx.lsp
12110            .server
12111            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12112                let lsp_throttle_completions = lsp_throttle_completions.clone();
12113                let cx = cx.clone();
12114                async move {
12115                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12116                        cx.background_executor()
12117                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12118                            .await;
12119                    }
12120                    Ok(Some(lsp::CompletionResponse::Array(vec![
12121                        lsp::CompletionItem {
12122                            label: "first".into(),
12123                            ..lsp::CompletionItem::default()
12124                        },
12125                        lsp::CompletionItem {
12126                            label: "last".into(),
12127                            ..lsp::CompletionItem::default()
12128                        },
12129                    ])))
12130                }
12131            });
12132
12133    cx.set_state(indoc! {"
12134        oneˇ
12135        two
12136        three
12137    "});
12138    cx.simulate_keystroke(".");
12139    cx.executor().run_until_parked();
12140    cx.condition(|editor, _| editor.context_menu_visible())
12141        .await;
12142    cx.update_editor(|editor, window, cx| {
12143        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12144        {
12145            assert_eq!(
12146                completion_menu_entries(&menu),
12147                &["first", "last"],
12148                "When LSP server is fast to reply, no fallback word completions are used"
12149            );
12150        } else {
12151            panic!("expected completion menu to be open");
12152        }
12153        editor.cancel(&Cancel, window, cx);
12154    });
12155    cx.executor().run_until_parked();
12156    cx.condition(|editor, _| !editor.context_menu_visible())
12157        .await;
12158
12159    throttle_completions.store(true, atomic::Ordering::Release);
12160    cx.simulate_keystroke(".");
12161    cx.executor()
12162        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12163    cx.executor().run_until_parked();
12164    cx.condition(|editor, _| editor.context_menu_visible())
12165        .await;
12166    cx.update_editor(|editor, _, _| {
12167        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12168        {
12169            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12170                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12171        } else {
12172            panic!("expected completion menu to be open");
12173        }
12174    });
12175}
12176
12177#[gpui::test]
12178async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12179    init_test(cx, |language_settings| {
12180        language_settings.defaults.completions = Some(CompletionSettings {
12181            words: WordsCompletionMode::Enabled,
12182            lsp: true,
12183            lsp_fetch_timeout_ms: 0,
12184            lsp_insert_mode: LspInsertMode::Insert,
12185        });
12186    });
12187
12188    let mut cx = EditorLspTestContext::new_rust(
12189        lsp::ServerCapabilities {
12190            completion_provider: Some(lsp::CompletionOptions {
12191                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12192                ..lsp::CompletionOptions::default()
12193            }),
12194            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12195            ..lsp::ServerCapabilities::default()
12196        },
12197        cx,
12198    )
12199    .await;
12200
12201    let _completion_requests_handler =
12202        cx.lsp
12203            .server
12204            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12205                Ok(Some(lsp::CompletionResponse::Array(vec![
12206                    lsp::CompletionItem {
12207                        label: "first".into(),
12208                        ..lsp::CompletionItem::default()
12209                    },
12210                    lsp::CompletionItem {
12211                        label: "last".into(),
12212                        ..lsp::CompletionItem::default()
12213                    },
12214                ])))
12215            });
12216
12217    cx.set_state(indoc! {"ˇ
12218        first
12219        last
12220        second
12221    "});
12222    cx.simulate_keystroke(".");
12223    cx.executor().run_until_parked();
12224    cx.condition(|editor, _| editor.context_menu_visible())
12225        .await;
12226    cx.update_editor(|editor, _, _| {
12227        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12228        {
12229            assert_eq!(
12230                completion_menu_entries(&menu),
12231                &["first", "last", "second"],
12232                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12233            );
12234        } else {
12235            panic!("expected completion menu to be open");
12236        }
12237    });
12238}
12239
12240#[gpui::test]
12241async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12242    init_test(cx, |language_settings| {
12243        language_settings.defaults.completions = Some(CompletionSettings {
12244            words: WordsCompletionMode::Disabled,
12245            lsp: true,
12246            lsp_fetch_timeout_ms: 0,
12247            lsp_insert_mode: LspInsertMode::Insert,
12248        });
12249    });
12250
12251    let mut cx = EditorLspTestContext::new_rust(
12252        lsp::ServerCapabilities {
12253            completion_provider: Some(lsp::CompletionOptions {
12254                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12255                ..lsp::CompletionOptions::default()
12256            }),
12257            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12258            ..lsp::ServerCapabilities::default()
12259        },
12260        cx,
12261    )
12262    .await;
12263
12264    let _completion_requests_handler =
12265        cx.lsp
12266            .server
12267            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12268                panic!("LSP completions should not be queried when dealing with word completions")
12269            });
12270
12271    cx.set_state(indoc! {"ˇ
12272        first
12273        last
12274        second
12275    "});
12276    cx.update_editor(|editor, window, cx| {
12277        editor.show_word_completions(&ShowWordCompletions, window, cx);
12278    });
12279    cx.executor().run_until_parked();
12280    cx.condition(|editor, _| editor.context_menu_visible())
12281        .await;
12282    cx.update_editor(|editor, _, _| {
12283        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12284        {
12285            assert_eq!(
12286                completion_menu_entries(&menu),
12287                &["first", "last", "second"],
12288                "`ShowWordCompletions` action should show word completions"
12289            );
12290        } else {
12291            panic!("expected completion menu to be open");
12292        }
12293    });
12294
12295    cx.simulate_keystroke("l");
12296    cx.executor().run_until_parked();
12297    cx.condition(|editor, _| editor.context_menu_visible())
12298        .await;
12299    cx.update_editor(|editor, _, _| {
12300        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12301        {
12302            assert_eq!(
12303                completion_menu_entries(&menu),
12304                &["last"],
12305                "After showing word completions, further editing should filter them and not query the LSP"
12306            );
12307        } else {
12308            panic!("expected completion menu to be open");
12309        }
12310    });
12311}
12312
12313#[gpui::test]
12314async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12315    init_test(cx, |language_settings| {
12316        language_settings.defaults.completions = Some(CompletionSettings {
12317            words: WordsCompletionMode::Fallback,
12318            lsp: false,
12319            lsp_fetch_timeout_ms: 0,
12320            lsp_insert_mode: LspInsertMode::Insert,
12321        });
12322    });
12323
12324    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12325
12326    cx.set_state(indoc! {"ˇ
12327        0_usize
12328        let
12329        33
12330        4.5f32
12331    "});
12332    cx.update_editor(|editor, window, cx| {
12333        editor.show_completions(&ShowCompletions::default(), window, cx);
12334    });
12335    cx.executor().run_until_parked();
12336    cx.condition(|editor, _| editor.context_menu_visible())
12337        .await;
12338    cx.update_editor(|editor, window, cx| {
12339        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12340        {
12341            assert_eq!(
12342                completion_menu_entries(&menu),
12343                &["let"],
12344                "With no digits in the completion query, no digits should be in the word completions"
12345            );
12346        } else {
12347            panic!("expected completion menu to be open");
12348        }
12349        editor.cancel(&Cancel, window, cx);
12350    });
12351
12352    cx.set_state(indoc! {"12353        0_usize
12354        let
12355        3
12356        33.35f32
12357    "});
12358    cx.update_editor(|editor, window, cx| {
12359        editor.show_completions(&ShowCompletions::default(), window, cx);
12360    });
12361    cx.executor().run_until_parked();
12362    cx.condition(|editor, _| editor.context_menu_visible())
12363        .await;
12364    cx.update_editor(|editor, _, _| {
12365        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12366        {
12367            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12368                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12369        } else {
12370            panic!("expected completion menu to be open");
12371        }
12372    });
12373}
12374
12375fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12376    let position = || lsp::Position {
12377        line: params.text_document_position.position.line,
12378        character: params.text_document_position.position.character,
12379    };
12380    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12381        range: lsp::Range {
12382            start: position(),
12383            end: position(),
12384        },
12385        new_text: text.to_string(),
12386    }))
12387}
12388
12389#[gpui::test]
12390async fn test_multiline_completion(cx: &mut TestAppContext) {
12391    init_test(cx, |_| {});
12392
12393    let fs = FakeFs::new(cx.executor());
12394    fs.insert_tree(
12395        path!("/a"),
12396        json!({
12397            "main.ts": "a",
12398        }),
12399    )
12400    .await;
12401
12402    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12403    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12404    let typescript_language = Arc::new(Language::new(
12405        LanguageConfig {
12406            name: "TypeScript".into(),
12407            matcher: LanguageMatcher {
12408                path_suffixes: vec!["ts".to_string()],
12409                ..LanguageMatcher::default()
12410            },
12411            line_comments: vec!["// ".into()],
12412            ..LanguageConfig::default()
12413        },
12414        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12415    ));
12416    language_registry.add(typescript_language.clone());
12417    let mut fake_servers = language_registry.register_fake_lsp(
12418        "TypeScript",
12419        FakeLspAdapter {
12420            capabilities: lsp::ServerCapabilities {
12421                completion_provider: Some(lsp::CompletionOptions {
12422                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12423                    ..lsp::CompletionOptions::default()
12424                }),
12425                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12426                ..lsp::ServerCapabilities::default()
12427            },
12428            // Emulate vtsls label generation
12429            label_for_completion: Some(Box::new(|item, _| {
12430                let text = if let Some(description) = item
12431                    .label_details
12432                    .as_ref()
12433                    .and_then(|label_details| label_details.description.as_ref())
12434                {
12435                    format!("{} {}", item.label, description)
12436                } else if let Some(detail) = &item.detail {
12437                    format!("{} {}", item.label, detail)
12438                } else {
12439                    item.label.clone()
12440                };
12441                let len = text.len();
12442                Some(language::CodeLabel {
12443                    text,
12444                    runs: Vec::new(),
12445                    filter_range: 0..len,
12446                })
12447            })),
12448            ..FakeLspAdapter::default()
12449        },
12450    );
12451    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12452    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12453    let worktree_id = workspace
12454        .update(cx, |workspace, _window, cx| {
12455            workspace.project().update(cx, |project, cx| {
12456                project.worktrees(cx).next().unwrap().read(cx).id()
12457            })
12458        })
12459        .unwrap();
12460    let _buffer = project
12461        .update(cx, |project, cx| {
12462            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12463        })
12464        .await
12465        .unwrap();
12466    let editor = workspace
12467        .update(cx, |workspace, window, cx| {
12468            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12469        })
12470        .unwrap()
12471        .await
12472        .unwrap()
12473        .downcast::<Editor>()
12474        .unwrap();
12475    let fake_server = fake_servers.next().await.unwrap();
12476
12477    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12478    let multiline_label_2 = "a\nb\nc\n";
12479    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12480    let multiline_description = "d\ne\nf\n";
12481    let multiline_detail_2 = "g\nh\ni\n";
12482
12483    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12484        move |params, _| async move {
12485            Ok(Some(lsp::CompletionResponse::Array(vec![
12486                lsp::CompletionItem {
12487                    label: multiline_label.to_string(),
12488                    text_edit: gen_text_edit(&params, "new_text_1"),
12489                    ..lsp::CompletionItem::default()
12490                },
12491                lsp::CompletionItem {
12492                    label: "single line label 1".to_string(),
12493                    detail: Some(multiline_detail.to_string()),
12494                    text_edit: gen_text_edit(&params, "new_text_2"),
12495                    ..lsp::CompletionItem::default()
12496                },
12497                lsp::CompletionItem {
12498                    label: "single line label 2".to_string(),
12499                    label_details: Some(lsp::CompletionItemLabelDetails {
12500                        description: Some(multiline_description.to_string()),
12501                        detail: None,
12502                    }),
12503                    text_edit: gen_text_edit(&params, "new_text_2"),
12504                    ..lsp::CompletionItem::default()
12505                },
12506                lsp::CompletionItem {
12507                    label: multiline_label_2.to_string(),
12508                    detail: Some(multiline_detail_2.to_string()),
12509                    text_edit: gen_text_edit(&params, "new_text_3"),
12510                    ..lsp::CompletionItem::default()
12511                },
12512                lsp::CompletionItem {
12513                    label: "Label with many     spaces and \t but without newlines".to_string(),
12514                    detail: Some(
12515                        "Details with many     spaces and \t but without newlines".to_string(),
12516                    ),
12517                    text_edit: gen_text_edit(&params, "new_text_4"),
12518                    ..lsp::CompletionItem::default()
12519                },
12520            ])))
12521        },
12522    );
12523
12524    editor.update_in(cx, |editor, window, cx| {
12525        cx.focus_self(window);
12526        editor.move_to_end(&MoveToEnd, window, cx);
12527        editor.handle_input(".", window, cx);
12528    });
12529    cx.run_until_parked();
12530    completion_handle.next().await.unwrap();
12531
12532    editor.update(cx, |editor, _| {
12533        assert!(editor.context_menu_visible());
12534        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12535        {
12536            let completion_labels = menu
12537                .completions
12538                .borrow()
12539                .iter()
12540                .map(|c| c.label.text.clone())
12541                .collect::<Vec<_>>();
12542            assert_eq!(
12543                completion_labels,
12544                &[
12545                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12546                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12547                    "single line label 2 d e f ",
12548                    "a b c g h i ",
12549                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12550                ],
12551                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12552            );
12553
12554            for completion in menu
12555                .completions
12556                .borrow()
12557                .iter() {
12558                    assert_eq!(
12559                        completion.label.filter_range,
12560                        0..completion.label.text.len(),
12561                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12562                    );
12563                }
12564        } else {
12565            panic!("expected completion menu to be open");
12566        }
12567    });
12568}
12569
12570#[gpui::test]
12571async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12572    init_test(cx, |_| {});
12573    let mut cx = EditorLspTestContext::new_rust(
12574        lsp::ServerCapabilities {
12575            completion_provider: Some(lsp::CompletionOptions {
12576                trigger_characters: Some(vec![".".to_string()]),
12577                ..Default::default()
12578            }),
12579            ..Default::default()
12580        },
12581        cx,
12582    )
12583    .await;
12584    cx.lsp
12585        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12586            Ok(Some(lsp::CompletionResponse::Array(vec![
12587                lsp::CompletionItem {
12588                    label: "first".into(),
12589                    ..Default::default()
12590                },
12591                lsp::CompletionItem {
12592                    label: "last".into(),
12593                    ..Default::default()
12594                },
12595            ])))
12596        });
12597    cx.set_state("variableˇ");
12598    cx.simulate_keystroke(".");
12599    cx.executor().run_until_parked();
12600
12601    cx.update_editor(|editor, _, _| {
12602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12603        {
12604            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12605        } else {
12606            panic!("expected completion menu to be open");
12607        }
12608    });
12609
12610    cx.update_editor(|editor, window, cx| {
12611        editor.move_page_down(&MovePageDown::default(), window, cx);
12612        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12613        {
12614            assert!(
12615                menu.selected_item == 1,
12616                "expected PageDown to select the last item from the context menu"
12617            );
12618        } else {
12619            panic!("expected completion menu to stay open after PageDown");
12620        }
12621    });
12622
12623    cx.update_editor(|editor, window, cx| {
12624        editor.move_page_up(&MovePageUp::default(), window, cx);
12625        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12626        {
12627            assert!(
12628                menu.selected_item == 0,
12629                "expected PageUp to select the first item from the context menu"
12630            );
12631        } else {
12632            panic!("expected completion menu to stay open after PageUp");
12633        }
12634    });
12635}
12636
12637#[gpui::test]
12638async fn test_as_is_completions(cx: &mut TestAppContext) {
12639    init_test(cx, |_| {});
12640    let mut cx = EditorLspTestContext::new_rust(
12641        lsp::ServerCapabilities {
12642            completion_provider: Some(lsp::CompletionOptions {
12643                ..Default::default()
12644            }),
12645            ..Default::default()
12646        },
12647        cx,
12648    )
12649    .await;
12650    cx.lsp
12651        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12652            Ok(Some(lsp::CompletionResponse::Array(vec![
12653                lsp::CompletionItem {
12654                    label: "unsafe".into(),
12655                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12656                        range: lsp::Range {
12657                            start: lsp::Position {
12658                                line: 1,
12659                                character: 2,
12660                            },
12661                            end: lsp::Position {
12662                                line: 1,
12663                                character: 3,
12664                            },
12665                        },
12666                        new_text: "unsafe".to_string(),
12667                    })),
12668                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12669                    ..Default::default()
12670                },
12671            ])))
12672        });
12673    cx.set_state("fn a() {}\n");
12674    cx.executor().run_until_parked();
12675    cx.update_editor(|editor, window, cx| {
12676        editor.show_completions(
12677            &ShowCompletions {
12678                trigger: Some("\n".into()),
12679            },
12680            window,
12681            cx,
12682        );
12683    });
12684    cx.executor().run_until_parked();
12685
12686    cx.update_editor(|editor, window, cx| {
12687        editor.confirm_completion(&Default::default(), window, cx)
12688    });
12689    cx.executor().run_until_parked();
12690    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12691}
12692
12693#[gpui::test]
12694async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12695    init_test(cx, |_| {});
12696
12697    let mut cx = EditorLspTestContext::new_rust(
12698        lsp::ServerCapabilities {
12699            completion_provider: Some(lsp::CompletionOptions {
12700                trigger_characters: Some(vec![".".to_string()]),
12701                resolve_provider: Some(true),
12702                ..Default::default()
12703            }),
12704            ..Default::default()
12705        },
12706        cx,
12707    )
12708    .await;
12709
12710    cx.set_state("fn main() { let a = 2ˇ; }");
12711    cx.simulate_keystroke(".");
12712    let completion_item = lsp::CompletionItem {
12713        label: "Some".into(),
12714        kind: Some(lsp::CompletionItemKind::SNIPPET),
12715        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12716        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12717            kind: lsp::MarkupKind::Markdown,
12718            value: "```rust\nSome(2)\n```".to_string(),
12719        })),
12720        deprecated: Some(false),
12721        sort_text: Some("Some".to_string()),
12722        filter_text: Some("Some".to_string()),
12723        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12724        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12725            range: lsp::Range {
12726                start: lsp::Position {
12727                    line: 0,
12728                    character: 22,
12729                },
12730                end: lsp::Position {
12731                    line: 0,
12732                    character: 22,
12733                },
12734            },
12735            new_text: "Some(2)".to_string(),
12736        })),
12737        additional_text_edits: Some(vec![lsp::TextEdit {
12738            range: lsp::Range {
12739                start: lsp::Position {
12740                    line: 0,
12741                    character: 20,
12742                },
12743                end: lsp::Position {
12744                    line: 0,
12745                    character: 22,
12746                },
12747            },
12748            new_text: "".to_string(),
12749        }]),
12750        ..Default::default()
12751    };
12752
12753    let closure_completion_item = completion_item.clone();
12754    let counter = Arc::new(AtomicUsize::new(0));
12755    let counter_clone = counter.clone();
12756    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12757        let task_completion_item = closure_completion_item.clone();
12758        counter_clone.fetch_add(1, atomic::Ordering::Release);
12759        async move {
12760            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12761                is_incomplete: true,
12762                item_defaults: None,
12763                items: vec![task_completion_item],
12764            })))
12765        }
12766    });
12767
12768    cx.condition(|editor, _| editor.context_menu_visible())
12769        .await;
12770    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12771    assert!(request.next().await.is_some());
12772    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12773
12774    cx.simulate_keystrokes("S o m");
12775    cx.condition(|editor, _| editor.context_menu_visible())
12776        .await;
12777    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12778    assert!(request.next().await.is_some());
12779    assert!(request.next().await.is_some());
12780    assert!(request.next().await.is_some());
12781    request.close();
12782    assert!(request.next().await.is_none());
12783    assert_eq!(
12784        counter.load(atomic::Ordering::Acquire),
12785        4,
12786        "With the completions menu open, only one LSP request should happen per input"
12787    );
12788}
12789
12790#[gpui::test]
12791async fn test_toggle_comment(cx: &mut TestAppContext) {
12792    init_test(cx, |_| {});
12793    let mut cx = EditorTestContext::new(cx).await;
12794    let language = Arc::new(Language::new(
12795        LanguageConfig {
12796            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12797            ..Default::default()
12798        },
12799        Some(tree_sitter_rust::LANGUAGE.into()),
12800    ));
12801    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12802
12803    // If multiple selections intersect a line, the line is only toggled once.
12804    cx.set_state(indoc! {"
12805        fn a() {
12806            «//b();
12807            ˇ»// «c();
12808            //ˇ»  d();
12809        }
12810    "});
12811
12812    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12813
12814    cx.assert_editor_state(indoc! {"
12815        fn a() {
12816            «b();
12817            c();
12818            ˇ» d();
12819        }
12820    "});
12821
12822    // The comment prefix is inserted at the same column for every line in a
12823    // selection.
12824    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12825
12826    cx.assert_editor_state(indoc! {"
12827        fn a() {
12828            // «b();
12829            // c();
12830            ˇ»//  d();
12831        }
12832    "});
12833
12834    // If a selection ends at the beginning of a line, that line is not toggled.
12835    cx.set_selections_state(indoc! {"
12836        fn a() {
12837            // b();
12838            «// c();
12839        ˇ»    //  d();
12840        }
12841    "});
12842
12843    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12844
12845    cx.assert_editor_state(indoc! {"
12846        fn a() {
12847            // b();
12848            «c();
12849        ˇ»    //  d();
12850        }
12851    "});
12852
12853    // If a selection span a single line and is empty, the line is toggled.
12854    cx.set_state(indoc! {"
12855        fn a() {
12856            a();
12857            b();
12858        ˇ
12859        }
12860    "});
12861
12862    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12863
12864    cx.assert_editor_state(indoc! {"
12865        fn a() {
12866            a();
12867            b();
12868        //•ˇ
12869        }
12870    "});
12871
12872    // If a selection span multiple lines, empty lines are not toggled.
12873    cx.set_state(indoc! {"
12874        fn a() {
12875            «a();
12876
12877            c();ˇ»
12878        }
12879    "});
12880
12881    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12882
12883    cx.assert_editor_state(indoc! {"
12884        fn a() {
12885            // «a();
12886
12887            // c();ˇ»
12888        }
12889    "});
12890
12891    // If a selection includes multiple comment prefixes, all lines are uncommented.
12892    cx.set_state(indoc! {"
12893        fn a() {
12894            «// a();
12895            /// b();
12896            //! c();ˇ»
12897        }
12898    "});
12899
12900    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12901
12902    cx.assert_editor_state(indoc! {"
12903        fn a() {
12904            «a();
12905            b();
12906            c();ˇ»
12907        }
12908    "});
12909}
12910
12911#[gpui::test]
12912async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12913    init_test(cx, |_| {});
12914    let mut cx = EditorTestContext::new(cx).await;
12915    let language = Arc::new(Language::new(
12916        LanguageConfig {
12917            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12918            ..Default::default()
12919        },
12920        Some(tree_sitter_rust::LANGUAGE.into()),
12921    ));
12922    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12923
12924    let toggle_comments = &ToggleComments {
12925        advance_downwards: false,
12926        ignore_indent: true,
12927    };
12928
12929    // If multiple selections intersect a line, the line is only toggled once.
12930    cx.set_state(indoc! {"
12931        fn a() {
12932        //    «b();
12933        //    c();
12934        //    ˇ» d();
12935        }
12936    "});
12937
12938    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12939
12940    cx.assert_editor_state(indoc! {"
12941        fn a() {
12942            «b();
12943            c();
12944            ˇ» d();
12945        }
12946    "});
12947
12948    // The comment prefix is inserted at the beginning of each line
12949    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12950
12951    cx.assert_editor_state(indoc! {"
12952        fn a() {
12953        //    «b();
12954        //    c();
12955        //    ˇ» d();
12956        }
12957    "});
12958
12959    // If a selection ends at the beginning of a line, that line is not toggled.
12960    cx.set_selections_state(indoc! {"
12961        fn a() {
12962        //    b();
12963        //    «c();
12964        ˇ»//     d();
12965        }
12966    "});
12967
12968    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12969
12970    cx.assert_editor_state(indoc! {"
12971        fn a() {
12972        //    b();
12973            «c();
12974        ˇ»//     d();
12975        }
12976    "});
12977
12978    // If a selection span a single line and is empty, the line is toggled.
12979    cx.set_state(indoc! {"
12980        fn a() {
12981            a();
12982            b();
12983        ˇ
12984        }
12985    "});
12986
12987    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12988
12989    cx.assert_editor_state(indoc! {"
12990        fn a() {
12991            a();
12992            b();
12993        //ˇ
12994        }
12995    "});
12996
12997    // If a selection span multiple lines, empty lines are not toggled.
12998    cx.set_state(indoc! {"
12999        fn a() {
13000            «a();
13001
13002            c();ˇ»
13003        }
13004    "});
13005
13006    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13007
13008    cx.assert_editor_state(indoc! {"
13009        fn a() {
13010        //    «a();
13011
13012        //    c();ˇ»
13013        }
13014    "});
13015
13016    // If a selection includes multiple comment prefixes, all lines are uncommented.
13017    cx.set_state(indoc! {"
13018        fn a() {
13019        //    «a();
13020        ///    b();
13021        //!    c();ˇ»
13022        }
13023    "});
13024
13025    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13026
13027    cx.assert_editor_state(indoc! {"
13028        fn a() {
13029            «a();
13030            b();
13031            c();ˇ»
13032        }
13033    "});
13034}
13035
13036#[gpui::test]
13037async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13038    init_test(cx, |_| {});
13039
13040    let language = Arc::new(Language::new(
13041        LanguageConfig {
13042            line_comments: vec!["// ".into()],
13043            ..Default::default()
13044        },
13045        Some(tree_sitter_rust::LANGUAGE.into()),
13046    ));
13047
13048    let mut cx = EditorTestContext::new(cx).await;
13049
13050    cx.language_registry().add(language.clone());
13051    cx.update_buffer(|buffer, cx| {
13052        buffer.set_language(Some(language), cx);
13053    });
13054
13055    let toggle_comments = &ToggleComments {
13056        advance_downwards: true,
13057        ignore_indent: false,
13058    };
13059
13060    // Single cursor on one line -> advance
13061    // Cursor moves horizontally 3 characters as well on non-blank line
13062    cx.set_state(indoc!(
13063        "fn a() {
13064             ˇdog();
13065             cat();
13066        }"
13067    ));
13068    cx.update_editor(|editor, window, cx| {
13069        editor.toggle_comments(toggle_comments, window, cx);
13070    });
13071    cx.assert_editor_state(indoc!(
13072        "fn a() {
13073             // dog();
13074             catˇ();
13075        }"
13076    ));
13077
13078    // Single selection on one line -> don't advance
13079    cx.set_state(indoc!(
13080        "fn a() {
13081             «dog()ˇ»;
13082             cat();
13083        }"
13084    ));
13085    cx.update_editor(|editor, window, cx| {
13086        editor.toggle_comments(toggle_comments, window, cx);
13087    });
13088    cx.assert_editor_state(indoc!(
13089        "fn a() {
13090             // «dog()ˇ»;
13091             cat();
13092        }"
13093    ));
13094
13095    // Multiple cursors on one line -> advance
13096    cx.set_state(indoc!(
13097        "fn a() {
13098             ˇdˇog();
13099             cat();
13100        }"
13101    ));
13102    cx.update_editor(|editor, window, cx| {
13103        editor.toggle_comments(toggle_comments, window, cx);
13104    });
13105    cx.assert_editor_state(indoc!(
13106        "fn a() {
13107             // dog();
13108             catˇ(ˇ);
13109        }"
13110    ));
13111
13112    // Multiple cursors on one line, with selection -> don't advance
13113    cx.set_state(indoc!(
13114        "fn a() {
13115             ˇdˇog«()ˇ»;
13116             cat();
13117        }"
13118    ));
13119    cx.update_editor(|editor, window, cx| {
13120        editor.toggle_comments(toggle_comments, window, cx);
13121    });
13122    cx.assert_editor_state(indoc!(
13123        "fn a() {
13124             // ˇdˇog«()ˇ»;
13125             cat();
13126        }"
13127    ));
13128
13129    // Single cursor on one line -> advance
13130    // Cursor moves to column 0 on blank line
13131    cx.set_state(indoc!(
13132        "fn a() {
13133             ˇdog();
13134
13135             cat();
13136        }"
13137    ));
13138    cx.update_editor(|editor, window, cx| {
13139        editor.toggle_comments(toggle_comments, window, cx);
13140    });
13141    cx.assert_editor_state(indoc!(
13142        "fn a() {
13143             // dog();
13144        ˇ
13145             cat();
13146        }"
13147    ));
13148
13149    // Single cursor on one line -> advance
13150    // Cursor starts and ends at column 0
13151    cx.set_state(indoc!(
13152        "fn a() {
13153         ˇ    dog();
13154             cat();
13155        }"
13156    ));
13157    cx.update_editor(|editor, window, cx| {
13158        editor.toggle_comments(toggle_comments, window, cx);
13159    });
13160    cx.assert_editor_state(indoc!(
13161        "fn a() {
13162             // dog();
13163         ˇ    cat();
13164        }"
13165    ));
13166}
13167
13168#[gpui::test]
13169async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13170    init_test(cx, |_| {});
13171
13172    let mut cx = EditorTestContext::new(cx).await;
13173
13174    let html_language = Arc::new(
13175        Language::new(
13176            LanguageConfig {
13177                name: "HTML".into(),
13178                block_comment: Some(("<!-- ".into(), " -->".into())),
13179                ..Default::default()
13180            },
13181            Some(tree_sitter_html::LANGUAGE.into()),
13182        )
13183        .with_injection_query(
13184            r#"
13185            (script_element
13186                (raw_text) @injection.content
13187                (#set! injection.language "javascript"))
13188            "#,
13189        )
13190        .unwrap(),
13191    );
13192
13193    let javascript_language = Arc::new(Language::new(
13194        LanguageConfig {
13195            name: "JavaScript".into(),
13196            line_comments: vec!["// ".into()],
13197            ..Default::default()
13198        },
13199        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13200    ));
13201
13202    cx.language_registry().add(html_language.clone());
13203    cx.language_registry().add(javascript_language.clone());
13204    cx.update_buffer(|buffer, cx| {
13205        buffer.set_language(Some(html_language), cx);
13206    });
13207
13208    // Toggle comments for empty selections
13209    cx.set_state(
13210        &r#"
13211            <p>A</p>ˇ
13212            <p>B</p>ˇ
13213            <p>C</p>ˇ
13214        "#
13215        .unindent(),
13216    );
13217    cx.update_editor(|editor, window, cx| {
13218        editor.toggle_comments(&ToggleComments::default(), window, cx)
13219    });
13220    cx.assert_editor_state(
13221        &r#"
13222            <!-- <p>A</p>ˇ -->
13223            <!-- <p>B</p>ˇ -->
13224            <!-- <p>C</p>ˇ -->
13225        "#
13226        .unindent(),
13227    );
13228    cx.update_editor(|editor, window, cx| {
13229        editor.toggle_comments(&ToggleComments::default(), window, cx)
13230    });
13231    cx.assert_editor_state(
13232        &r#"
13233            <p>A</p>ˇ
13234            <p>B</p>ˇ
13235            <p>C</p>ˇ
13236        "#
13237        .unindent(),
13238    );
13239
13240    // Toggle comments for mixture of empty and non-empty selections, where
13241    // multiple selections occupy a given line.
13242    cx.set_state(
13243        &r#"
13244            <p>A«</p>
13245            <p>ˇ»B</p>ˇ
13246            <p>C«</p>
13247            <p>ˇ»D</p>ˇ
13248        "#
13249        .unindent(),
13250    );
13251
13252    cx.update_editor(|editor, window, cx| {
13253        editor.toggle_comments(&ToggleComments::default(), window, cx)
13254    });
13255    cx.assert_editor_state(
13256        &r#"
13257            <!-- <p>A«</p>
13258            <p>ˇ»B</p>ˇ -->
13259            <!-- <p>C«</p>
13260            <p>ˇ»D</p>ˇ -->
13261        "#
13262        .unindent(),
13263    );
13264    cx.update_editor(|editor, window, cx| {
13265        editor.toggle_comments(&ToggleComments::default(), window, cx)
13266    });
13267    cx.assert_editor_state(
13268        &r#"
13269            <p>A«</p>
13270            <p>ˇ»B</p>ˇ
13271            <p>C«</p>
13272            <p>ˇ»D</p>ˇ
13273        "#
13274        .unindent(),
13275    );
13276
13277    // Toggle comments when different languages are active for different
13278    // selections.
13279    cx.set_state(
13280        &r#"
13281            ˇ<script>
13282                ˇvar x = new Y();
13283            ˇ</script>
13284        "#
13285        .unindent(),
13286    );
13287    cx.executor().run_until_parked();
13288    cx.update_editor(|editor, window, cx| {
13289        editor.toggle_comments(&ToggleComments::default(), window, cx)
13290    });
13291    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13292    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13293    cx.assert_editor_state(
13294        &r#"
13295            <!-- ˇ<script> -->
13296                // ˇvar x = new Y();
13297            <!-- ˇ</script> -->
13298        "#
13299        .unindent(),
13300    );
13301}
13302
13303#[gpui::test]
13304fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13305    init_test(cx, |_| {});
13306
13307    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13308    let multibuffer = cx.new(|cx| {
13309        let mut multibuffer = MultiBuffer::new(ReadWrite);
13310        multibuffer.push_excerpts(
13311            buffer.clone(),
13312            [
13313                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13314                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13315            ],
13316            cx,
13317        );
13318        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13319        multibuffer
13320    });
13321
13322    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13323    editor.update_in(cx, |editor, window, cx| {
13324        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13325        editor.change_selections(None, window, cx, |s| {
13326            s.select_ranges([
13327                Point::new(0, 0)..Point::new(0, 0),
13328                Point::new(1, 0)..Point::new(1, 0),
13329            ])
13330        });
13331
13332        editor.handle_input("X", window, cx);
13333        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13334        assert_eq!(
13335            editor.selections.ranges(cx),
13336            [
13337                Point::new(0, 1)..Point::new(0, 1),
13338                Point::new(1, 1)..Point::new(1, 1),
13339            ]
13340        );
13341
13342        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13343        editor.change_selections(None, window, cx, |s| {
13344            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13345        });
13346        editor.backspace(&Default::default(), window, cx);
13347        assert_eq!(editor.text(cx), "Xa\nbbb");
13348        assert_eq!(
13349            editor.selections.ranges(cx),
13350            [Point::new(1, 0)..Point::new(1, 0)]
13351        );
13352
13353        editor.change_selections(None, window, cx, |s| {
13354            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13355        });
13356        editor.backspace(&Default::default(), window, cx);
13357        assert_eq!(editor.text(cx), "X\nbb");
13358        assert_eq!(
13359            editor.selections.ranges(cx),
13360            [Point::new(0, 1)..Point::new(0, 1)]
13361        );
13362    });
13363}
13364
13365#[gpui::test]
13366fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13367    init_test(cx, |_| {});
13368
13369    let markers = vec![('[', ']').into(), ('(', ')').into()];
13370    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13371        indoc! {"
13372            [aaaa
13373            (bbbb]
13374            cccc)",
13375        },
13376        markers.clone(),
13377    );
13378    let excerpt_ranges = markers.into_iter().map(|marker| {
13379        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13380        ExcerptRange::new(context.clone())
13381    });
13382    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13383    let multibuffer = cx.new(|cx| {
13384        let mut multibuffer = MultiBuffer::new(ReadWrite);
13385        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13386        multibuffer
13387    });
13388
13389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13390    editor.update_in(cx, |editor, window, cx| {
13391        let (expected_text, selection_ranges) = marked_text_ranges(
13392            indoc! {"
13393                aaaa
13394                bˇbbb
13395                bˇbbˇb
13396                cccc"
13397            },
13398            true,
13399        );
13400        assert_eq!(editor.text(cx), expected_text);
13401        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13402
13403        editor.handle_input("X", window, cx);
13404
13405        let (expected_text, expected_selections) = marked_text_ranges(
13406            indoc! {"
13407                aaaa
13408                bXˇbbXb
13409                bXˇbbXˇb
13410                cccc"
13411            },
13412            false,
13413        );
13414        assert_eq!(editor.text(cx), expected_text);
13415        assert_eq!(editor.selections.ranges(cx), expected_selections);
13416
13417        editor.newline(&Newline, window, cx);
13418        let (expected_text, expected_selections) = marked_text_ranges(
13419            indoc! {"
13420                aaaa
13421                bX
13422                ˇbbX
13423                b
13424                bX
13425                ˇbbX
13426                ˇb
13427                cccc"
13428            },
13429            false,
13430        );
13431        assert_eq!(editor.text(cx), expected_text);
13432        assert_eq!(editor.selections.ranges(cx), expected_selections);
13433    });
13434}
13435
13436#[gpui::test]
13437fn test_refresh_selections(cx: &mut TestAppContext) {
13438    init_test(cx, |_| {});
13439
13440    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13441    let mut excerpt1_id = None;
13442    let multibuffer = cx.new(|cx| {
13443        let mut multibuffer = MultiBuffer::new(ReadWrite);
13444        excerpt1_id = multibuffer
13445            .push_excerpts(
13446                buffer.clone(),
13447                [
13448                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13449                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13450                ],
13451                cx,
13452            )
13453            .into_iter()
13454            .next();
13455        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13456        multibuffer
13457    });
13458
13459    let editor = cx.add_window(|window, cx| {
13460        let mut editor = build_editor(multibuffer.clone(), window, cx);
13461        let snapshot = editor.snapshot(window, cx);
13462        editor.change_selections(None, window, cx, |s| {
13463            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13464        });
13465        editor.begin_selection(
13466            Point::new(2, 1).to_display_point(&snapshot),
13467            true,
13468            1,
13469            window,
13470            cx,
13471        );
13472        assert_eq!(
13473            editor.selections.ranges(cx),
13474            [
13475                Point::new(1, 3)..Point::new(1, 3),
13476                Point::new(2, 1)..Point::new(2, 1),
13477            ]
13478        );
13479        editor
13480    });
13481
13482    // Refreshing selections is a no-op when excerpts haven't changed.
13483    _ = editor.update(cx, |editor, window, cx| {
13484        editor.change_selections(None, window, cx, |s| s.refresh());
13485        assert_eq!(
13486            editor.selections.ranges(cx),
13487            [
13488                Point::new(1, 3)..Point::new(1, 3),
13489                Point::new(2, 1)..Point::new(2, 1),
13490            ]
13491        );
13492    });
13493
13494    multibuffer.update(cx, |multibuffer, cx| {
13495        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13496    });
13497    _ = editor.update(cx, |editor, window, cx| {
13498        // Removing an excerpt causes the first selection to become degenerate.
13499        assert_eq!(
13500            editor.selections.ranges(cx),
13501            [
13502                Point::new(0, 0)..Point::new(0, 0),
13503                Point::new(0, 1)..Point::new(0, 1)
13504            ]
13505        );
13506
13507        // Refreshing selections will relocate the first selection to the original buffer
13508        // location.
13509        editor.change_selections(None, window, cx, |s| s.refresh());
13510        assert_eq!(
13511            editor.selections.ranges(cx),
13512            [
13513                Point::new(0, 1)..Point::new(0, 1),
13514                Point::new(0, 3)..Point::new(0, 3)
13515            ]
13516        );
13517        assert!(editor.selections.pending_anchor().is_some());
13518    });
13519}
13520
13521#[gpui::test]
13522fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13523    init_test(cx, |_| {});
13524
13525    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13526    let mut excerpt1_id = None;
13527    let multibuffer = cx.new(|cx| {
13528        let mut multibuffer = MultiBuffer::new(ReadWrite);
13529        excerpt1_id = multibuffer
13530            .push_excerpts(
13531                buffer.clone(),
13532                [
13533                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13534                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13535                ],
13536                cx,
13537            )
13538            .into_iter()
13539            .next();
13540        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13541        multibuffer
13542    });
13543
13544    let editor = cx.add_window(|window, cx| {
13545        let mut editor = build_editor(multibuffer.clone(), window, cx);
13546        let snapshot = editor.snapshot(window, cx);
13547        editor.begin_selection(
13548            Point::new(1, 3).to_display_point(&snapshot),
13549            false,
13550            1,
13551            window,
13552            cx,
13553        );
13554        assert_eq!(
13555            editor.selections.ranges(cx),
13556            [Point::new(1, 3)..Point::new(1, 3)]
13557        );
13558        editor
13559    });
13560
13561    multibuffer.update(cx, |multibuffer, cx| {
13562        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13563    });
13564    _ = editor.update(cx, |editor, window, cx| {
13565        assert_eq!(
13566            editor.selections.ranges(cx),
13567            [Point::new(0, 0)..Point::new(0, 0)]
13568        );
13569
13570        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13571        editor.change_selections(None, window, cx, |s| s.refresh());
13572        assert_eq!(
13573            editor.selections.ranges(cx),
13574            [Point::new(0, 3)..Point::new(0, 3)]
13575        );
13576        assert!(editor.selections.pending_anchor().is_some());
13577    });
13578}
13579
13580#[gpui::test]
13581async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13582    init_test(cx, |_| {});
13583
13584    let language = Arc::new(
13585        Language::new(
13586            LanguageConfig {
13587                brackets: BracketPairConfig {
13588                    pairs: vec![
13589                        BracketPair {
13590                            start: "{".to_string(),
13591                            end: "}".to_string(),
13592                            close: true,
13593                            surround: true,
13594                            newline: true,
13595                        },
13596                        BracketPair {
13597                            start: "/* ".to_string(),
13598                            end: " */".to_string(),
13599                            close: true,
13600                            surround: true,
13601                            newline: true,
13602                        },
13603                    ],
13604                    ..Default::default()
13605                },
13606                ..Default::default()
13607            },
13608            Some(tree_sitter_rust::LANGUAGE.into()),
13609        )
13610        .with_indents_query("")
13611        .unwrap(),
13612    );
13613
13614    let text = concat!(
13615        "{   }\n",     //
13616        "  x\n",       //
13617        "  /*   */\n", //
13618        "x\n",         //
13619        "{{} }\n",     //
13620    );
13621
13622    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13623    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13624    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13625    editor
13626        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13627        .await;
13628
13629    editor.update_in(cx, |editor, window, cx| {
13630        editor.change_selections(None, window, cx, |s| {
13631            s.select_display_ranges([
13632                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13633                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13634                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13635            ])
13636        });
13637        editor.newline(&Newline, window, cx);
13638
13639        assert_eq!(
13640            editor.buffer().read(cx).read(cx).text(),
13641            concat!(
13642                "{ \n",    // Suppress rustfmt
13643                "\n",      //
13644                "}\n",     //
13645                "  x\n",   //
13646                "  /* \n", //
13647                "  \n",    //
13648                "  */\n",  //
13649                "x\n",     //
13650                "{{} \n",  //
13651                "}\n",     //
13652            )
13653        );
13654    });
13655}
13656
13657#[gpui::test]
13658fn test_highlighted_ranges(cx: &mut TestAppContext) {
13659    init_test(cx, |_| {});
13660
13661    let editor = cx.add_window(|window, cx| {
13662        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13663        build_editor(buffer.clone(), window, cx)
13664    });
13665
13666    _ = editor.update(cx, |editor, window, cx| {
13667        struct Type1;
13668        struct Type2;
13669
13670        let buffer = editor.buffer.read(cx).snapshot(cx);
13671
13672        let anchor_range =
13673            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13674
13675        editor.highlight_background::<Type1>(
13676            &[
13677                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13678                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13679                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13680                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13681            ],
13682            |_| Hsla::red(),
13683            cx,
13684        );
13685        editor.highlight_background::<Type2>(
13686            &[
13687                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13688                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13689                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13690                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13691            ],
13692            |_| Hsla::green(),
13693            cx,
13694        );
13695
13696        let snapshot = editor.snapshot(window, cx);
13697        let mut highlighted_ranges = editor.background_highlights_in_range(
13698            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13699            &snapshot,
13700            cx.theme(),
13701        );
13702        // Enforce a consistent ordering based on color without relying on the ordering of the
13703        // highlight's `TypeId` which is non-executor.
13704        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13705        assert_eq!(
13706            highlighted_ranges,
13707            &[
13708                (
13709                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13710                    Hsla::red(),
13711                ),
13712                (
13713                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13714                    Hsla::red(),
13715                ),
13716                (
13717                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13718                    Hsla::green(),
13719                ),
13720                (
13721                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13722                    Hsla::green(),
13723                ),
13724            ]
13725        );
13726        assert_eq!(
13727            editor.background_highlights_in_range(
13728                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13729                &snapshot,
13730                cx.theme(),
13731            ),
13732            &[(
13733                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13734                Hsla::red(),
13735            )]
13736        );
13737    });
13738}
13739
13740#[gpui::test]
13741async fn test_following(cx: &mut TestAppContext) {
13742    init_test(cx, |_| {});
13743
13744    let fs = FakeFs::new(cx.executor());
13745    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13746
13747    let buffer = project.update(cx, |project, cx| {
13748        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13749        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13750    });
13751    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13752    let follower = cx.update(|cx| {
13753        cx.open_window(
13754            WindowOptions {
13755                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13756                    gpui::Point::new(px(0.), px(0.)),
13757                    gpui::Point::new(px(10.), px(80.)),
13758                ))),
13759                ..Default::default()
13760            },
13761            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13762        )
13763        .unwrap()
13764    });
13765
13766    let is_still_following = Rc::new(RefCell::new(true));
13767    let follower_edit_event_count = Rc::new(RefCell::new(0));
13768    let pending_update = Rc::new(RefCell::new(None));
13769    let leader_entity = leader.root(cx).unwrap();
13770    let follower_entity = follower.root(cx).unwrap();
13771    _ = follower.update(cx, {
13772        let update = pending_update.clone();
13773        let is_still_following = is_still_following.clone();
13774        let follower_edit_event_count = follower_edit_event_count.clone();
13775        |_, window, cx| {
13776            cx.subscribe_in(
13777                &leader_entity,
13778                window,
13779                move |_, leader, event, window, cx| {
13780                    leader.read(cx).add_event_to_update_proto(
13781                        event,
13782                        &mut update.borrow_mut(),
13783                        window,
13784                        cx,
13785                    );
13786                },
13787            )
13788            .detach();
13789
13790            cx.subscribe_in(
13791                &follower_entity,
13792                window,
13793                move |_, _, event: &EditorEvent, _window, _cx| {
13794                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13795                        *is_still_following.borrow_mut() = false;
13796                    }
13797
13798                    if let EditorEvent::BufferEdited = event {
13799                        *follower_edit_event_count.borrow_mut() += 1;
13800                    }
13801                },
13802            )
13803            .detach();
13804        }
13805    });
13806
13807    // Update the selections only
13808    _ = leader.update(cx, |leader, window, cx| {
13809        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13810    });
13811    follower
13812        .update(cx, |follower, window, cx| {
13813            follower.apply_update_proto(
13814                &project,
13815                pending_update.borrow_mut().take().unwrap(),
13816                window,
13817                cx,
13818            )
13819        })
13820        .unwrap()
13821        .await
13822        .unwrap();
13823    _ = follower.update(cx, |follower, _, cx| {
13824        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13825    });
13826    assert!(*is_still_following.borrow());
13827    assert_eq!(*follower_edit_event_count.borrow(), 0);
13828
13829    // Update the scroll position only
13830    _ = leader.update(cx, |leader, window, cx| {
13831        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13832    });
13833    follower
13834        .update(cx, |follower, window, cx| {
13835            follower.apply_update_proto(
13836                &project,
13837                pending_update.borrow_mut().take().unwrap(),
13838                window,
13839                cx,
13840            )
13841        })
13842        .unwrap()
13843        .await
13844        .unwrap();
13845    assert_eq!(
13846        follower
13847            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13848            .unwrap(),
13849        gpui::Point::new(1.5, 3.5)
13850    );
13851    assert!(*is_still_following.borrow());
13852    assert_eq!(*follower_edit_event_count.borrow(), 0);
13853
13854    // Update the selections and scroll position. The follower's scroll position is updated
13855    // via autoscroll, not via the leader's exact scroll position.
13856    _ = leader.update(cx, |leader, window, cx| {
13857        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13858        leader.request_autoscroll(Autoscroll::newest(), cx);
13859        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13860    });
13861    follower
13862        .update(cx, |follower, window, cx| {
13863            follower.apply_update_proto(
13864                &project,
13865                pending_update.borrow_mut().take().unwrap(),
13866                window,
13867                cx,
13868            )
13869        })
13870        .unwrap()
13871        .await
13872        .unwrap();
13873    _ = follower.update(cx, |follower, _, cx| {
13874        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13875        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13876    });
13877    assert!(*is_still_following.borrow());
13878
13879    // Creating a pending selection that precedes another selection
13880    _ = leader.update(cx, |leader, window, cx| {
13881        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13882        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13883    });
13884    follower
13885        .update(cx, |follower, window, cx| {
13886            follower.apply_update_proto(
13887                &project,
13888                pending_update.borrow_mut().take().unwrap(),
13889                window,
13890                cx,
13891            )
13892        })
13893        .unwrap()
13894        .await
13895        .unwrap();
13896    _ = follower.update(cx, |follower, _, cx| {
13897        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13898    });
13899    assert!(*is_still_following.borrow());
13900
13901    // Extend the pending selection so that it surrounds another selection
13902    _ = leader.update(cx, |leader, window, cx| {
13903        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13904    });
13905    follower
13906        .update(cx, |follower, window, cx| {
13907            follower.apply_update_proto(
13908                &project,
13909                pending_update.borrow_mut().take().unwrap(),
13910                window,
13911                cx,
13912            )
13913        })
13914        .unwrap()
13915        .await
13916        .unwrap();
13917    _ = follower.update(cx, |follower, _, cx| {
13918        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13919    });
13920
13921    // Scrolling locally breaks the follow
13922    _ = follower.update(cx, |follower, window, cx| {
13923        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13924        follower.set_scroll_anchor(
13925            ScrollAnchor {
13926                anchor: top_anchor,
13927                offset: gpui::Point::new(0.0, 0.5),
13928            },
13929            window,
13930            cx,
13931        );
13932    });
13933    assert!(!(*is_still_following.borrow()));
13934}
13935
13936#[gpui::test]
13937async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13938    init_test(cx, |_| {});
13939
13940    let fs = FakeFs::new(cx.executor());
13941    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13942    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13943    let pane = workspace
13944        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13945        .unwrap();
13946
13947    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13948
13949    let leader = pane.update_in(cx, |_, window, cx| {
13950        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13951        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13952    });
13953
13954    // Start following the editor when it has no excerpts.
13955    let mut state_message =
13956        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13957    let workspace_entity = workspace.root(cx).unwrap();
13958    let follower_1 = cx
13959        .update_window(*workspace.deref(), |_, window, cx| {
13960            Editor::from_state_proto(
13961                workspace_entity,
13962                ViewId {
13963                    creator: CollaboratorId::PeerId(PeerId::default()),
13964                    id: 0,
13965                },
13966                &mut state_message,
13967                window,
13968                cx,
13969            )
13970        })
13971        .unwrap()
13972        .unwrap()
13973        .await
13974        .unwrap();
13975
13976    let update_message = Rc::new(RefCell::new(None));
13977    follower_1.update_in(cx, {
13978        let update = update_message.clone();
13979        |_, window, cx| {
13980            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13981                leader.read(cx).add_event_to_update_proto(
13982                    event,
13983                    &mut update.borrow_mut(),
13984                    window,
13985                    cx,
13986                );
13987            })
13988            .detach();
13989        }
13990    });
13991
13992    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13993        (
13994            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13995            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13996        )
13997    });
13998
13999    // Insert some excerpts.
14000    leader.update(cx, |leader, cx| {
14001        leader.buffer.update(cx, |multibuffer, cx| {
14002            multibuffer.set_excerpts_for_path(
14003                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14004                buffer_1.clone(),
14005                vec![
14006                    Point::row_range(0..3),
14007                    Point::row_range(1..6),
14008                    Point::row_range(12..15),
14009                ],
14010                0,
14011                cx,
14012            );
14013            multibuffer.set_excerpts_for_path(
14014                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14015                buffer_2.clone(),
14016                vec![Point::row_range(0..6), Point::row_range(8..12)],
14017                0,
14018                cx,
14019            );
14020        });
14021    });
14022
14023    // Apply the update of adding the excerpts.
14024    follower_1
14025        .update_in(cx, |follower, window, cx| {
14026            follower.apply_update_proto(
14027                &project,
14028                update_message.borrow().clone().unwrap(),
14029                window,
14030                cx,
14031            )
14032        })
14033        .await
14034        .unwrap();
14035    assert_eq!(
14036        follower_1.update(cx, |editor, cx| editor.text(cx)),
14037        leader.update(cx, |editor, cx| editor.text(cx))
14038    );
14039    update_message.borrow_mut().take();
14040
14041    // Start following separately after it already has excerpts.
14042    let mut state_message =
14043        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14044    let workspace_entity = workspace.root(cx).unwrap();
14045    let follower_2 = cx
14046        .update_window(*workspace.deref(), |_, window, cx| {
14047            Editor::from_state_proto(
14048                workspace_entity,
14049                ViewId {
14050                    creator: CollaboratorId::PeerId(PeerId::default()),
14051                    id: 0,
14052                },
14053                &mut state_message,
14054                window,
14055                cx,
14056            )
14057        })
14058        .unwrap()
14059        .unwrap()
14060        .await
14061        .unwrap();
14062    assert_eq!(
14063        follower_2.update(cx, |editor, cx| editor.text(cx)),
14064        leader.update(cx, |editor, cx| editor.text(cx))
14065    );
14066
14067    // Remove some excerpts.
14068    leader.update(cx, |leader, cx| {
14069        leader.buffer.update(cx, |multibuffer, cx| {
14070            let excerpt_ids = multibuffer.excerpt_ids();
14071            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14072            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14073        });
14074    });
14075
14076    // Apply the update of removing the excerpts.
14077    follower_1
14078        .update_in(cx, |follower, window, cx| {
14079            follower.apply_update_proto(
14080                &project,
14081                update_message.borrow().clone().unwrap(),
14082                window,
14083                cx,
14084            )
14085        })
14086        .await
14087        .unwrap();
14088    follower_2
14089        .update_in(cx, |follower, window, cx| {
14090            follower.apply_update_proto(
14091                &project,
14092                update_message.borrow().clone().unwrap(),
14093                window,
14094                cx,
14095            )
14096        })
14097        .await
14098        .unwrap();
14099    update_message.borrow_mut().take();
14100    assert_eq!(
14101        follower_1.update(cx, |editor, cx| editor.text(cx)),
14102        leader.update(cx, |editor, cx| editor.text(cx))
14103    );
14104}
14105
14106#[gpui::test]
14107async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14108    init_test(cx, |_| {});
14109
14110    let mut cx = EditorTestContext::new(cx).await;
14111    let lsp_store =
14112        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14113
14114    cx.set_state(indoc! {"
14115        ˇfn func(abc def: i32) -> u32 {
14116        }
14117    "});
14118
14119    cx.update(|_, cx| {
14120        lsp_store.update(cx, |lsp_store, cx| {
14121            lsp_store
14122                .update_diagnostics(
14123                    LanguageServerId(0),
14124                    lsp::PublishDiagnosticsParams {
14125                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14126                        version: None,
14127                        diagnostics: vec![
14128                            lsp::Diagnostic {
14129                                range: lsp::Range::new(
14130                                    lsp::Position::new(0, 11),
14131                                    lsp::Position::new(0, 12),
14132                                ),
14133                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14134                                ..Default::default()
14135                            },
14136                            lsp::Diagnostic {
14137                                range: lsp::Range::new(
14138                                    lsp::Position::new(0, 12),
14139                                    lsp::Position::new(0, 15),
14140                                ),
14141                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14142                                ..Default::default()
14143                            },
14144                            lsp::Diagnostic {
14145                                range: lsp::Range::new(
14146                                    lsp::Position::new(0, 25),
14147                                    lsp::Position::new(0, 28),
14148                                ),
14149                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14150                                ..Default::default()
14151                            },
14152                        ],
14153                    },
14154                    None,
14155                    DiagnosticSourceKind::Pushed,
14156                    &[],
14157                    cx,
14158                )
14159                .unwrap()
14160        });
14161    });
14162
14163    executor.run_until_parked();
14164
14165    cx.update_editor(|editor, window, cx| {
14166        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14167    });
14168
14169    cx.assert_editor_state(indoc! {"
14170        fn func(abc def: i32) -> ˇu32 {
14171        }
14172    "});
14173
14174    cx.update_editor(|editor, window, cx| {
14175        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14176    });
14177
14178    cx.assert_editor_state(indoc! {"
14179        fn func(abc ˇdef: i32) -> u32 {
14180        }
14181    "});
14182
14183    cx.update_editor(|editor, window, cx| {
14184        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14185    });
14186
14187    cx.assert_editor_state(indoc! {"
14188        fn func(abcˇ def: i32) -> u32 {
14189        }
14190    "});
14191
14192    cx.update_editor(|editor, window, cx| {
14193        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14194    });
14195
14196    cx.assert_editor_state(indoc! {"
14197        fn func(abc def: i32) -> ˇu32 {
14198        }
14199    "});
14200}
14201
14202#[gpui::test]
14203async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14204    init_test(cx, |_| {});
14205
14206    let mut cx = EditorTestContext::new(cx).await;
14207
14208    let diff_base = r#"
14209        use some::mod;
14210
14211        const A: u32 = 42;
14212
14213        fn main() {
14214            println!("hello");
14215
14216            println!("world");
14217        }
14218        "#
14219    .unindent();
14220
14221    // Edits are modified, removed, modified, added
14222    cx.set_state(
14223        &r#"
14224        use some::modified;
14225
14226        ˇ
14227        fn main() {
14228            println!("hello there");
14229
14230            println!("around the");
14231            println!("world");
14232        }
14233        "#
14234        .unindent(),
14235    );
14236
14237    cx.set_head_text(&diff_base);
14238    executor.run_until_parked();
14239
14240    cx.update_editor(|editor, window, cx| {
14241        //Wrap around the bottom of the buffer
14242        for _ in 0..3 {
14243            editor.go_to_next_hunk(&GoToHunk, window, cx);
14244        }
14245    });
14246
14247    cx.assert_editor_state(
14248        &r#"
14249        ˇuse some::modified;
14250
14251
14252        fn main() {
14253            println!("hello there");
14254
14255            println!("around the");
14256            println!("world");
14257        }
14258        "#
14259        .unindent(),
14260    );
14261
14262    cx.update_editor(|editor, window, cx| {
14263        //Wrap around the top of the buffer
14264        for _ in 0..2 {
14265            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14266        }
14267    });
14268
14269    cx.assert_editor_state(
14270        &r#"
14271        use some::modified;
14272
14273
14274        fn main() {
14275        ˇ    println!("hello there");
14276
14277            println!("around the");
14278            println!("world");
14279        }
14280        "#
14281        .unindent(),
14282    );
14283
14284    cx.update_editor(|editor, window, cx| {
14285        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14286    });
14287
14288    cx.assert_editor_state(
14289        &r#"
14290        use some::modified;
14291
14292        ˇ
14293        fn main() {
14294            println!("hello there");
14295
14296            println!("around the");
14297            println!("world");
14298        }
14299        "#
14300        .unindent(),
14301    );
14302
14303    cx.update_editor(|editor, window, cx| {
14304        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14305    });
14306
14307    cx.assert_editor_state(
14308        &r#"
14309        ˇuse some::modified;
14310
14311
14312        fn main() {
14313            println!("hello there");
14314
14315            println!("around the");
14316            println!("world");
14317        }
14318        "#
14319        .unindent(),
14320    );
14321
14322    cx.update_editor(|editor, window, cx| {
14323        for _ in 0..2 {
14324            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14325        }
14326    });
14327
14328    cx.assert_editor_state(
14329        &r#"
14330        use some::modified;
14331
14332
14333        fn main() {
14334        ˇ    println!("hello there");
14335
14336            println!("around the");
14337            println!("world");
14338        }
14339        "#
14340        .unindent(),
14341    );
14342
14343    cx.update_editor(|editor, window, cx| {
14344        editor.fold(&Fold, window, cx);
14345    });
14346
14347    cx.update_editor(|editor, window, cx| {
14348        editor.go_to_next_hunk(&GoToHunk, window, cx);
14349    });
14350
14351    cx.assert_editor_state(
14352        &r#"
14353        ˇuse some::modified;
14354
14355
14356        fn main() {
14357            println!("hello there");
14358
14359            println!("around the");
14360            println!("world");
14361        }
14362        "#
14363        .unindent(),
14364    );
14365}
14366
14367#[test]
14368fn test_split_words() {
14369    fn split(text: &str) -> Vec<&str> {
14370        split_words(text).collect()
14371    }
14372
14373    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14374    assert_eq!(split("hello_world"), &["hello_", "world"]);
14375    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14376    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14377    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14378    assert_eq!(split("helloworld"), &["helloworld"]);
14379
14380    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14381}
14382
14383#[gpui::test]
14384async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14385    init_test(cx, |_| {});
14386
14387    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14388    let mut assert = |before, after| {
14389        let _state_context = cx.set_state(before);
14390        cx.run_until_parked();
14391        cx.update_editor(|editor, window, cx| {
14392            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14393        });
14394        cx.run_until_parked();
14395        cx.assert_editor_state(after);
14396    };
14397
14398    // Outside bracket jumps to outside of matching bracket
14399    assert("console.logˇ(var);", "console.log(var)ˇ;");
14400    assert("console.log(var)ˇ;", "console.logˇ(var);");
14401
14402    // Inside bracket jumps to inside of matching bracket
14403    assert("console.log(ˇvar);", "console.log(varˇ);");
14404    assert("console.log(varˇ);", "console.log(ˇvar);");
14405
14406    // When outside a bracket and inside, favor jumping to the inside bracket
14407    assert(
14408        "console.log('foo', [1, 2, 3]ˇ);",
14409        "console.log(ˇ'foo', [1, 2, 3]);",
14410    );
14411    assert(
14412        "console.log(ˇ'foo', [1, 2, 3]);",
14413        "console.log('foo', [1, 2, 3]ˇ);",
14414    );
14415
14416    // Bias forward if two options are equally likely
14417    assert(
14418        "let result = curried_fun()ˇ();",
14419        "let result = curried_fun()()ˇ;",
14420    );
14421
14422    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14423    assert(
14424        indoc! {"
14425            function test() {
14426                console.log('test')ˇ
14427            }"},
14428        indoc! {"
14429            function test() {
14430                console.logˇ('test')
14431            }"},
14432    );
14433}
14434
14435#[gpui::test]
14436async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14437    init_test(cx, |_| {});
14438
14439    let fs = FakeFs::new(cx.executor());
14440    fs.insert_tree(
14441        path!("/a"),
14442        json!({
14443            "main.rs": "fn main() { let a = 5; }",
14444            "other.rs": "// Test file",
14445        }),
14446    )
14447    .await;
14448    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14449
14450    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14451    language_registry.add(Arc::new(Language::new(
14452        LanguageConfig {
14453            name: "Rust".into(),
14454            matcher: LanguageMatcher {
14455                path_suffixes: vec!["rs".to_string()],
14456                ..Default::default()
14457            },
14458            brackets: BracketPairConfig {
14459                pairs: vec![BracketPair {
14460                    start: "{".to_string(),
14461                    end: "}".to_string(),
14462                    close: true,
14463                    surround: true,
14464                    newline: true,
14465                }],
14466                disabled_scopes_by_bracket_ix: Vec::new(),
14467            },
14468            ..Default::default()
14469        },
14470        Some(tree_sitter_rust::LANGUAGE.into()),
14471    )));
14472    let mut fake_servers = language_registry.register_fake_lsp(
14473        "Rust",
14474        FakeLspAdapter {
14475            capabilities: lsp::ServerCapabilities {
14476                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14477                    first_trigger_character: "{".to_string(),
14478                    more_trigger_character: None,
14479                }),
14480                ..Default::default()
14481            },
14482            ..Default::default()
14483        },
14484    );
14485
14486    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14487
14488    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14489
14490    let worktree_id = workspace
14491        .update(cx, |workspace, _, cx| {
14492            workspace.project().update(cx, |project, cx| {
14493                project.worktrees(cx).next().unwrap().read(cx).id()
14494            })
14495        })
14496        .unwrap();
14497
14498    let buffer = project
14499        .update(cx, |project, cx| {
14500            project.open_local_buffer(path!("/a/main.rs"), cx)
14501        })
14502        .await
14503        .unwrap();
14504    let editor_handle = workspace
14505        .update(cx, |workspace, window, cx| {
14506            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14507        })
14508        .unwrap()
14509        .await
14510        .unwrap()
14511        .downcast::<Editor>()
14512        .unwrap();
14513
14514    cx.executor().start_waiting();
14515    let fake_server = fake_servers.next().await.unwrap();
14516
14517    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14518        |params, _| async move {
14519            assert_eq!(
14520                params.text_document_position.text_document.uri,
14521                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14522            );
14523            assert_eq!(
14524                params.text_document_position.position,
14525                lsp::Position::new(0, 21),
14526            );
14527
14528            Ok(Some(vec![lsp::TextEdit {
14529                new_text: "]".to_string(),
14530                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14531            }]))
14532        },
14533    );
14534
14535    editor_handle.update_in(cx, |editor, window, cx| {
14536        window.focus(&editor.focus_handle(cx));
14537        editor.change_selections(None, window, cx, |s| {
14538            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14539        });
14540        editor.handle_input("{", window, cx);
14541    });
14542
14543    cx.executor().run_until_parked();
14544
14545    buffer.update(cx, |buffer, _| {
14546        assert_eq!(
14547            buffer.text(),
14548            "fn main() { let a = {5}; }",
14549            "No extra braces from on type formatting should appear in the buffer"
14550        )
14551    });
14552}
14553
14554#[gpui::test(iterations = 20, seeds(31))]
14555async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14556    init_test(cx, |_| {});
14557
14558    let mut cx = EditorLspTestContext::new_rust(
14559        lsp::ServerCapabilities {
14560            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14561                first_trigger_character: ".".to_string(),
14562                more_trigger_character: None,
14563            }),
14564            ..Default::default()
14565        },
14566        cx,
14567    )
14568    .await;
14569
14570    cx.update_buffer(|buffer, _| {
14571        // This causes autoindent to be async.
14572        buffer.set_sync_parse_timeout(Duration::ZERO)
14573    });
14574
14575    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14576    cx.simulate_keystroke("\n");
14577    cx.run_until_parked();
14578
14579    let buffer_cloned =
14580        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14581    let mut request =
14582        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14583            let buffer_cloned = buffer_cloned.clone();
14584            async move {
14585                buffer_cloned.update(&mut cx, |buffer, _| {
14586                    assert_eq!(
14587                        buffer.text(),
14588                        "fn c() {\n    d()\n        .\n}\n",
14589                        "OnTypeFormatting should triggered after autoindent applied"
14590                    )
14591                })?;
14592
14593                Ok(Some(vec![]))
14594            }
14595        });
14596
14597    cx.simulate_keystroke(".");
14598    cx.run_until_parked();
14599
14600    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14601    assert!(request.next().await.is_some());
14602    request.close();
14603    assert!(request.next().await.is_none());
14604}
14605
14606#[gpui::test]
14607async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14608    init_test(cx, |_| {});
14609
14610    let fs = FakeFs::new(cx.executor());
14611    fs.insert_tree(
14612        path!("/a"),
14613        json!({
14614            "main.rs": "fn main() { let a = 5; }",
14615            "other.rs": "// Test file",
14616        }),
14617    )
14618    .await;
14619
14620    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14621
14622    let server_restarts = Arc::new(AtomicUsize::new(0));
14623    let closure_restarts = Arc::clone(&server_restarts);
14624    let language_server_name = "test language server";
14625    let language_name: LanguageName = "Rust".into();
14626
14627    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14628    language_registry.add(Arc::new(Language::new(
14629        LanguageConfig {
14630            name: language_name.clone(),
14631            matcher: LanguageMatcher {
14632                path_suffixes: vec!["rs".to_string()],
14633                ..Default::default()
14634            },
14635            ..Default::default()
14636        },
14637        Some(tree_sitter_rust::LANGUAGE.into()),
14638    )));
14639    let mut fake_servers = language_registry.register_fake_lsp(
14640        "Rust",
14641        FakeLspAdapter {
14642            name: language_server_name,
14643            initialization_options: Some(json!({
14644                "testOptionValue": true
14645            })),
14646            initializer: Some(Box::new(move |fake_server| {
14647                let task_restarts = Arc::clone(&closure_restarts);
14648                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14649                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14650                    futures::future::ready(Ok(()))
14651                });
14652            })),
14653            ..Default::default()
14654        },
14655    );
14656
14657    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14658    let _buffer = project
14659        .update(cx, |project, cx| {
14660            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14661        })
14662        .await
14663        .unwrap();
14664    let _fake_server = fake_servers.next().await.unwrap();
14665    update_test_language_settings(cx, |language_settings| {
14666        language_settings.languages.insert(
14667            language_name.clone(),
14668            LanguageSettingsContent {
14669                tab_size: NonZeroU32::new(8),
14670                ..Default::default()
14671            },
14672        );
14673    });
14674    cx.executor().run_until_parked();
14675    assert_eq!(
14676        server_restarts.load(atomic::Ordering::Acquire),
14677        0,
14678        "Should not restart LSP server on an unrelated change"
14679    );
14680
14681    update_test_project_settings(cx, |project_settings| {
14682        project_settings.lsp.insert(
14683            "Some other server name".into(),
14684            LspSettings {
14685                binary: None,
14686                settings: None,
14687                initialization_options: Some(json!({
14688                    "some other init value": false
14689                })),
14690                enable_lsp_tasks: false,
14691            },
14692        );
14693    });
14694    cx.executor().run_until_parked();
14695    assert_eq!(
14696        server_restarts.load(atomic::Ordering::Acquire),
14697        0,
14698        "Should not restart LSP server on an unrelated LSP settings change"
14699    );
14700
14701    update_test_project_settings(cx, |project_settings| {
14702        project_settings.lsp.insert(
14703            language_server_name.into(),
14704            LspSettings {
14705                binary: None,
14706                settings: None,
14707                initialization_options: Some(json!({
14708                    "anotherInitValue": false
14709                })),
14710                enable_lsp_tasks: false,
14711            },
14712        );
14713    });
14714    cx.executor().run_until_parked();
14715    assert_eq!(
14716        server_restarts.load(atomic::Ordering::Acquire),
14717        1,
14718        "Should restart LSP server on a related LSP settings change"
14719    );
14720
14721    update_test_project_settings(cx, |project_settings| {
14722        project_settings.lsp.insert(
14723            language_server_name.into(),
14724            LspSettings {
14725                binary: None,
14726                settings: None,
14727                initialization_options: Some(json!({
14728                    "anotherInitValue": false
14729                })),
14730                enable_lsp_tasks: false,
14731            },
14732        );
14733    });
14734    cx.executor().run_until_parked();
14735    assert_eq!(
14736        server_restarts.load(atomic::Ordering::Acquire),
14737        1,
14738        "Should not restart LSP server on a related LSP settings change that is the same"
14739    );
14740
14741    update_test_project_settings(cx, |project_settings| {
14742        project_settings.lsp.insert(
14743            language_server_name.into(),
14744            LspSettings {
14745                binary: None,
14746                settings: None,
14747                initialization_options: None,
14748                enable_lsp_tasks: false,
14749            },
14750        );
14751    });
14752    cx.executor().run_until_parked();
14753    assert_eq!(
14754        server_restarts.load(atomic::Ordering::Acquire),
14755        2,
14756        "Should restart LSP server on another related LSP settings change"
14757    );
14758}
14759
14760#[gpui::test]
14761async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14762    init_test(cx, |_| {});
14763
14764    let mut cx = EditorLspTestContext::new_rust(
14765        lsp::ServerCapabilities {
14766            completion_provider: Some(lsp::CompletionOptions {
14767                trigger_characters: Some(vec![".".to_string()]),
14768                resolve_provider: Some(true),
14769                ..Default::default()
14770            }),
14771            ..Default::default()
14772        },
14773        cx,
14774    )
14775    .await;
14776
14777    cx.set_state("fn main() { let a = 2ˇ; }");
14778    cx.simulate_keystroke(".");
14779    let completion_item = lsp::CompletionItem {
14780        label: "some".into(),
14781        kind: Some(lsp::CompletionItemKind::SNIPPET),
14782        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14783        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14784            kind: lsp::MarkupKind::Markdown,
14785            value: "```rust\nSome(2)\n```".to_string(),
14786        })),
14787        deprecated: Some(false),
14788        sort_text: Some("fffffff2".to_string()),
14789        filter_text: Some("some".to_string()),
14790        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14791        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14792            range: lsp::Range {
14793                start: lsp::Position {
14794                    line: 0,
14795                    character: 22,
14796                },
14797                end: lsp::Position {
14798                    line: 0,
14799                    character: 22,
14800                },
14801            },
14802            new_text: "Some(2)".to_string(),
14803        })),
14804        additional_text_edits: Some(vec![lsp::TextEdit {
14805            range: lsp::Range {
14806                start: lsp::Position {
14807                    line: 0,
14808                    character: 20,
14809                },
14810                end: lsp::Position {
14811                    line: 0,
14812                    character: 22,
14813                },
14814            },
14815            new_text: "".to_string(),
14816        }]),
14817        ..Default::default()
14818    };
14819
14820    let closure_completion_item = completion_item.clone();
14821    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14822        let task_completion_item = closure_completion_item.clone();
14823        async move {
14824            Ok(Some(lsp::CompletionResponse::Array(vec![
14825                task_completion_item,
14826            ])))
14827        }
14828    });
14829
14830    request.next().await;
14831
14832    cx.condition(|editor, _| editor.context_menu_visible())
14833        .await;
14834    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14835        editor
14836            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14837            .unwrap()
14838    });
14839    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14840
14841    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14842        let task_completion_item = completion_item.clone();
14843        async move { Ok(task_completion_item) }
14844    })
14845    .next()
14846    .await
14847    .unwrap();
14848    apply_additional_edits.await.unwrap();
14849    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14850}
14851
14852#[gpui::test]
14853async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14854    init_test(cx, |_| {});
14855
14856    let mut cx = EditorLspTestContext::new_rust(
14857        lsp::ServerCapabilities {
14858            completion_provider: Some(lsp::CompletionOptions {
14859                trigger_characters: Some(vec![".".to_string()]),
14860                resolve_provider: Some(true),
14861                ..Default::default()
14862            }),
14863            ..Default::default()
14864        },
14865        cx,
14866    )
14867    .await;
14868
14869    cx.set_state("fn main() { let a = 2ˇ; }");
14870    cx.simulate_keystroke(".");
14871
14872    let item1 = lsp::CompletionItem {
14873        label: "method id()".to_string(),
14874        filter_text: Some("id".to_string()),
14875        detail: None,
14876        documentation: None,
14877        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14878            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14879            new_text: ".id".to_string(),
14880        })),
14881        ..lsp::CompletionItem::default()
14882    };
14883
14884    let item2 = lsp::CompletionItem {
14885        label: "other".to_string(),
14886        filter_text: Some("other".to_string()),
14887        detail: None,
14888        documentation: None,
14889        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14890            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14891            new_text: ".other".to_string(),
14892        })),
14893        ..lsp::CompletionItem::default()
14894    };
14895
14896    let item1 = item1.clone();
14897    cx.set_request_handler::<lsp::request::Completion, _, _>({
14898        let item1 = item1.clone();
14899        move |_, _, _| {
14900            let item1 = item1.clone();
14901            let item2 = item2.clone();
14902            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14903        }
14904    })
14905    .next()
14906    .await;
14907
14908    cx.condition(|editor, _| editor.context_menu_visible())
14909        .await;
14910    cx.update_editor(|editor, _, _| {
14911        let context_menu = editor.context_menu.borrow_mut();
14912        let context_menu = context_menu
14913            .as_ref()
14914            .expect("Should have the context menu deployed");
14915        match context_menu {
14916            CodeContextMenu::Completions(completions_menu) => {
14917                let completions = completions_menu.completions.borrow_mut();
14918                assert_eq!(
14919                    completions
14920                        .iter()
14921                        .map(|completion| &completion.label.text)
14922                        .collect::<Vec<_>>(),
14923                    vec!["method id()", "other"]
14924                )
14925            }
14926            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14927        }
14928    });
14929
14930    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14931        let item1 = item1.clone();
14932        move |_, item_to_resolve, _| {
14933            let item1 = item1.clone();
14934            async move {
14935                if item1 == item_to_resolve {
14936                    Ok(lsp::CompletionItem {
14937                        label: "method id()".to_string(),
14938                        filter_text: Some("id".to_string()),
14939                        detail: Some("Now resolved!".to_string()),
14940                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14941                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14942                            range: lsp::Range::new(
14943                                lsp::Position::new(0, 22),
14944                                lsp::Position::new(0, 22),
14945                            ),
14946                            new_text: ".id".to_string(),
14947                        })),
14948                        ..lsp::CompletionItem::default()
14949                    })
14950                } else {
14951                    Ok(item_to_resolve)
14952                }
14953            }
14954        }
14955    })
14956    .next()
14957    .await
14958    .unwrap();
14959    cx.run_until_parked();
14960
14961    cx.update_editor(|editor, window, cx| {
14962        editor.context_menu_next(&Default::default(), window, cx);
14963    });
14964
14965    cx.update_editor(|editor, _, _| {
14966        let context_menu = editor.context_menu.borrow_mut();
14967        let context_menu = context_menu
14968            .as_ref()
14969            .expect("Should have the context menu deployed");
14970        match context_menu {
14971            CodeContextMenu::Completions(completions_menu) => {
14972                let completions = completions_menu.completions.borrow_mut();
14973                assert_eq!(
14974                    completions
14975                        .iter()
14976                        .map(|completion| &completion.label.text)
14977                        .collect::<Vec<_>>(),
14978                    vec!["method id() Now resolved!", "other"],
14979                    "Should update first completion label, but not second as the filter text did not match."
14980                );
14981            }
14982            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14983        }
14984    });
14985}
14986
14987#[gpui::test]
14988async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14989    init_test(cx, |_| {});
14990    let mut cx = EditorLspTestContext::new_rust(
14991        lsp::ServerCapabilities {
14992            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14993            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14994            completion_provider: Some(lsp::CompletionOptions {
14995                resolve_provider: Some(true),
14996                ..Default::default()
14997            }),
14998            ..Default::default()
14999        },
15000        cx,
15001    )
15002    .await;
15003    cx.set_state(indoc! {"
15004        struct TestStruct {
15005            field: i32
15006        }
15007
15008        fn mainˇ() {
15009            let unused_var = 42;
15010            let test_struct = TestStruct { field: 42 };
15011        }
15012    "});
15013    let symbol_range = cx.lsp_range(indoc! {"
15014        struct TestStruct {
15015            field: i32
15016        }
15017
15018        «fn main»() {
15019            let unused_var = 42;
15020            let test_struct = TestStruct { field: 42 };
15021        }
15022    "});
15023    let mut hover_requests =
15024        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15025            Ok(Some(lsp::Hover {
15026                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15027                    kind: lsp::MarkupKind::Markdown,
15028                    value: "Function documentation".to_string(),
15029                }),
15030                range: Some(symbol_range),
15031            }))
15032        });
15033
15034    // Case 1: Test that code action menu hide hover popover
15035    cx.dispatch_action(Hover);
15036    hover_requests.next().await;
15037    cx.condition(|editor, _| editor.hover_state.visible()).await;
15038    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15039        move |_, _, _| async move {
15040            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15041                lsp::CodeAction {
15042                    title: "Remove unused variable".to_string(),
15043                    kind: Some(CodeActionKind::QUICKFIX),
15044                    edit: Some(lsp::WorkspaceEdit {
15045                        changes: Some(
15046                            [(
15047                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15048                                vec![lsp::TextEdit {
15049                                    range: lsp::Range::new(
15050                                        lsp::Position::new(5, 4),
15051                                        lsp::Position::new(5, 27),
15052                                    ),
15053                                    new_text: "".to_string(),
15054                                }],
15055                            )]
15056                            .into_iter()
15057                            .collect(),
15058                        ),
15059                        ..Default::default()
15060                    }),
15061                    ..Default::default()
15062                },
15063            )]))
15064        },
15065    );
15066    cx.update_editor(|editor, window, cx| {
15067        editor.toggle_code_actions(
15068            &ToggleCodeActions {
15069                deployed_from: None,
15070                quick_launch: false,
15071            },
15072            window,
15073            cx,
15074        );
15075    });
15076    code_action_requests.next().await;
15077    cx.run_until_parked();
15078    cx.condition(|editor, _| editor.context_menu_visible())
15079        .await;
15080    cx.update_editor(|editor, _, _| {
15081        assert!(
15082            !editor.hover_state.visible(),
15083            "Hover popover should be hidden when code action menu is shown"
15084        );
15085        // Hide code actions
15086        editor.context_menu.take();
15087    });
15088
15089    // Case 2: Test that code completions hide hover popover
15090    cx.dispatch_action(Hover);
15091    hover_requests.next().await;
15092    cx.condition(|editor, _| editor.hover_state.visible()).await;
15093    let counter = Arc::new(AtomicUsize::new(0));
15094    let mut completion_requests =
15095        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15096            let counter = counter.clone();
15097            async move {
15098                counter.fetch_add(1, atomic::Ordering::Release);
15099                Ok(Some(lsp::CompletionResponse::Array(vec![
15100                    lsp::CompletionItem {
15101                        label: "main".into(),
15102                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15103                        detail: Some("() -> ()".to_string()),
15104                        ..Default::default()
15105                    },
15106                    lsp::CompletionItem {
15107                        label: "TestStruct".into(),
15108                        kind: Some(lsp::CompletionItemKind::STRUCT),
15109                        detail: Some("struct TestStruct".to_string()),
15110                        ..Default::default()
15111                    },
15112                ])))
15113            }
15114        });
15115    cx.update_editor(|editor, window, cx| {
15116        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15117    });
15118    completion_requests.next().await;
15119    cx.condition(|editor, _| editor.context_menu_visible())
15120        .await;
15121    cx.update_editor(|editor, _, _| {
15122        assert!(
15123            !editor.hover_state.visible(),
15124            "Hover popover should be hidden when completion menu is shown"
15125        );
15126    });
15127}
15128
15129#[gpui::test]
15130async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15131    init_test(cx, |_| {});
15132
15133    let mut cx = EditorLspTestContext::new_rust(
15134        lsp::ServerCapabilities {
15135            completion_provider: Some(lsp::CompletionOptions {
15136                trigger_characters: Some(vec![".".to_string()]),
15137                resolve_provider: Some(true),
15138                ..Default::default()
15139            }),
15140            ..Default::default()
15141        },
15142        cx,
15143    )
15144    .await;
15145
15146    cx.set_state("fn main() { let a = 2ˇ; }");
15147    cx.simulate_keystroke(".");
15148
15149    let unresolved_item_1 = lsp::CompletionItem {
15150        label: "id".to_string(),
15151        filter_text: Some("id".to_string()),
15152        detail: None,
15153        documentation: None,
15154        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15155            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15156            new_text: ".id".to_string(),
15157        })),
15158        ..lsp::CompletionItem::default()
15159    };
15160    let resolved_item_1 = lsp::CompletionItem {
15161        additional_text_edits: Some(vec![lsp::TextEdit {
15162            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15163            new_text: "!!".to_string(),
15164        }]),
15165        ..unresolved_item_1.clone()
15166    };
15167    let unresolved_item_2 = lsp::CompletionItem {
15168        label: "other".to_string(),
15169        filter_text: Some("other".to_string()),
15170        detail: None,
15171        documentation: None,
15172        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15173            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15174            new_text: ".other".to_string(),
15175        })),
15176        ..lsp::CompletionItem::default()
15177    };
15178    let resolved_item_2 = lsp::CompletionItem {
15179        additional_text_edits: Some(vec![lsp::TextEdit {
15180            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15181            new_text: "??".to_string(),
15182        }]),
15183        ..unresolved_item_2.clone()
15184    };
15185
15186    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15187    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15188    cx.lsp
15189        .server
15190        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15191            let unresolved_item_1 = unresolved_item_1.clone();
15192            let resolved_item_1 = resolved_item_1.clone();
15193            let unresolved_item_2 = unresolved_item_2.clone();
15194            let resolved_item_2 = resolved_item_2.clone();
15195            let resolve_requests_1 = resolve_requests_1.clone();
15196            let resolve_requests_2 = resolve_requests_2.clone();
15197            move |unresolved_request, _| {
15198                let unresolved_item_1 = unresolved_item_1.clone();
15199                let resolved_item_1 = resolved_item_1.clone();
15200                let unresolved_item_2 = unresolved_item_2.clone();
15201                let resolved_item_2 = resolved_item_2.clone();
15202                let resolve_requests_1 = resolve_requests_1.clone();
15203                let resolve_requests_2 = resolve_requests_2.clone();
15204                async move {
15205                    if unresolved_request == unresolved_item_1 {
15206                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15207                        Ok(resolved_item_1.clone())
15208                    } else if unresolved_request == unresolved_item_2 {
15209                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15210                        Ok(resolved_item_2.clone())
15211                    } else {
15212                        panic!("Unexpected completion item {unresolved_request:?}")
15213                    }
15214                }
15215            }
15216        })
15217        .detach();
15218
15219    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15220        let unresolved_item_1 = unresolved_item_1.clone();
15221        let unresolved_item_2 = unresolved_item_2.clone();
15222        async move {
15223            Ok(Some(lsp::CompletionResponse::Array(vec![
15224                unresolved_item_1,
15225                unresolved_item_2,
15226            ])))
15227        }
15228    })
15229    .next()
15230    .await;
15231
15232    cx.condition(|editor, _| editor.context_menu_visible())
15233        .await;
15234    cx.update_editor(|editor, _, _| {
15235        let context_menu = editor.context_menu.borrow_mut();
15236        let context_menu = context_menu
15237            .as_ref()
15238            .expect("Should have the context menu deployed");
15239        match context_menu {
15240            CodeContextMenu::Completions(completions_menu) => {
15241                let completions = completions_menu.completions.borrow_mut();
15242                assert_eq!(
15243                    completions
15244                        .iter()
15245                        .map(|completion| &completion.label.text)
15246                        .collect::<Vec<_>>(),
15247                    vec!["id", "other"]
15248                )
15249            }
15250            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15251        }
15252    });
15253    cx.run_until_parked();
15254
15255    cx.update_editor(|editor, window, cx| {
15256        editor.context_menu_next(&ContextMenuNext, window, cx);
15257    });
15258    cx.run_until_parked();
15259    cx.update_editor(|editor, window, cx| {
15260        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15261    });
15262    cx.run_until_parked();
15263    cx.update_editor(|editor, window, cx| {
15264        editor.context_menu_next(&ContextMenuNext, window, cx);
15265    });
15266    cx.run_until_parked();
15267    cx.update_editor(|editor, window, cx| {
15268        editor
15269            .compose_completion(&ComposeCompletion::default(), window, cx)
15270            .expect("No task returned")
15271    })
15272    .await
15273    .expect("Completion failed");
15274    cx.run_until_parked();
15275
15276    cx.update_editor(|editor, _, cx| {
15277        assert_eq!(
15278            resolve_requests_1.load(atomic::Ordering::Acquire),
15279            1,
15280            "Should always resolve once despite multiple selections"
15281        );
15282        assert_eq!(
15283            resolve_requests_2.load(atomic::Ordering::Acquire),
15284            1,
15285            "Should always resolve once after multiple selections and applying the completion"
15286        );
15287        assert_eq!(
15288            editor.text(cx),
15289            "fn main() { let a = ??.other; }",
15290            "Should use resolved data when applying the completion"
15291        );
15292    });
15293}
15294
15295#[gpui::test]
15296async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15297    init_test(cx, |_| {});
15298
15299    let item_0 = lsp::CompletionItem {
15300        label: "abs".into(),
15301        insert_text: Some("abs".into()),
15302        data: Some(json!({ "very": "special"})),
15303        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15304        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15305            lsp::InsertReplaceEdit {
15306                new_text: "abs".to_string(),
15307                insert: lsp::Range::default(),
15308                replace: lsp::Range::default(),
15309            },
15310        )),
15311        ..lsp::CompletionItem::default()
15312    };
15313    let items = iter::once(item_0.clone())
15314        .chain((11..51).map(|i| lsp::CompletionItem {
15315            label: format!("item_{}", i),
15316            insert_text: Some(format!("item_{}", i)),
15317            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15318            ..lsp::CompletionItem::default()
15319        }))
15320        .collect::<Vec<_>>();
15321
15322    let default_commit_characters = vec!["?".to_string()];
15323    let default_data = json!({ "default": "data"});
15324    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15325    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15326    let default_edit_range = lsp::Range {
15327        start: lsp::Position {
15328            line: 0,
15329            character: 5,
15330        },
15331        end: lsp::Position {
15332            line: 0,
15333            character: 5,
15334        },
15335    };
15336
15337    let mut cx = EditorLspTestContext::new_rust(
15338        lsp::ServerCapabilities {
15339            completion_provider: Some(lsp::CompletionOptions {
15340                trigger_characters: Some(vec![".".to_string()]),
15341                resolve_provider: Some(true),
15342                ..Default::default()
15343            }),
15344            ..Default::default()
15345        },
15346        cx,
15347    )
15348    .await;
15349
15350    cx.set_state("fn main() { let a = 2ˇ; }");
15351    cx.simulate_keystroke(".");
15352
15353    let completion_data = default_data.clone();
15354    let completion_characters = default_commit_characters.clone();
15355    let completion_items = items.clone();
15356    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15357        let default_data = completion_data.clone();
15358        let default_commit_characters = completion_characters.clone();
15359        let items = completion_items.clone();
15360        async move {
15361            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15362                items,
15363                item_defaults: Some(lsp::CompletionListItemDefaults {
15364                    data: Some(default_data.clone()),
15365                    commit_characters: Some(default_commit_characters.clone()),
15366                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15367                        default_edit_range,
15368                    )),
15369                    insert_text_format: Some(default_insert_text_format),
15370                    insert_text_mode: Some(default_insert_text_mode),
15371                }),
15372                ..lsp::CompletionList::default()
15373            })))
15374        }
15375    })
15376    .next()
15377    .await;
15378
15379    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15380    cx.lsp
15381        .server
15382        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15383            let closure_resolved_items = resolved_items.clone();
15384            move |item_to_resolve, _| {
15385                let closure_resolved_items = closure_resolved_items.clone();
15386                async move {
15387                    closure_resolved_items.lock().push(item_to_resolve.clone());
15388                    Ok(item_to_resolve)
15389                }
15390            }
15391        })
15392        .detach();
15393
15394    cx.condition(|editor, _| editor.context_menu_visible())
15395        .await;
15396    cx.run_until_parked();
15397    cx.update_editor(|editor, _, _| {
15398        let menu = editor.context_menu.borrow_mut();
15399        match menu.as_ref().expect("should have the completions menu") {
15400            CodeContextMenu::Completions(completions_menu) => {
15401                assert_eq!(
15402                    completions_menu
15403                        .entries
15404                        .borrow()
15405                        .iter()
15406                        .map(|mat| mat.string.clone())
15407                        .collect::<Vec<String>>(),
15408                    items
15409                        .iter()
15410                        .map(|completion| completion.label.clone())
15411                        .collect::<Vec<String>>()
15412                );
15413            }
15414            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15415        }
15416    });
15417    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15418    // with 4 from the end.
15419    assert_eq!(
15420        *resolved_items.lock(),
15421        [&items[0..16], &items[items.len() - 4..items.len()]]
15422            .concat()
15423            .iter()
15424            .cloned()
15425            .map(|mut item| {
15426                if item.data.is_none() {
15427                    item.data = Some(default_data.clone());
15428                }
15429                item
15430            })
15431            .collect::<Vec<lsp::CompletionItem>>(),
15432        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15433    );
15434    resolved_items.lock().clear();
15435
15436    cx.update_editor(|editor, window, cx| {
15437        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15438    });
15439    cx.run_until_parked();
15440    // Completions that have already been resolved are skipped.
15441    assert_eq!(
15442        *resolved_items.lock(),
15443        items[items.len() - 16..items.len() - 4]
15444            .iter()
15445            .cloned()
15446            .map(|mut item| {
15447                if item.data.is_none() {
15448                    item.data = Some(default_data.clone());
15449                }
15450                item
15451            })
15452            .collect::<Vec<lsp::CompletionItem>>()
15453    );
15454    resolved_items.lock().clear();
15455}
15456
15457#[gpui::test]
15458async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15459    init_test(cx, |_| {});
15460
15461    let mut cx = EditorLspTestContext::new(
15462        Language::new(
15463            LanguageConfig {
15464                matcher: LanguageMatcher {
15465                    path_suffixes: vec!["jsx".into()],
15466                    ..Default::default()
15467                },
15468                overrides: [(
15469                    "element".into(),
15470                    LanguageConfigOverride {
15471                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15472                        ..Default::default()
15473                    },
15474                )]
15475                .into_iter()
15476                .collect(),
15477                ..Default::default()
15478            },
15479            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15480        )
15481        .with_override_query("(jsx_self_closing_element) @element")
15482        .unwrap(),
15483        lsp::ServerCapabilities {
15484            completion_provider: Some(lsp::CompletionOptions {
15485                trigger_characters: Some(vec![":".to_string()]),
15486                ..Default::default()
15487            }),
15488            ..Default::default()
15489        },
15490        cx,
15491    )
15492    .await;
15493
15494    cx.lsp
15495        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15496            Ok(Some(lsp::CompletionResponse::Array(vec![
15497                lsp::CompletionItem {
15498                    label: "bg-blue".into(),
15499                    ..Default::default()
15500                },
15501                lsp::CompletionItem {
15502                    label: "bg-red".into(),
15503                    ..Default::default()
15504                },
15505                lsp::CompletionItem {
15506                    label: "bg-yellow".into(),
15507                    ..Default::default()
15508                },
15509            ])))
15510        });
15511
15512    cx.set_state(r#"<p class="bgˇ" />"#);
15513
15514    // Trigger completion when typing a dash, because the dash is an extra
15515    // word character in the 'element' scope, which contains the cursor.
15516    cx.simulate_keystroke("-");
15517    cx.executor().run_until_parked();
15518    cx.update_editor(|editor, _, _| {
15519        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15520        {
15521            assert_eq!(
15522                completion_menu_entries(&menu),
15523                &["bg-blue", "bg-red", "bg-yellow"]
15524            );
15525        } else {
15526            panic!("expected completion menu to be open");
15527        }
15528    });
15529
15530    cx.simulate_keystroke("l");
15531    cx.executor().run_until_parked();
15532    cx.update_editor(|editor, _, _| {
15533        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15534        {
15535            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15536        } else {
15537            panic!("expected completion menu to be open");
15538        }
15539    });
15540
15541    // When filtering completions, consider the character after the '-' to
15542    // be the start of a subword.
15543    cx.set_state(r#"<p class="yelˇ" />"#);
15544    cx.simulate_keystroke("l");
15545    cx.executor().run_until_parked();
15546    cx.update_editor(|editor, _, _| {
15547        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15548        {
15549            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15550        } else {
15551            panic!("expected completion menu to be open");
15552        }
15553    });
15554}
15555
15556fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15557    let entries = menu.entries.borrow();
15558    entries.iter().map(|mat| mat.string.clone()).collect()
15559}
15560
15561#[gpui::test]
15562async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15563    init_test(cx, |settings| {
15564        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15565            FormatterList(vec![Formatter::Prettier].into()),
15566        ))
15567    });
15568
15569    let fs = FakeFs::new(cx.executor());
15570    fs.insert_file(path!("/file.ts"), Default::default()).await;
15571
15572    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15573    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15574
15575    language_registry.add(Arc::new(Language::new(
15576        LanguageConfig {
15577            name: "TypeScript".into(),
15578            matcher: LanguageMatcher {
15579                path_suffixes: vec!["ts".to_string()],
15580                ..Default::default()
15581            },
15582            ..Default::default()
15583        },
15584        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15585    )));
15586    update_test_language_settings(cx, |settings| {
15587        settings.defaults.prettier = Some(PrettierSettings {
15588            allowed: true,
15589            ..PrettierSettings::default()
15590        });
15591    });
15592
15593    let test_plugin = "test_plugin";
15594    let _ = language_registry.register_fake_lsp(
15595        "TypeScript",
15596        FakeLspAdapter {
15597            prettier_plugins: vec![test_plugin],
15598            ..Default::default()
15599        },
15600    );
15601
15602    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15603    let buffer = project
15604        .update(cx, |project, cx| {
15605            project.open_local_buffer(path!("/file.ts"), cx)
15606        })
15607        .await
15608        .unwrap();
15609
15610    let buffer_text = "one\ntwo\nthree\n";
15611    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15612    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15613    editor.update_in(cx, |editor, window, cx| {
15614        editor.set_text(buffer_text, window, cx)
15615    });
15616
15617    editor
15618        .update_in(cx, |editor, window, cx| {
15619            editor.perform_format(
15620                project.clone(),
15621                FormatTrigger::Manual,
15622                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15623                window,
15624                cx,
15625            )
15626        })
15627        .unwrap()
15628        .await;
15629    assert_eq!(
15630        editor.update(cx, |editor, cx| editor.text(cx)),
15631        buffer_text.to_string() + prettier_format_suffix,
15632        "Test prettier formatting was not applied to the original buffer text",
15633    );
15634
15635    update_test_language_settings(cx, |settings| {
15636        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15637    });
15638    let format = editor.update_in(cx, |editor, window, cx| {
15639        editor.perform_format(
15640            project.clone(),
15641            FormatTrigger::Manual,
15642            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15643            window,
15644            cx,
15645        )
15646    });
15647    format.await.unwrap();
15648    assert_eq!(
15649        editor.update(cx, |editor, cx| editor.text(cx)),
15650        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15651        "Autoformatting (via test prettier) was not applied to the original buffer text",
15652    );
15653}
15654
15655#[gpui::test]
15656async fn test_addition_reverts(cx: &mut TestAppContext) {
15657    init_test(cx, |_| {});
15658    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15659    let base_text = indoc! {r#"
15660        struct Row;
15661        struct Row1;
15662        struct Row2;
15663
15664        struct Row4;
15665        struct Row5;
15666        struct Row6;
15667
15668        struct Row8;
15669        struct Row9;
15670        struct Row10;"#};
15671
15672    // When addition hunks are not adjacent to carets, no hunk revert is performed
15673    assert_hunk_revert(
15674        indoc! {r#"struct Row;
15675                   struct Row1;
15676                   struct Row1.1;
15677                   struct Row1.2;
15678                   struct Row2;ˇ
15679
15680                   struct Row4;
15681                   struct Row5;
15682                   struct Row6;
15683
15684                   struct Row8;
15685                   ˇstruct Row9;
15686                   struct Row9.1;
15687                   struct Row9.2;
15688                   struct Row9.3;
15689                   struct Row10;"#},
15690        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15691        indoc! {r#"struct Row;
15692                   struct Row1;
15693                   struct Row1.1;
15694                   struct Row1.2;
15695                   struct Row2;ˇ
15696
15697                   struct Row4;
15698                   struct Row5;
15699                   struct Row6;
15700
15701                   struct Row8;
15702                   ˇstruct Row9;
15703                   struct Row9.1;
15704                   struct Row9.2;
15705                   struct Row9.3;
15706                   struct Row10;"#},
15707        base_text,
15708        &mut cx,
15709    );
15710    // Same for selections
15711    assert_hunk_revert(
15712        indoc! {r#"struct Row;
15713                   struct Row1;
15714                   struct Row2;
15715                   struct Row2.1;
15716                   struct Row2.2;
15717                   «ˇ
15718                   struct Row4;
15719                   struct» Row5;
15720                   «struct Row6;
15721                   ˇ»
15722                   struct Row9.1;
15723                   struct Row9.2;
15724                   struct Row9.3;
15725                   struct Row8;
15726                   struct Row9;
15727                   struct Row10;"#},
15728        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15729        indoc! {r#"struct Row;
15730                   struct Row1;
15731                   struct Row2;
15732                   struct Row2.1;
15733                   struct Row2.2;
15734                   «ˇ
15735                   struct Row4;
15736                   struct» Row5;
15737                   «struct Row6;
15738                   ˇ»
15739                   struct Row9.1;
15740                   struct Row9.2;
15741                   struct Row9.3;
15742                   struct Row8;
15743                   struct Row9;
15744                   struct Row10;"#},
15745        base_text,
15746        &mut cx,
15747    );
15748
15749    // When carets and selections intersect the addition hunks, those are reverted.
15750    // Adjacent carets got merged.
15751    assert_hunk_revert(
15752        indoc! {r#"struct Row;
15753                   ˇ// something on the top
15754                   struct Row1;
15755                   struct Row2;
15756                   struct Roˇw3.1;
15757                   struct Row2.2;
15758                   struct Row2.3;ˇ
15759
15760                   struct Row4;
15761                   struct ˇRow5.1;
15762                   struct Row5.2;
15763                   struct «Rowˇ»5.3;
15764                   struct Row5;
15765                   struct Row6;
15766                   ˇ
15767                   struct Row9.1;
15768                   struct «Rowˇ»9.2;
15769                   struct «ˇRow»9.3;
15770                   struct Row8;
15771                   struct Row9;
15772                   «ˇ// something on bottom»
15773                   struct Row10;"#},
15774        vec![
15775            DiffHunkStatusKind::Added,
15776            DiffHunkStatusKind::Added,
15777            DiffHunkStatusKind::Added,
15778            DiffHunkStatusKind::Added,
15779            DiffHunkStatusKind::Added,
15780        ],
15781        indoc! {r#"struct Row;
15782                   ˇstruct Row1;
15783                   struct Row2;
15784                   ˇ
15785                   struct Row4;
15786                   ˇstruct Row5;
15787                   struct Row6;
15788                   ˇ
15789                   ˇstruct Row8;
15790                   struct Row9;
15791                   ˇstruct Row10;"#},
15792        base_text,
15793        &mut cx,
15794    );
15795}
15796
15797#[gpui::test]
15798async fn test_modification_reverts(cx: &mut TestAppContext) {
15799    init_test(cx, |_| {});
15800    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15801    let base_text = indoc! {r#"
15802        struct Row;
15803        struct Row1;
15804        struct Row2;
15805
15806        struct Row4;
15807        struct Row5;
15808        struct Row6;
15809
15810        struct Row8;
15811        struct Row9;
15812        struct Row10;"#};
15813
15814    // Modification hunks behave the same as the addition ones.
15815    assert_hunk_revert(
15816        indoc! {r#"struct Row;
15817                   struct Row1;
15818                   struct Row33;
15819                   ˇ
15820                   struct Row4;
15821                   struct Row5;
15822                   struct Row6;
15823                   ˇ
15824                   struct Row99;
15825                   struct Row9;
15826                   struct Row10;"#},
15827        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15828        indoc! {r#"struct Row;
15829                   struct Row1;
15830                   struct Row33;
15831                   ˇ
15832                   struct Row4;
15833                   struct Row5;
15834                   struct Row6;
15835                   ˇ
15836                   struct Row99;
15837                   struct Row9;
15838                   struct Row10;"#},
15839        base_text,
15840        &mut cx,
15841    );
15842    assert_hunk_revert(
15843        indoc! {r#"struct Row;
15844                   struct Row1;
15845                   struct Row33;
15846                   «ˇ
15847                   struct Row4;
15848                   struct» Row5;
15849                   «struct Row6;
15850                   ˇ»
15851                   struct Row99;
15852                   struct Row9;
15853                   struct Row10;"#},
15854        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15855        indoc! {r#"struct Row;
15856                   struct Row1;
15857                   struct Row33;
15858                   «ˇ
15859                   struct Row4;
15860                   struct» Row5;
15861                   «struct Row6;
15862                   ˇ»
15863                   struct Row99;
15864                   struct Row9;
15865                   struct Row10;"#},
15866        base_text,
15867        &mut cx,
15868    );
15869
15870    assert_hunk_revert(
15871        indoc! {r#"ˇstruct Row1.1;
15872                   struct Row1;
15873                   «ˇstr»uct Row22;
15874
15875                   struct ˇRow44;
15876                   struct Row5;
15877                   struct «Rˇ»ow66;ˇ
15878
15879                   «struˇ»ct Row88;
15880                   struct Row9;
15881                   struct Row1011;ˇ"#},
15882        vec![
15883            DiffHunkStatusKind::Modified,
15884            DiffHunkStatusKind::Modified,
15885            DiffHunkStatusKind::Modified,
15886            DiffHunkStatusKind::Modified,
15887            DiffHunkStatusKind::Modified,
15888            DiffHunkStatusKind::Modified,
15889        ],
15890        indoc! {r#"struct Row;
15891                   ˇstruct Row1;
15892                   struct Row2;
15893                   ˇ
15894                   struct Row4;
15895                   ˇstruct Row5;
15896                   struct Row6;
15897                   ˇ
15898                   struct Row8;
15899                   ˇstruct Row9;
15900                   struct Row10;ˇ"#},
15901        base_text,
15902        &mut cx,
15903    );
15904}
15905
15906#[gpui::test]
15907async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15908    init_test(cx, |_| {});
15909    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15910    let base_text = indoc! {r#"
15911        one
15912
15913        two
15914        three
15915        "#};
15916
15917    cx.set_head_text(base_text);
15918    cx.set_state("\nˇ\n");
15919    cx.executor().run_until_parked();
15920    cx.update_editor(|editor, _window, cx| {
15921        editor.expand_selected_diff_hunks(cx);
15922    });
15923    cx.executor().run_until_parked();
15924    cx.update_editor(|editor, window, cx| {
15925        editor.backspace(&Default::default(), window, cx);
15926    });
15927    cx.run_until_parked();
15928    cx.assert_state_with_diff(
15929        indoc! {r#"
15930
15931        - two
15932        - threeˇ
15933        +
15934        "#}
15935        .to_string(),
15936    );
15937}
15938
15939#[gpui::test]
15940async fn test_deletion_reverts(cx: &mut TestAppContext) {
15941    init_test(cx, |_| {});
15942    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15943    let base_text = indoc! {r#"struct Row;
15944struct Row1;
15945struct Row2;
15946
15947struct Row4;
15948struct Row5;
15949struct Row6;
15950
15951struct Row8;
15952struct Row9;
15953struct Row10;"#};
15954
15955    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15956    assert_hunk_revert(
15957        indoc! {r#"struct Row;
15958                   struct Row2;
15959
15960                   ˇstruct Row4;
15961                   struct Row5;
15962                   struct Row6;
15963                   ˇ
15964                   struct Row8;
15965                   struct Row10;"#},
15966        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15967        indoc! {r#"struct Row;
15968                   struct Row2;
15969
15970                   ˇstruct Row4;
15971                   struct Row5;
15972                   struct Row6;
15973                   ˇ
15974                   struct Row8;
15975                   struct Row10;"#},
15976        base_text,
15977        &mut cx,
15978    );
15979    assert_hunk_revert(
15980        indoc! {r#"struct Row;
15981                   struct Row2;
15982
15983                   «ˇstruct Row4;
15984                   struct» Row5;
15985                   «struct Row6;
15986                   ˇ»
15987                   struct Row8;
15988                   struct Row10;"#},
15989        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15990        indoc! {r#"struct Row;
15991                   struct Row2;
15992
15993                   «ˇstruct Row4;
15994                   struct» Row5;
15995                   «struct Row6;
15996                   ˇ»
15997                   struct Row8;
15998                   struct Row10;"#},
15999        base_text,
16000        &mut cx,
16001    );
16002
16003    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16004    assert_hunk_revert(
16005        indoc! {r#"struct Row;
16006                   ˇstruct Row2;
16007
16008                   struct Row4;
16009                   struct Row5;
16010                   struct Row6;
16011
16012                   struct Row8;ˇ
16013                   struct Row10;"#},
16014        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16015        indoc! {r#"struct Row;
16016                   struct Row1;
16017                   ˇstruct Row2;
16018
16019                   struct Row4;
16020                   struct Row5;
16021                   struct Row6;
16022
16023                   struct Row8;ˇ
16024                   struct Row9;
16025                   struct Row10;"#},
16026        base_text,
16027        &mut cx,
16028    );
16029    assert_hunk_revert(
16030        indoc! {r#"struct Row;
16031                   struct Row2«ˇ;
16032                   struct Row4;
16033                   struct» Row5;
16034                   «struct Row6;
16035
16036                   struct Row8;ˇ»
16037                   struct Row10;"#},
16038        vec![
16039            DiffHunkStatusKind::Deleted,
16040            DiffHunkStatusKind::Deleted,
16041            DiffHunkStatusKind::Deleted,
16042        ],
16043        indoc! {r#"struct Row;
16044                   struct Row1;
16045                   struct Row2«ˇ;
16046
16047                   struct Row4;
16048                   struct» Row5;
16049                   «struct Row6;
16050
16051                   struct Row8;ˇ»
16052                   struct Row9;
16053                   struct Row10;"#},
16054        base_text,
16055        &mut cx,
16056    );
16057}
16058
16059#[gpui::test]
16060async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16061    init_test(cx, |_| {});
16062
16063    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16064    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16065    let base_text_3 =
16066        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16067
16068    let text_1 = edit_first_char_of_every_line(base_text_1);
16069    let text_2 = edit_first_char_of_every_line(base_text_2);
16070    let text_3 = edit_first_char_of_every_line(base_text_3);
16071
16072    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16073    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16074    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16075
16076    let multibuffer = cx.new(|cx| {
16077        let mut multibuffer = MultiBuffer::new(ReadWrite);
16078        multibuffer.push_excerpts(
16079            buffer_1.clone(),
16080            [
16081                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16082                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16083                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16084            ],
16085            cx,
16086        );
16087        multibuffer.push_excerpts(
16088            buffer_2.clone(),
16089            [
16090                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16091                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16092                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16093            ],
16094            cx,
16095        );
16096        multibuffer.push_excerpts(
16097            buffer_3.clone(),
16098            [
16099                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16100                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16101                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16102            ],
16103            cx,
16104        );
16105        multibuffer
16106    });
16107
16108    let fs = FakeFs::new(cx.executor());
16109    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16110    let (editor, cx) = cx
16111        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16112    editor.update_in(cx, |editor, _window, cx| {
16113        for (buffer, diff_base) in [
16114            (buffer_1.clone(), base_text_1),
16115            (buffer_2.clone(), base_text_2),
16116            (buffer_3.clone(), base_text_3),
16117        ] {
16118            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16119            editor
16120                .buffer
16121                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16122        }
16123    });
16124    cx.executor().run_until_parked();
16125
16126    editor.update_in(cx, |editor, window, cx| {
16127        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}");
16128        editor.select_all(&SelectAll, window, cx);
16129        editor.git_restore(&Default::default(), window, cx);
16130    });
16131    cx.executor().run_until_parked();
16132
16133    // When all ranges are selected, all buffer hunks are reverted.
16134    editor.update(cx, |editor, cx| {
16135        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");
16136    });
16137    buffer_1.update(cx, |buffer, _| {
16138        assert_eq!(buffer.text(), base_text_1);
16139    });
16140    buffer_2.update(cx, |buffer, _| {
16141        assert_eq!(buffer.text(), base_text_2);
16142    });
16143    buffer_3.update(cx, |buffer, _| {
16144        assert_eq!(buffer.text(), base_text_3);
16145    });
16146
16147    editor.update_in(cx, |editor, window, cx| {
16148        editor.undo(&Default::default(), window, cx);
16149    });
16150
16151    editor.update_in(cx, |editor, window, cx| {
16152        editor.change_selections(None, window, cx, |s| {
16153            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16154        });
16155        editor.git_restore(&Default::default(), window, cx);
16156    });
16157
16158    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16159    // but not affect buffer_2 and its related excerpts.
16160    editor.update(cx, |editor, cx| {
16161        assert_eq!(
16162            editor.text(cx),
16163            "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}"
16164        );
16165    });
16166    buffer_1.update(cx, |buffer, _| {
16167        assert_eq!(buffer.text(), base_text_1);
16168    });
16169    buffer_2.update(cx, |buffer, _| {
16170        assert_eq!(
16171            buffer.text(),
16172            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16173        );
16174    });
16175    buffer_3.update(cx, |buffer, _| {
16176        assert_eq!(
16177            buffer.text(),
16178            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16179        );
16180    });
16181
16182    fn edit_first_char_of_every_line(text: &str) -> String {
16183        text.split('\n')
16184            .map(|line| format!("X{}", &line[1..]))
16185            .collect::<Vec<_>>()
16186            .join("\n")
16187    }
16188}
16189
16190#[gpui::test]
16191async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16192    init_test(cx, |_| {});
16193
16194    let cols = 4;
16195    let rows = 10;
16196    let sample_text_1 = sample_text(rows, cols, 'a');
16197    assert_eq!(
16198        sample_text_1,
16199        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16200    );
16201    let sample_text_2 = sample_text(rows, cols, 'l');
16202    assert_eq!(
16203        sample_text_2,
16204        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16205    );
16206    let sample_text_3 = sample_text(rows, cols, 'v');
16207    assert_eq!(
16208        sample_text_3,
16209        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16210    );
16211
16212    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16213    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16214    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16215
16216    let multi_buffer = cx.new(|cx| {
16217        let mut multibuffer = MultiBuffer::new(ReadWrite);
16218        multibuffer.push_excerpts(
16219            buffer_1.clone(),
16220            [
16221                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16222                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16223                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16224            ],
16225            cx,
16226        );
16227        multibuffer.push_excerpts(
16228            buffer_2.clone(),
16229            [
16230                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16231                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16232                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16233            ],
16234            cx,
16235        );
16236        multibuffer.push_excerpts(
16237            buffer_3.clone(),
16238            [
16239                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16240                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16241                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16242            ],
16243            cx,
16244        );
16245        multibuffer
16246    });
16247
16248    let fs = FakeFs::new(cx.executor());
16249    fs.insert_tree(
16250        "/a",
16251        json!({
16252            "main.rs": sample_text_1,
16253            "other.rs": sample_text_2,
16254            "lib.rs": sample_text_3,
16255        }),
16256    )
16257    .await;
16258    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16259    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16260    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16261    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16262        Editor::new(
16263            EditorMode::full(),
16264            multi_buffer,
16265            Some(project.clone()),
16266            window,
16267            cx,
16268        )
16269    });
16270    let multibuffer_item_id = workspace
16271        .update(cx, |workspace, window, cx| {
16272            assert!(
16273                workspace.active_item(cx).is_none(),
16274                "active item should be None before the first item is added"
16275            );
16276            workspace.add_item_to_active_pane(
16277                Box::new(multi_buffer_editor.clone()),
16278                None,
16279                true,
16280                window,
16281                cx,
16282            );
16283            let active_item = workspace
16284                .active_item(cx)
16285                .expect("should have an active item after adding the multi buffer");
16286            assert!(
16287                !active_item.is_singleton(cx),
16288                "A multi buffer was expected to active after adding"
16289            );
16290            active_item.item_id()
16291        })
16292        .unwrap();
16293    cx.executor().run_until_parked();
16294
16295    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16296        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16297            s.select_ranges(Some(1..2))
16298        });
16299        editor.open_excerpts(&OpenExcerpts, window, cx);
16300    });
16301    cx.executor().run_until_parked();
16302    let first_item_id = workspace
16303        .update(cx, |workspace, window, cx| {
16304            let active_item = workspace
16305                .active_item(cx)
16306                .expect("should have an active item after navigating into the 1st buffer");
16307            let first_item_id = active_item.item_id();
16308            assert_ne!(
16309                first_item_id, multibuffer_item_id,
16310                "Should navigate into the 1st buffer and activate it"
16311            );
16312            assert!(
16313                active_item.is_singleton(cx),
16314                "New active item should be a singleton buffer"
16315            );
16316            assert_eq!(
16317                active_item
16318                    .act_as::<Editor>(cx)
16319                    .expect("should have navigated into an editor for the 1st buffer")
16320                    .read(cx)
16321                    .text(cx),
16322                sample_text_1
16323            );
16324
16325            workspace
16326                .go_back(workspace.active_pane().downgrade(), window, cx)
16327                .detach_and_log_err(cx);
16328
16329            first_item_id
16330        })
16331        .unwrap();
16332    cx.executor().run_until_parked();
16333    workspace
16334        .update(cx, |workspace, _, cx| {
16335            let active_item = workspace
16336                .active_item(cx)
16337                .expect("should have an active item after navigating back");
16338            assert_eq!(
16339                active_item.item_id(),
16340                multibuffer_item_id,
16341                "Should navigate back to the multi buffer"
16342            );
16343            assert!(!active_item.is_singleton(cx));
16344        })
16345        .unwrap();
16346
16347    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16348        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16349            s.select_ranges(Some(39..40))
16350        });
16351        editor.open_excerpts(&OpenExcerpts, window, cx);
16352    });
16353    cx.executor().run_until_parked();
16354    let second_item_id = workspace
16355        .update(cx, |workspace, window, cx| {
16356            let active_item = workspace
16357                .active_item(cx)
16358                .expect("should have an active item after navigating into the 2nd buffer");
16359            let second_item_id = active_item.item_id();
16360            assert_ne!(
16361                second_item_id, multibuffer_item_id,
16362                "Should navigate away from the multibuffer"
16363            );
16364            assert_ne!(
16365                second_item_id, first_item_id,
16366                "Should navigate into the 2nd buffer and activate it"
16367            );
16368            assert!(
16369                active_item.is_singleton(cx),
16370                "New active item should be a singleton buffer"
16371            );
16372            assert_eq!(
16373                active_item
16374                    .act_as::<Editor>(cx)
16375                    .expect("should have navigated into an editor")
16376                    .read(cx)
16377                    .text(cx),
16378                sample_text_2
16379            );
16380
16381            workspace
16382                .go_back(workspace.active_pane().downgrade(), window, cx)
16383                .detach_and_log_err(cx);
16384
16385            second_item_id
16386        })
16387        .unwrap();
16388    cx.executor().run_until_parked();
16389    workspace
16390        .update(cx, |workspace, _, cx| {
16391            let active_item = workspace
16392                .active_item(cx)
16393                .expect("should have an active item after navigating back from the 2nd buffer");
16394            assert_eq!(
16395                active_item.item_id(),
16396                multibuffer_item_id,
16397                "Should navigate back from the 2nd buffer to the multi buffer"
16398            );
16399            assert!(!active_item.is_singleton(cx));
16400        })
16401        .unwrap();
16402
16403    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16404        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16405            s.select_ranges(Some(70..70))
16406        });
16407        editor.open_excerpts(&OpenExcerpts, window, cx);
16408    });
16409    cx.executor().run_until_parked();
16410    workspace
16411        .update(cx, |workspace, window, cx| {
16412            let active_item = workspace
16413                .active_item(cx)
16414                .expect("should have an active item after navigating into the 3rd buffer");
16415            let third_item_id = active_item.item_id();
16416            assert_ne!(
16417                third_item_id, multibuffer_item_id,
16418                "Should navigate into the 3rd buffer and activate it"
16419            );
16420            assert_ne!(third_item_id, first_item_id);
16421            assert_ne!(third_item_id, second_item_id);
16422            assert!(
16423                active_item.is_singleton(cx),
16424                "New active item should be a singleton buffer"
16425            );
16426            assert_eq!(
16427                active_item
16428                    .act_as::<Editor>(cx)
16429                    .expect("should have navigated into an editor")
16430                    .read(cx)
16431                    .text(cx),
16432                sample_text_3
16433            );
16434
16435            workspace
16436                .go_back(workspace.active_pane().downgrade(), window, cx)
16437                .detach_and_log_err(cx);
16438        })
16439        .unwrap();
16440    cx.executor().run_until_parked();
16441    workspace
16442        .update(cx, |workspace, _, cx| {
16443            let active_item = workspace
16444                .active_item(cx)
16445                .expect("should have an active item after navigating back from the 3rd buffer");
16446            assert_eq!(
16447                active_item.item_id(),
16448                multibuffer_item_id,
16449                "Should navigate back from the 3rd buffer to the multi buffer"
16450            );
16451            assert!(!active_item.is_singleton(cx));
16452        })
16453        .unwrap();
16454}
16455
16456#[gpui::test]
16457async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16458    init_test(cx, |_| {});
16459
16460    let mut cx = EditorTestContext::new(cx).await;
16461
16462    let diff_base = r#"
16463        use some::mod;
16464
16465        const A: u32 = 42;
16466
16467        fn main() {
16468            println!("hello");
16469
16470            println!("world");
16471        }
16472        "#
16473    .unindent();
16474
16475    cx.set_state(
16476        &r#"
16477        use some::modified;
16478
16479        ˇ
16480        fn main() {
16481            println!("hello there");
16482
16483            println!("around the");
16484            println!("world");
16485        }
16486        "#
16487        .unindent(),
16488    );
16489
16490    cx.set_head_text(&diff_base);
16491    executor.run_until_parked();
16492
16493    cx.update_editor(|editor, window, cx| {
16494        editor.go_to_next_hunk(&GoToHunk, window, cx);
16495        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16496    });
16497    executor.run_until_parked();
16498    cx.assert_state_with_diff(
16499        r#"
16500          use some::modified;
16501
16502
16503          fn main() {
16504        -     println!("hello");
16505        + ˇ    println!("hello there");
16506
16507              println!("around the");
16508              println!("world");
16509          }
16510        "#
16511        .unindent(),
16512    );
16513
16514    cx.update_editor(|editor, window, cx| {
16515        for _ in 0..2 {
16516            editor.go_to_next_hunk(&GoToHunk, window, cx);
16517            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16518        }
16519    });
16520    executor.run_until_parked();
16521    cx.assert_state_with_diff(
16522        r#"
16523        - use some::mod;
16524        + ˇuse some::modified;
16525
16526
16527          fn main() {
16528        -     println!("hello");
16529        +     println!("hello there");
16530
16531        +     println!("around the");
16532              println!("world");
16533          }
16534        "#
16535        .unindent(),
16536    );
16537
16538    cx.update_editor(|editor, window, cx| {
16539        editor.go_to_next_hunk(&GoToHunk, window, cx);
16540        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16541    });
16542    executor.run_until_parked();
16543    cx.assert_state_with_diff(
16544        r#"
16545        - use some::mod;
16546        + use some::modified;
16547
16548        - const A: u32 = 42;
16549          ˇ
16550          fn main() {
16551        -     println!("hello");
16552        +     println!("hello there");
16553
16554        +     println!("around the");
16555              println!("world");
16556          }
16557        "#
16558        .unindent(),
16559    );
16560
16561    cx.update_editor(|editor, window, cx| {
16562        editor.cancel(&Cancel, window, cx);
16563    });
16564
16565    cx.assert_state_with_diff(
16566        r#"
16567          use some::modified;
16568
16569          ˇ
16570          fn main() {
16571              println!("hello there");
16572
16573              println!("around the");
16574              println!("world");
16575          }
16576        "#
16577        .unindent(),
16578    );
16579}
16580
16581#[gpui::test]
16582async fn test_diff_base_change_with_expanded_diff_hunks(
16583    executor: BackgroundExecutor,
16584    cx: &mut TestAppContext,
16585) {
16586    init_test(cx, |_| {});
16587
16588    let mut cx = EditorTestContext::new(cx).await;
16589
16590    let diff_base = r#"
16591        use some::mod1;
16592        use some::mod2;
16593
16594        const A: u32 = 42;
16595        const B: u32 = 42;
16596        const C: u32 = 42;
16597
16598        fn main() {
16599            println!("hello");
16600
16601            println!("world");
16602        }
16603        "#
16604    .unindent();
16605
16606    cx.set_state(
16607        &r#"
16608        use some::mod2;
16609
16610        const A: u32 = 42;
16611        const C: u32 = 42;
16612
16613        fn main(ˇ) {
16614            //println!("hello");
16615
16616            println!("world");
16617            //
16618            //
16619        }
16620        "#
16621        .unindent(),
16622    );
16623
16624    cx.set_head_text(&diff_base);
16625    executor.run_until_parked();
16626
16627    cx.update_editor(|editor, window, cx| {
16628        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16629    });
16630    executor.run_until_parked();
16631    cx.assert_state_with_diff(
16632        r#"
16633        - use some::mod1;
16634          use some::mod2;
16635
16636          const A: u32 = 42;
16637        - const B: u32 = 42;
16638          const C: u32 = 42;
16639
16640          fn main(ˇ) {
16641        -     println!("hello");
16642        +     //println!("hello");
16643
16644              println!("world");
16645        +     //
16646        +     //
16647          }
16648        "#
16649        .unindent(),
16650    );
16651
16652    cx.set_head_text("new diff base!");
16653    executor.run_until_parked();
16654    cx.assert_state_with_diff(
16655        r#"
16656        - new diff base!
16657        + use some::mod2;
16658        +
16659        + const A: u32 = 42;
16660        + const C: u32 = 42;
16661        +
16662        + fn main(ˇ) {
16663        +     //println!("hello");
16664        +
16665        +     println!("world");
16666        +     //
16667        +     //
16668        + }
16669        "#
16670        .unindent(),
16671    );
16672}
16673
16674#[gpui::test]
16675async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16676    init_test(cx, |_| {});
16677
16678    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16679    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16680    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16681    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16682    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16683    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16684
16685    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16686    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16687    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16688
16689    let multi_buffer = cx.new(|cx| {
16690        let mut multibuffer = MultiBuffer::new(ReadWrite);
16691        multibuffer.push_excerpts(
16692            buffer_1.clone(),
16693            [
16694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16697            ],
16698            cx,
16699        );
16700        multibuffer.push_excerpts(
16701            buffer_2.clone(),
16702            [
16703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16706            ],
16707            cx,
16708        );
16709        multibuffer.push_excerpts(
16710            buffer_3.clone(),
16711            [
16712                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16713                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16715            ],
16716            cx,
16717        );
16718        multibuffer
16719    });
16720
16721    let editor =
16722        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16723    editor
16724        .update(cx, |editor, _window, cx| {
16725            for (buffer, diff_base) in [
16726                (buffer_1.clone(), file_1_old),
16727                (buffer_2.clone(), file_2_old),
16728                (buffer_3.clone(), file_3_old),
16729            ] {
16730                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16731                editor
16732                    .buffer
16733                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16734            }
16735        })
16736        .unwrap();
16737
16738    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16739    cx.run_until_parked();
16740
16741    cx.assert_editor_state(
16742        &"
16743            ˇaaa
16744            ccc
16745            ddd
16746
16747            ggg
16748            hhh
16749
16750
16751            lll
16752            mmm
16753            NNN
16754
16755            qqq
16756            rrr
16757
16758            uuu
16759            111
16760            222
16761            333
16762
16763            666
16764            777
16765
16766            000
16767            !!!"
16768        .unindent(),
16769    );
16770
16771    cx.update_editor(|editor, window, cx| {
16772        editor.select_all(&SelectAll, window, cx);
16773        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16774    });
16775    cx.executor().run_until_parked();
16776
16777    cx.assert_state_with_diff(
16778        "
16779            «aaa
16780          - bbb
16781            ccc
16782            ddd
16783
16784            ggg
16785            hhh
16786
16787
16788            lll
16789            mmm
16790          - nnn
16791          + NNN
16792
16793            qqq
16794            rrr
16795
16796            uuu
16797            111
16798            222
16799            333
16800
16801          + 666
16802            777
16803
16804            000
16805            !!!ˇ»"
16806            .unindent(),
16807    );
16808}
16809
16810#[gpui::test]
16811async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16812    init_test(cx, |_| {});
16813
16814    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16815    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16816
16817    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16818    let multi_buffer = cx.new(|cx| {
16819        let mut multibuffer = MultiBuffer::new(ReadWrite);
16820        multibuffer.push_excerpts(
16821            buffer.clone(),
16822            [
16823                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16824                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16825                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16826            ],
16827            cx,
16828        );
16829        multibuffer
16830    });
16831
16832    let editor =
16833        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16834    editor
16835        .update(cx, |editor, _window, cx| {
16836            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16837            editor
16838                .buffer
16839                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16840        })
16841        .unwrap();
16842
16843    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16844    cx.run_until_parked();
16845
16846    cx.update_editor(|editor, window, cx| {
16847        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16848    });
16849    cx.executor().run_until_parked();
16850
16851    // When the start of a hunk coincides with the start of its excerpt,
16852    // the hunk is expanded. When the start of a a hunk is earlier than
16853    // the start of its excerpt, the hunk is not expanded.
16854    cx.assert_state_with_diff(
16855        "
16856            ˇaaa
16857          - bbb
16858          + BBB
16859
16860          - ddd
16861          - eee
16862          + DDD
16863          + EEE
16864            fff
16865
16866            iii
16867        "
16868        .unindent(),
16869    );
16870}
16871
16872#[gpui::test]
16873async fn test_edits_around_expanded_insertion_hunks(
16874    executor: BackgroundExecutor,
16875    cx: &mut TestAppContext,
16876) {
16877    init_test(cx, |_| {});
16878
16879    let mut cx = EditorTestContext::new(cx).await;
16880
16881    let diff_base = r#"
16882        use some::mod1;
16883        use some::mod2;
16884
16885        const A: u32 = 42;
16886
16887        fn main() {
16888            println!("hello");
16889
16890            println!("world");
16891        }
16892        "#
16893    .unindent();
16894    executor.run_until_parked();
16895    cx.set_state(
16896        &r#"
16897        use some::mod1;
16898        use some::mod2;
16899
16900        const A: u32 = 42;
16901        const B: u32 = 42;
16902        const C: u32 = 42;
16903        ˇ
16904
16905        fn main() {
16906            println!("hello");
16907
16908            println!("world");
16909        }
16910        "#
16911        .unindent(),
16912    );
16913
16914    cx.set_head_text(&diff_base);
16915    executor.run_until_parked();
16916
16917    cx.update_editor(|editor, window, cx| {
16918        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16919    });
16920    executor.run_until_parked();
16921
16922    cx.assert_state_with_diff(
16923        r#"
16924        use some::mod1;
16925        use some::mod2;
16926
16927        const A: u32 = 42;
16928      + const B: u32 = 42;
16929      + const C: u32 = 42;
16930      + ˇ
16931
16932        fn main() {
16933            println!("hello");
16934
16935            println!("world");
16936        }
16937      "#
16938        .unindent(),
16939    );
16940
16941    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16942    executor.run_until_parked();
16943
16944    cx.assert_state_with_diff(
16945        r#"
16946        use some::mod1;
16947        use some::mod2;
16948
16949        const A: u32 = 42;
16950      + const B: u32 = 42;
16951      + const C: u32 = 42;
16952      + const D: u32 = 42;
16953      + ˇ
16954
16955        fn main() {
16956            println!("hello");
16957
16958            println!("world");
16959        }
16960      "#
16961        .unindent(),
16962    );
16963
16964    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16965    executor.run_until_parked();
16966
16967    cx.assert_state_with_diff(
16968        r#"
16969        use some::mod1;
16970        use some::mod2;
16971
16972        const A: u32 = 42;
16973      + const B: u32 = 42;
16974      + const C: u32 = 42;
16975      + const D: u32 = 42;
16976      + const E: u32 = 42;
16977      + ˇ
16978
16979        fn main() {
16980            println!("hello");
16981
16982            println!("world");
16983        }
16984      "#
16985        .unindent(),
16986    );
16987
16988    cx.update_editor(|editor, window, cx| {
16989        editor.delete_line(&DeleteLine, window, cx);
16990    });
16991    executor.run_until_parked();
16992
16993    cx.assert_state_with_diff(
16994        r#"
16995        use some::mod1;
16996        use some::mod2;
16997
16998        const A: u32 = 42;
16999      + const B: u32 = 42;
17000      + const C: u32 = 42;
17001      + const D: u32 = 42;
17002      + const E: u32 = 42;
17003        ˇ
17004        fn main() {
17005            println!("hello");
17006
17007            println!("world");
17008        }
17009      "#
17010        .unindent(),
17011    );
17012
17013    cx.update_editor(|editor, window, cx| {
17014        editor.move_up(&MoveUp, window, cx);
17015        editor.delete_line(&DeleteLine, window, cx);
17016        editor.move_up(&MoveUp, window, cx);
17017        editor.delete_line(&DeleteLine, window, cx);
17018        editor.move_up(&MoveUp, window, cx);
17019        editor.delete_line(&DeleteLine, window, cx);
17020    });
17021    executor.run_until_parked();
17022    cx.assert_state_with_diff(
17023        r#"
17024        use some::mod1;
17025        use some::mod2;
17026
17027        const A: u32 = 42;
17028      + const B: u32 = 42;
17029        ˇ
17030        fn main() {
17031            println!("hello");
17032
17033            println!("world");
17034        }
17035      "#
17036        .unindent(),
17037    );
17038
17039    cx.update_editor(|editor, window, cx| {
17040        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17041        editor.delete_line(&DeleteLine, window, cx);
17042    });
17043    executor.run_until_parked();
17044    cx.assert_state_with_diff(
17045        r#"
17046        ˇ
17047        fn main() {
17048            println!("hello");
17049
17050            println!("world");
17051        }
17052      "#
17053        .unindent(),
17054    );
17055}
17056
17057#[gpui::test]
17058async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17059    init_test(cx, |_| {});
17060
17061    let mut cx = EditorTestContext::new(cx).await;
17062    cx.set_head_text(indoc! { "
17063        one
17064        two
17065        three
17066        four
17067        five
17068        "
17069    });
17070    cx.set_state(indoc! { "
17071        one
17072        ˇthree
17073        five
17074    "});
17075    cx.run_until_parked();
17076    cx.update_editor(|editor, window, cx| {
17077        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17078    });
17079    cx.assert_state_with_diff(
17080        indoc! { "
17081        one
17082      - two
17083        ˇthree
17084      - four
17085        five
17086    "}
17087        .to_string(),
17088    );
17089    cx.update_editor(|editor, window, cx| {
17090        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17091    });
17092
17093    cx.assert_state_with_diff(
17094        indoc! { "
17095        one
17096        ˇthree
17097        five
17098    "}
17099        .to_string(),
17100    );
17101
17102    cx.set_state(indoc! { "
17103        one
17104        ˇTWO
17105        three
17106        four
17107        five
17108    "});
17109    cx.run_until_parked();
17110    cx.update_editor(|editor, window, cx| {
17111        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17112    });
17113
17114    cx.assert_state_with_diff(
17115        indoc! { "
17116            one
17117          - two
17118          + ˇTWO
17119            three
17120            four
17121            five
17122        "}
17123        .to_string(),
17124    );
17125    cx.update_editor(|editor, window, cx| {
17126        editor.move_up(&Default::default(), window, cx);
17127        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17128    });
17129    cx.assert_state_with_diff(
17130        indoc! { "
17131            one
17132            ˇTWO
17133            three
17134            four
17135            five
17136        "}
17137        .to_string(),
17138    );
17139}
17140
17141#[gpui::test]
17142async fn test_edits_around_expanded_deletion_hunks(
17143    executor: BackgroundExecutor,
17144    cx: &mut TestAppContext,
17145) {
17146    init_test(cx, |_| {});
17147
17148    let mut cx = EditorTestContext::new(cx).await;
17149
17150    let diff_base = r#"
17151        use some::mod1;
17152        use some::mod2;
17153
17154        const A: u32 = 42;
17155        const B: u32 = 42;
17156        const C: u32 = 42;
17157
17158
17159        fn main() {
17160            println!("hello");
17161
17162            println!("world");
17163        }
17164    "#
17165    .unindent();
17166    executor.run_until_parked();
17167    cx.set_state(
17168        &r#"
17169        use some::mod1;
17170        use some::mod2;
17171
17172        ˇconst B: u32 = 42;
17173        const C: u32 = 42;
17174
17175
17176        fn main() {
17177            println!("hello");
17178
17179            println!("world");
17180        }
17181        "#
17182        .unindent(),
17183    );
17184
17185    cx.set_head_text(&diff_base);
17186    executor.run_until_parked();
17187
17188    cx.update_editor(|editor, window, cx| {
17189        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17190    });
17191    executor.run_until_parked();
17192
17193    cx.assert_state_with_diff(
17194        r#"
17195        use some::mod1;
17196        use some::mod2;
17197
17198      - const A: u32 = 42;
17199        ˇconst B: u32 = 42;
17200        const C: u32 = 42;
17201
17202
17203        fn main() {
17204            println!("hello");
17205
17206            println!("world");
17207        }
17208      "#
17209        .unindent(),
17210    );
17211
17212    cx.update_editor(|editor, window, cx| {
17213        editor.delete_line(&DeleteLine, window, cx);
17214    });
17215    executor.run_until_parked();
17216    cx.assert_state_with_diff(
17217        r#"
17218        use some::mod1;
17219        use some::mod2;
17220
17221      - const A: u32 = 42;
17222      - const B: u32 = 42;
17223        ˇconst C: u32 = 42;
17224
17225
17226        fn main() {
17227            println!("hello");
17228
17229            println!("world");
17230        }
17231      "#
17232        .unindent(),
17233    );
17234
17235    cx.update_editor(|editor, window, cx| {
17236        editor.delete_line(&DeleteLine, window, cx);
17237    });
17238    executor.run_until_parked();
17239    cx.assert_state_with_diff(
17240        r#"
17241        use some::mod1;
17242        use some::mod2;
17243
17244      - const A: u32 = 42;
17245      - const B: u32 = 42;
17246      - const C: u32 = 42;
17247        ˇ
17248
17249        fn main() {
17250            println!("hello");
17251
17252            println!("world");
17253        }
17254      "#
17255        .unindent(),
17256    );
17257
17258    cx.update_editor(|editor, window, cx| {
17259        editor.handle_input("replacement", window, cx);
17260    });
17261    executor.run_until_parked();
17262    cx.assert_state_with_diff(
17263        r#"
17264        use some::mod1;
17265        use some::mod2;
17266
17267      - const A: u32 = 42;
17268      - const B: u32 = 42;
17269      - const C: u32 = 42;
17270      -
17271      + replacementˇ
17272
17273        fn main() {
17274            println!("hello");
17275
17276            println!("world");
17277        }
17278      "#
17279        .unindent(),
17280    );
17281}
17282
17283#[gpui::test]
17284async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17285    init_test(cx, |_| {});
17286
17287    let mut cx = EditorTestContext::new(cx).await;
17288
17289    let base_text = r#"
17290        one
17291        two
17292        three
17293        four
17294        five
17295    "#
17296    .unindent();
17297    executor.run_until_parked();
17298    cx.set_state(
17299        &r#"
17300        one
17301        two
17302        fˇour
17303        five
17304        "#
17305        .unindent(),
17306    );
17307
17308    cx.set_head_text(&base_text);
17309    executor.run_until_parked();
17310
17311    cx.update_editor(|editor, window, cx| {
17312        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17313    });
17314    executor.run_until_parked();
17315
17316    cx.assert_state_with_diff(
17317        r#"
17318          one
17319          two
17320        - three
17321          fˇour
17322          five
17323        "#
17324        .unindent(),
17325    );
17326
17327    cx.update_editor(|editor, window, cx| {
17328        editor.backspace(&Backspace, window, cx);
17329        editor.backspace(&Backspace, window, cx);
17330    });
17331    executor.run_until_parked();
17332    cx.assert_state_with_diff(
17333        r#"
17334          one
17335          two
17336        - threeˇ
17337        - four
17338        + our
17339          five
17340        "#
17341        .unindent(),
17342    );
17343}
17344
17345#[gpui::test]
17346async fn test_edit_after_expanded_modification_hunk(
17347    executor: BackgroundExecutor,
17348    cx: &mut TestAppContext,
17349) {
17350    init_test(cx, |_| {});
17351
17352    let mut cx = EditorTestContext::new(cx).await;
17353
17354    let diff_base = r#"
17355        use some::mod1;
17356        use some::mod2;
17357
17358        const A: u32 = 42;
17359        const B: u32 = 42;
17360        const C: u32 = 42;
17361        const D: u32 = 42;
17362
17363
17364        fn main() {
17365            println!("hello");
17366
17367            println!("world");
17368        }"#
17369    .unindent();
17370
17371    cx.set_state(
17372        &r#"
17373        use some::mod1;
17374        use some::mod2;
17375
17376        const A: u32 = 42;
17377        const B: u32 = 42;
17378        const C: u32 = 43ˇ
17379        const D: u32 = 42;
17380
17381
17382        fn main() {
17383            println!("hello");
17384
17385            println!("world");
17386        }"#
17387        .unindent(),
17388    );
17389
17390    cx.set_head_text(&diff_base);
17391    executor.run_until_parked();
17392    cx.update_editor(|editor, window, cx| {
17393        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17394    });
17395    executor.run_until_parked();
17396
17397    cx.assert_state_with_diff(
17398        r#"
17399        use some::mod1;
17400        use some::mod2;
17401
17402        const A: u32 = 42;
17403        const B: u32 = 42;
17404      - const C: u32 = 42;
17405      + const C: u32 = 43ˇ
17406        const D: u32 = 42;
17407
17408
17409        fn main() {
17410            println!("hello");
17411
17412            println!("world");
17413        }"#
17414        .unindent(),
17415    );
17416
17417    cx.update_editor(|editor, window, cx| {
17418        editor.handle_input("\nnew_line\n", window, cx);
17419    });
17420    executor.run_until_parked();
17421
17422    cx.assert_state_with_diff(
17423        r#"
17424        use some::mod1;
17425        use some::mod2;
17426
17427        const A: u32 = 42;
17428        const B: u32 = 42;
17429      - const C: u32 = 42;
17430      + const C: u32 = 43
17431      + new_line
17432      + ˇ
17433        const D: u32 = 42;
17434
17435
17436        fn main() {
17437            println!("hello");
17438
17439            println!("world");
17440        }"#
17441        .unindent(),
17442    );
17443}
17444
17445#[gpui::test]
17446async fn test_stage_and_unstage_added_file_hunk(
17447    executor: BackgroundExecutor,
17448    cx: &mut TestAppContext,
17449) {
17450    init_test(cx, |_| {});
17451
17452    let mut cx = EditorTestContext::new(cx).await;
17453    cx.update_editor(|editor, _, cx| {
17454        editor.set_expand_all_diff_hunks(cx);
17455    });
17456
17457    let working_copy = r#"
17458            ˇfn main() {
17459                println!("hello, world!");
17460            }
17461        "#
17462    .unindent();
17463
17464    cx.set_state(&working_copy);
17465    executor.run_until_parked();
17466
17467    cx.assert_state_with_diff(
17468        r#"
17469            + ˇfn main() {
17470            +     println!("hello, world!");
17471            + }
17472        "#
17473        .unindent(),
17474    );
17475    cx.assert_index_text(None);
17476
17477    cx.update_editor(|editor, window, cx| {
17478        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17479    });
17480    executor.run_until_parked();
17481    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17482    cx.assert_state_with_diff(
17483        r#"
17484            + ˇfn main() {
17485            +     println!("hello, world!");
17486            + }
17487        "#
17488        .unindent(),
17489    );
17490
17491    cx.update_editor(|editor, window, cx| {
17492        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17493    });
17494    executor.run_until_parked();
17495    cx.assert_index_text(None);
17496}
17497
17498async fn setup_indent_guides_editor(
17499    text: &str,
17500    cx: &mut TestAppContext,
17501) -> (BufferId, EditorTestContext) {
17502    init_test(cx, |_| {});
17503
17504    let mut cx = EditorTestContext::new(cx).await;
17505
17506    let buffer_id = cx.update_editor(|editor, window, cx| {
17507        editor.set_text(text, window, cx);
17508        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17509
17510        buffer_ids[0]
17511    });
17512
17513    (buffer_id, cx)
17514}
17515
17516fn assert_indent_guides(
17517    range: Range<u32>,
17518    expected: Vec<IndentGuide>,
17519    active_indices: Option<Vec<usize>>,
17520    cx: &mut EditorTestContext,
17521) {
17522    let indent_guides = cx.update_editor(|editor, window, cx| {
17523        let snapshot = editor.snapshot(window, cx).display_snapshot;
17524        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17525            editor,
17526            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17527            true,
17528            &snapshot,
17529            cx,
17530        );
17531
17532        indent_guides.sort_by(|a, b| {
17533            a.depth.cmp(&b.depth).then(
17534                a.start_row
17535                    .cmp(&b.start_row)
17536                    .then(a.end_row.cmp(&b.end_row)),
17537            )
17538        });
17539        indent_guides
17540    });
17541
17542    if let Some(expected) = active_indices {
17543        let active_indices = cx.update_editor(|editor, window, cx| {
17544            let snapshot = editor.snapshot(window, cx).display_snapshot;
17545            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17546        });
17547
17548        assert_eq!(
17549            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17550            expected,
17551            "Active indent guide indices do not match"
17552        );
17553    }
17554
17555    assert_eq!(indent_guides, expected, "Indent guides do not match");
17556}
17557
17558fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17559    IndentGuide {
17560        buffer_id,
17561        start_row: MultiBufferRow(start_row),
17562        end_row: MultiBufferRow(end_row),
17563        depth,
17564        tab_size: 4,
17565        settings: IndentGuideSettings {
17566            enabled: true,
17567            line_width: 1,
17568            active_line_width: 1,
17569            ..Default::default()
17570        },
17571    }
17572}
17573
17574#[gpui::test]
17575async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17576    let (buffer_id, mut cx) = setup_indent_guides_editor(
17577        &"
17578        fn main() {
17579            let a = 1;
17580        }"
17581        .unindent(),
17582        cx,
17583    )
17584    .await;
17585
17586    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17587}
17588
17589#[gpui::test]
17590async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17591    let (buffer_id, mut cx) = setup_indent_guides_editor(
17592        &"
17593        fn main() {
17594            let a = 1;
17595            let b = 2;
17596        }"
17597        .unindent(),
17598        cx,
17599    )
17600    .await;
17601
17602    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17603}
17604
17605#[gpui::test]
17606async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17607    let (buffer_id, mut cx) = setup_indent_guides_editor(
17608        &"
17609        fn main() {
17610            let a = 1;
17611            if a == 3 {
17612                let b = 2;
17613            } else {
17614                let c = 3;
17615            }
17616        }"
17617        .unindent(),
17618        cx,
17619    )
17620    .await;
17621
17622    assert_indent_guides(
17623        0..8,
17624        vec![
17625            indent_guide(buffer_id, 1, 6, 0),
17626            indent_guide(buffer_id, 3, 3, 1),
17627            indent_guide(buffer_id, 5, 5, 1),
17628        ],
17629        None,
17630        &mut cx,
17631    );
17632}
17633
17634#[gpui::test]
17635async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17636    let (buffer_id, mut cx) = setup_indent_guides_editor(
17637        &"
17638        fn main() {
17639            let a = 1;
17640                let b = 2;
17641            let c = 3;
17642        }"
17643        .unindent(),
17644        cx,
17645    )
17646    .await;
17647
17648    assert_indent_guides(
17649        0..5,
17650        vec![
17651            indent_guide(buffer_id, 1, 3, 0),
17652            indent_guide(buffer_id, 2, 2, 1),
17653        ],
17654        None,
17655        &mut cx,
17656    );
17657}
17658
17659#[gpui::test]
17660async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17661    let (buffer_id, mut cx) = setup_indent_guides_editor(
17662        &"
17663        fn main() {
17664            let a = 1;
17665
17666            let c = 3;
17667        }"
17668        .unindent(),
17669        cx,
17670    )
17671    .await;
17672
17673    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17674}
17675
17676#[gpui::test]
17677async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17678    let (buffer_id, mut cx) = setup_indent_guides_editor(
17679        &"
17680        fn main() {
17681            let a = 1;
17682
17683            let c = 3;
17684
17685            if a == 3 {
17686                let b = 2;
17687            } else {
17688                let c = 3;
17689            }
17690        }"
17691        .unindent(),
17692        cx,
17693    )
17694    .await;
17695
17696    assert_indent_guides(
17697        0..11,
17698        vec![
17699            indent_guide(buffer_id, 1, 9, 0),
17700            indent_guide(buffer_id, 6, 6, 1),
17701            indent_guide(buffer_id, 8, 8, 1),
17702        ],
17703        None,
17704        &mut cx,
17705    );
17706}
17707
17708#[gpui::test]
17709async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17710    let (buffer_id, mut cx) = setup_indent_guides_editor(
17711        &"
17712        fn main() {
17713            let a = 1;
17714
17715            let c = 3;
17716
17717            if a == 3 {
17718                let b = 2;
17719            } else {
17720                let c = 3;
17721            }
17722        }"
17723        .unindent(),
17724        cx,
17725    )
17726    .await;
17727
17728    assert_indent_guides(
17729        1..11,
17730        vec![
17731            indent_guide(buffer_id, 1, 9, 0),
17732            indent_guide(buffer_id, 6, 6, 1),
17733            indent_guide(buffer_id, 8, 8, 1),
17734        ],
17735        None,
17736        &mut cx,
17737    );
17738}
17739
17740#[gpui::test]
17741async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17742    let (buffer_id, mut cx) = setup_indent_guides_editor(
17743        &"
17744        fn main() {
17745            let a = 1;
17746
17747            let c = 3;
17748
17749            if a == 3 {
17750                let b = 2;
17751            } else {
17752                let c = 3;
17753            }
17754        }"
17755        .unindent(),
17756        cx,
17757    )
17758    .await;
17759
17760    assert_indent_guides(
17761        1..10,
17762        vec![
17763            indent_guide(buffer_id, 1, 9, 0),
17764            indent_guide(buffer_id, 6, 6, 1),
17765            indent_guide(buffer_id, 8, 8, 1),
17766        ],
17767        None,
17768        &mut cx,
17769    );
17770}
17771
17772#[gpui::test]
17773async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17774    let (buffer_id, mut cx) = setup_indent_guides_editor(
17775        &"
17776        fn main() {
17777            if a {
17778                b(
17779                    c,
17780                    d,
17781                )
17782            } else {
17783                e(
17784                    f
17785                )
17786            }
17787        }"
17788        .unindent(),
17789        cx,
17790    )
17791    .await;
17792
17793    assert_indent_guides(
17794        0..11,
17795        vec![
17796            indent_guide(buffer_id, 1, 10, 0),
17797            indent_guide(buffer_id, 2, 5, 1),
17798            indent_guide(buffer_id, 7, 9, 1),
17799            indent_guide(buffer_id, 3, 4, 2),
17800            indent_guide(buffer_id, 8, 8, 2),
17801        ],
17802        None,
17803        &mut cx,
17804    );
17805
17806    cx.update_editor(|editor, window, cx| {
17807        editor.fold_at(MultiBufferRow(2), window, cx);
17808        assert_eq!(
17809            editor.display_text(cx),
17810            "
17811            fn main() {
17812                if a {
17813                    b(⋯
17814                    )
17815                } else {
17816                    e(
17817                        f
17818                    )
17819                }
17820            }"
17821            .unindent()
17822        );
17823    });
17824
17825    assert_indent_guides(
17826        0..11,
17827        vec![
17828            indent_guide(buffer_id, 1, 10, 0),
17829            indent_guide(buffer_id, 2, 5, 1),
17830            indent_guide(buffer_id, 7, 9, 1),
17831            indent_guide(buffer_id, 8, 8, 2),
17832        ],
17833        None,
17834        &mut cx,
17835    );
17836}
17837
17838#[gpui::test]
17839async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17840    let (buffer_id, mut cx) = setup_indent_guides_editor(
17841        &"
17842        block1
17843            block2
17844                block3
17845                    block4
17846            block2
17847        block1
17848        block1"
17849            .unindent(),
17850        cx,
17851    )
17852    .await;
17853
17854    assert_indent_guides(
17855        1..10,
17856        vec![
17857            indent_guide(buffer_id, 1, 4, 0),
17858            indent_guide(buffer_id, 2, 3, 1),
17859            indent_guide(buffer_id, 3, 3, 2),
17860        ],
17861        None,
17862        &mut cx,
17863    );
17864}
17865
17866#[gpui::test]
17867async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17868    let (buffer_id, mut cx) = setup_indent_guides_editor(
17869        &"
17870        block1
17871            block2
17872                block3
17873
17874        block1
17875        block1"
17876            .unindent(),
17877        cx,
17878    )
17879    .await;
17880
17881    assert_indent_guides(
17882        0..6,
17883        vec![
17884            indent_guide(buffer_id, 1, 2, 0),
17885            indent_guide(buffer_id, 2, 2, 1),
17886        ],
17887        None,
17888        &mut cx,
17889    );
17890}
17891
17892#[gpui::test]
17893async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17894    let (buffer_id, mut cx) = setup_indent_guides_editor(
17895        &"
17896        function component() {
17897        \treturn (
17898        \t\t\t
17899        \t\t<div>
17900        \t\t\t<abc></abc>
17901        \t\t</div>
17902        \t)
17903        }"
17904        .unindent(),
17905        cx,
17906    )
17907    .await;
17908
17909    assert_indent_guides(
17910        0..8,
17911        vec![
17912            indent_guide(buffer_id, 1, 6, 0),
17913            indent_guide(buffer_id, 2, 5, 1),
17914            indent_guide(buffer_id, 4, 4, 2),
17915        ],
17916        None,
17917        &mut cx,
17918    );
17919}
17920
17921#[gpui::test]
17922async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17923    let (buffer_id, mut cx) = setup_indent_guides_editor(
17924        &"
17925        function component() {
17926        \treturn (
17927        \t
17928        \t\t<div>
17929        \t\t\t<abc></abc>
17930        \t\t</div>
17931        \t)
17932        }"
17933        .unindent(),
17934        cx,
17935    )
17936    .await;
17937
17938    assert_indent_guides(
17939        0..8,
17940        vec![
17941            indent_guide(buffer_id, 1, 6, 0),
17942            indent_guide(buffer_id, 2, 5, 1),
17943            indent_guide(buffer_id, 4, 4, 2),
17944        ],
17945        None,
17946        &mut cx,
17947    );
17948}
17949
17950#[gpui::test]
17951async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17952    let (buffer_id, mut cx) = setup_indent_guides_editor(
17953        &"
17954        block1
17955
17956
17957
17958            block2
17959        "
17960        .unindent(),
17961        cx,
17962    )
17963    .await;
17964
17965    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17966}
17967
17968#[gpui::test]
17969async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17970    let (buffer_id, mut cx) = setup_indent_guides_editor(
17971        &"
17972        def a:
17973        \tb = 3
17974        \tif True:
17975        \t\tc = 4
17976        \t\td = 5
17977        \tprint(b)
17978        "
17979        .unindent(),
17980        cx,
17981    )
17982    .await;
17983
17984    assert_indent_guides(
17985        0..6,
17986        vec![
17987            indent_guide(buffer_id, 1, 5, 0),
17988            indent_guide(buffer_id, 3, 4, 1),
17989        ],
17990        None,
17991        &mut cx,
17992    );
17993}
17994
17995#[gpui::test]
17996async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17997    let (buffer_id, mut cx) = setup_indent_guides_editor(
17998        &"
17999    fn main() {
18000        let a = 1;
18001    }"
18002        .unindent(),
18003        cx,
18004    )
18005    .await;
18006
18007    cx.update_editor(|editor, window, cx| {
18008        editor.change_selections(None, window, cx, |s| {
18009            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18010        });
18011    });
18012
18013    assert_indent_guides(
18014        0..3,
18015        vec![indent_guide(buffer_id, 1, 1, 0)],
18016        Some(vec![0]),
18017        &mut cx,
18018    );
18019}
18020
18021#[gpui::test]
18022async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18023    let (buffer_id, mut cx) = setup_indent_guides_editor(
18024        &"
18025    fn main() {
18026        if 1 == 2 {
18027            let a = 1;
18028        }
18029    }"
18030        .unindent(),
18031        cx,
18032    )
18033    .await;
18034
18035    cx.update_editor(|editor, window, cx| {
18036        editor.change_selections(None, window, cx, |s| {
18037            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18038        });
18039    });
18040
18041    assert_indent_guides(
18042        0..4,
18043        vec![
18044            indent_guide(buffer_id, 1, 3, 0),
18045            indent_guide(buffer_id, 2, 2, 1),
18046        ],
18047        Some(vec![1]),
18048        &mut cx,
18049    );
18050
18051    cx.update_editor(|editor, window, cx| {
18052        editor.change_selections(None, window, cx, |s| {
18053            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18054        });
18055    });
18056
18057    assert_indent_guides(
18058        0..4,
18059        vec![
18060            indent_guide(buffer_id, 1, 3, 0),
18061            indent_guide(buffer_id, 2, 2, 1),
18062        ],
18063        Some(vec![1]),
18064        &mut cx,
18065    );
18066
18067    cx.update_editor(|editor, window, cx| {
18068        editor.change_selections(None, window, cx, |s| {
18069            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18070        });
18071    });
18072
18073    assert_indent_guides(
18074        0..4,
18075        vec![
18076            indent_guide(buffer_id, 1, 3, 0),
18077            indent_guide(buffer_id, 2, 2, 1),
18078        ],
18079        Some(vec![0]),
18080        &mut cx,
18081    );
18082}
18083
18084#[gpui::test]
18085async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18086    let (buffer_id, mut cx) = setup_indent_guides_editor(
18087        &"
18088    fn main() {
18089        let a = 1;
18090
18091        let b = 2;
18092    }"
18093        .unindent(),
18094        cx,
18095    )
18096    .await;
18097
18098    cx.update_editor(|editor, window, cx| {
18099        editor.change_selections(None, window, cx, |s| {
18100            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18101        });
18102    });
18103
18104    assert_indent_guides(
18105        0..5,
18106        vec![indent_guide(buffer_id, 1, 3, 0)],
18107        Some(vec![0]),
18108        &mut cx,
18109    );
18110}
18111
18112#[gpui::test]
18113async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18114    let (buffer_id, mut cx) = setup_indent_guides_editor(
18115        &"
18116    def m:
18117        a = 1
18118        pass"
18119            .unindent(),
18120        cx,
18121    )
18122    .await;
18123
18124    cx.update_editor(|editor, window, cx| {
18125        editor.change_selections(None, window, cx, |s| {
18126            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18127        });
18128    });
18129
18130    assert_indent_guides(
18131        0..3,
18132        vec![indent_guide(buffer_id, 1, 2, 0)],
18133        Some(vec![0]),
18134        &mut cx,
18135    );
18136}
18137
18138#[gpui::test]
18139async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18140    init_test(cx, |_| {});
18141    let mut cx = EditorTestContext::new(cx).await;
18142    let text = indoc! {
18143        "
18144        impl A {
18145            fn b() {
18146                0;
18147                3;
18148                5;
18149                6;
18150                7;
18151            }
18152        }
18153        "
18154    };
18155    let base_text = indoc! {
18156        "
18157        impl A {
18158            fn b() {
18159                0;
18160                1;
18161                2;
18162                3;
18163                4;
18164            }
18165            fn c() {
18166                5;
18167                6;
18168                7;
18169            }
18170        }
18171        "
18172    };
18173
18174    cx.update_editor(|editor, window, cx| {
18175        editor.set_text(text, window, cx);
18176
18177        editor.buffer().update(cx, |multibuffer, cx| {
18178            let buffer = multibuffer.as_singleton().unwrap();
18179            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18180
18181            multibuffer.set_all_diff_hunks_expanded(cx);
18182            multibuffer.add_diff(diff, cx);
18183
18184            buffer.read(cx).remote_id()
18185        })
18186    });
18187    cx.run_until_parked();
18188
18189    cx.assert_state_with_diff(
18190        indoc! { "
18191          impl A {
18192              fn b() {
18193                  0;
18194        -         1;
18195        -         2;
18196                  3;
18197        -         4;
18198        -     }
18199        -     fn c() {
18200                  5;
18201                  6;
18202                  7;
18203              }
18204          }
18205          ˇ"
18206        }
18207        .to_string(),
18208    );
18209
18210    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18211        editor
18212            .snapshot(window, cx)
18213            .buffer_snapshot
18214            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18215            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18216            .collect::<Vec<_>>()
18217    });
18218    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18219    assert_eq!(
18220        actual_guides,
18221        vec![
18222            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18223            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18224            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18225        ]
18226    );
18227}
18228
18229#[gpui::test]
18230async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18231    init_test(cx, |_| {});
18232    let mut cx = EditorTestContext::new(cx).await;
18233
18234    let diff_base = r#"
18235        a
18236        b
18237        c
18238        "#
18239    .unindent();
18240
18241    cx.set_state(
18242        &r#"
18243        ˇA
18244        b
18245        C
18246        "#
18247        .unindent(),
18248    );
18249    cx.set_head_text(&diff_base);
18250    cx.update_editor(|editor, window, cx| {
18251        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18252    });
18253    executor.run_until_parked();
18254
18255    let both_hunks_expanded = r#"
18256        - a
18257        + ˇA
18258          b
18259        - c
18260        + C
18261        "#
18262    .unindent();
18263
18264    cx.assert_state_with_diff(both_hunks_expanded.clone());
18265
18266    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18267        let snapshot = editor.snapshot(window, cx);
18268        let hunks = editor
18269            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18270            .collect::<Vec<_>>();
18271        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18272        let buffer_id = hunks[0].buffer_id;
18273        hunks
18274            .into_iter()
18275            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18276            .collect::<Vec<_>>()
18277    });
18278    assert_eq!(hunk_ranges.len(), 2);
18279
18280    cx.update_editor(|editor, _, cx| {
18281        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18282    });
18283    executor.run_until_parked();
18284
18285    let second_hunk_expanded = r#"
18286          ˇA
18287          b
18288        - c
18289        + C
18290        "#
18291    .unindent();
18292
18293    cx.assert_state_with_diff(second_hunk_expanded);
18294
18295    cx.update_editor(|editor, _, cx| {
18296        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18297    });
18298    executor.run_until_parked();
18299
18300    cx.assert_state_with_diff(both_hunks_expanded.clone());
18301
18302    cx.update_editor(|editor, _, cx| {
18303        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18304    });
18305    executor.run_until_parked();
18306
18307    let first_hunk_expanded = r#"
18308        - a
18309        + ˇA
18310          b
18311          C
18312        "#
18313    .unindent();
18314
18315    cx.assert_state_with_diff(first_hunk_expanded);
18316
18317    cx.update_editor(|editor, _, cx| {
18318        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18319    });
18320    executor.run_until_parked();
18321
18322    cx.assert_state_with_diff(both_hunks_expanded);
18323
18324    cx.set_state(
18325        &r#"
18326        ˇA
18327        b
18328        "#
18329        .unindent(),
18330    );
18331    cx.run_until_parked();
18332
18333    // TODO this cursor position seems bad
18334    cx.assert_state_with_diff(
18335        r#"
18336        - ˇa
18337        + A
18338          b
18339        "#
18340        .unindent(),
18341    );
18342
18343    cx.update_editor(|editor, window, cx| {
18344        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18345    });
18346
18347    cx.assert_state_with_diff(
18348        r#"
18349            - ˇa
18350            + A
18351              b
18352            - c
18353            "#
18354        .unindent(),
18355    );
18356
18357    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18358        let snapshot = editor.snapshot(window, cx);
18359        let hunks = editor
18360            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18361            .collect::<Vec<_>>();
18362        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18363        let buffer_id = hunks[0].buffer_id;
18364        hunks
18365            .into_iter()
18366            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18367            .collect::<Vec<_>>()
18368    });
18369    assert_eq!(hunk_ranges.len(), 2);
18370
18371    cx.update_editor(|editor, _, cx| {
18372        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18373    });
18374    executor.run_until_parked();
18375
18376    cx.assert_state_with_diff(
18377        r#"
18378        - ˇa
18379        + A
18380          b
18381        "#
18382        .unindent(),
18383    );
18384}
18385
18386#[gpui::test]
18387async fn test_toggle_deletion_hunk_at_start_of_file(
18388    executor: BackgroundExecutor,
18389    cx: &mut TestAppContext,
18390) {
18391    init_test(cx, |_| {});
18392    let mut cx = EditorTestContext::new(cx).await;
18393
18394    let diff_base = r#"
18395        a
18396        b
18397        c
18398        "#
18399    .unindent();
18400
18401    cx.set_state(
18402        &r#"
18403        ˇb
18404        c
18405        "#
18406        .unindent(),
18407    );
18408    cx.set_head_text(&diff_base);
18409    cx.update_editor(|editor, window, cx| {
18410        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18411    });
18412    executor.run_until_parked();
18413
18414    let hunk_expanded = r#"
18415        - a
18416          ˇb
18417          c
18418        "#
18419    .unindent();
18420
18421    cx.assert_state_with_diff(hunk_expanded.clone());
18422
18423    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18424        let snapshot = editor.snapshot(window, cx);
18425        let hunks = editor
18426            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18427            .collect::<Vec<_>>();
18428        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18429        let buffer_id = hunks[0].buffer_id;
18430        hunks
18431            .into_iter()
18432            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18433            .collect::<Vec<_>>()
18434    });
18435    assert_eq!(hunk_ranges.len(), 1);
18436
18437    cx.update_editor(|editor, _, cx| {
18438        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18439    });
18440    executor.run_until_parked();
18441
18442    let hunk_collapsed = r#"
18443          ˇb
18444          c
18445        "#
18446    .unindent();
18447
18448    cx.assert_state_with_diff(hunk_collapsed);
18449
18450    cx.update_editor(|editor, _, cx| {
18451        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18452    });
18453    executor.run_until_parked();
18454
18455    cx.assert_state_with_diff(hunk_expanded.clone());
18456}
18457
18458#[gpui::test]
18459async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18460    init_test(cx, |_| {});
18461
18462    let fs = FakeFs::new(cx.executor());
18463    fs.insert_tree(
18464        path!("/test"),
18465        json!({
18466            ".git": {},
18467            "file-1": "ONE\n",
18468            "file-2": "TWO\n",
18469            "file-3": "THREE\n",
18470        }),
18471    )
18472    .await;
18473
18474    fs.set_head_for_repo(
18475        path!("/test/.git").as_ref(),
18476        &[
18477            ("file-1".into(), "one\n".into()),
18478            ("file-2".into(), "two\n".into()),
18479            ("file-3".into(), "three\n".into()),
18480        ],
18481        "deadbeef",
18482    );
18483
18484    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18485    let mut buffers = vec![];
18486    for i in 1..=3 {
18487        let buffer = project
18488            .update(cx, |project, cx| {
18489                let path = format!(path!("/test/file-{}"), i);
18490                project.open_local_buffer(path, cx)
18491            })
18492            .await
18493            .unwrap();
18494        buffers.push(buffer);
18495    }
18496
18497    let multibuffer = cx.new(|cx| {
18498        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18499        multibuffer.set_all_diff_hunks_expanded(cx);
18500        for buffer in &buffers {
18501            let snapshot = buffer.read(cx).snapshot();
18502            multibuffer.set_excerpts_for_path(
18503                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18504                buffer.clone(),
18505                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18506                DEFAULT_MULTIBUFFER_CONTEXT,
18507                cx,
18508            );
18509        }
18510        multibuffer
18511    });
18512
18513    let editor = cx.add_window(|window, cx| {
18514        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18515    });
18516    cx.run_until_parked();
18517
18518    let snapshot = editor
18519        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18520        .unwrap();
18521    let hunks = snapshot
18522        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18523        .map(|hunk| match hunk {
18524            DisplayDiffHunk::Unfolded {
18525                display_row_range, ..
18526            } => display_row_range,
18527            DisplayDiffHunk::Folded { .. } => unreachable!(),
18528        })
18529        .collect::<Vec<_>>();
18530    assert_eq!(
18531        hunks,
18532        [
18533            DisplayRow(2)..DisplayRow(4),
18534            DisplayRow(7)..DisplayRow(9),
18535            DisplayRow(12)..DisplayRow(14),
18536        ]
18537    );
18538}
18539
18540#[gpui::test]
18541async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18542    init_test(cx, |_| {});
18543
18544    let mut cx = EditorTestContext::new(cx).await;
18545    cx.set_head_text(indoc! { "
18546        one
18547        two
18548        three
18549        four
18550        five
18551        "
18552    });
18553    cx.set_index_text(indoc! { "
18554        one
18555        two
18556        three
18557        four
18558        five
18559        "
18560    });
18561    cx.set_state(indoc! {"
18562        one
18563        TWO
18564        ˇTHREE
18565        FOUR
18566        five
18567    "});
18568    cx.run_until_parked();
18569    cx.update_editor(|editor, window, cx| {
18570        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18571    });
18572    cx.run_until_parked();
18573    cx.assert_index_text(Some(indoc! {"
18574        one
18575        TWO
18576        THREE
18577        FOUR
18578        five
18579    "}));
18580    cx.set_state(indoc! { "
18581        one
18582        TWO
18583        ˇTHREE-HUNDRED
18584        FOUR
18585        five
18586    "});
18587    cx.run_until_parked();
18588    cx.update_editor(|editor, window, cx| {
18589        let snapshot = editor.snapshot(window, cx);
18590        let hunks = editor
18591            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18592            .collect::<Vec<_>>();
18593        assert_eq!(hunks.len(), 1);
18594        assert_eq!(
18595            hunks[0].status(),
18596            DiffHunkStatus {
18597                kind: DiffHunkStatusKind::Modified,
18598                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18599            }
18600        );
18601
18602        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18603    });
18604    cx.run_until_parked();
18605    cx.assert_index_text(Some(indoc! {"
18606        one
18607        TWO
18608        THREE-HUNDRED
18609        FOUR
18610        five
18611    "}));
18612}
18613
18614#[gpui::test]
18615fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18616    init_test(cx, |_| {});
18617
18618    let editor = cx.add_window(|window, cx| {
18619        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18620        build_editor(buffer, window, cx)
18621    });
18622
18623    let render_args = Arc::new(Mutex::new(None));
18624    let snapshot = editor
18625        .update(cx, |editor, window, cx| {
18626            let snapshot = editor.buffer().read(cx).snapshot(cx);
18627            let range =
18628                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18629
18630            struct RenderArgs {
18631                row: MultiBufferRow,
18632                folded: bool,
18633                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18634            }
18635
18636            let crease = Crease::inline(
18637                range,
18638                FoldPlaceholder::test(),
18639                {
18640                    let toggle_callback = render_args.clone();
18641                    move |row, folded, callback, _window, _cx| {
18642                        *toggle_callback.lock() = Some(RenderArgs {
18643                            row,
18644                            folded,
18645                            callback,
18646                        });
18647                        div()
18648                    }
18649                },
18650                |_row, _folded, _window, _cx| div(),
18651            );
18652
18653            editor.insert_creases(Some(crease), cx);
18654            let snapshot = editor.snapshot(window, cx);
18655            let _div = snapshot.render_crease_toggle(
18656                MultiBufferRow(1),
18657                false,
18658                cx.entity().clone(),
18659                window,
18660                cx,
18661            );
18662            snapshot
18663        })
18664        .unwrap();
18665
18666    let render_args = render_args.lock().take().unwrap();
18667    assert_eq!(render_args.row, MultiBufferRow(1));
18668    assert!(!render_args.folded);
18669    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18670
18671    cx.update_window(*editor, |_, window, cx| {
18672        (render_args.callback)(true, window, cx)
18673    })
18674    .unwrap();
18675    let snapshot = editor
18676        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18677        .unwrap();
18678    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18679
18680    cx.update_window(*editor, |_, window, cx| {
18681        (render_args.callback)(false, window, cx)
18682    })
18683    .unwrap();
18684    let snapshot = editor
18685        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18686        .unwrap();
18687    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18688}
18689
18690#[gpui::test]
18691async fn test_input_text(cx: &mut TestAppContext) {
18692    init_test(cx, |_| {});
18693    let mut cx = EditorTestContext::new(cx).await;
18694
18695    cx.set_state(
18696        &r#"ˇone
18697        two
18698
18699        three
18700        fourˇ
18701        five
18702
18703        siˇx"#
18704            .unindent(),
18705    );
18706
18707    cx.dispatch_action(HandleInput(String::new()));
18708    cx.assert_editor_state(
18709        &r#"ˇone
18710        two
18711
18712        three
18713        fourˇ
18714        five
18715
18716        siˇx"#
18717            .unindent(),
18718    );
18719
18720    cx.dispatch_action(HandleInput("AAAA".to_string()));
18721    cx.assert_editor_state(
18722        &r#"AAAAˇone
18723        two
18724
18725        three
18726        fourAAAAˇ
18727        five
18728
18729        siAAAAˇx"#
18730            .unindent(),
18731    );
18732}
18733
18734#[gpui::test]
18735async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18736    init_test(cx, |_| {});
18737
18738    let mut cx = EditorTestContext::new(cx).await;
18739    cx.set_state(
18740        r#"let foo = 1;
18741let foo = 2;
18742let foo = 3;
18743let fooˇ = 4;
18744let foo = 5;
18745let foo = 6;
18746let foo = 7;
18747let foo = 8;
18748let foo = 9;
18749let foo = 10;
18750let foo = 11;
18751let foo = 12;
18752let foo = 13;
18753let foo = 14;
18754let foo = 15;"#,
18755    );
18756
18757    cx.update_editor(|e, window, cx| {
18758        assert_eq!(
18759            e.next_scroll_position,
18760            NextScrollCursorCenterTopBottom::Center,
18761            "Default next scroll direction is center",
18762        );
18763
18764        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18765        assert_eq!(
18766            e.next_scroll_position,
18767            NextScrollCursorCenterTopBottom::Top,
18768            "After center, next scroll direction should be top",
18769        );
18770
18771        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18772        assert_eq!(
18773            e.next_scroll_position,
18774            NextScrollCursorCenterTopBottom::Bottom,
18775            "After top, next scroll direction should be bottom",
18776        );
18777
18778        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18779        assert_eq!(
18780            e.next_scroll_position,
18781            NextScrollCursorCenterTopBottom::Center,
18782            "After bottom, scrolling should start over",
18783        );
18784
18785        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18786        assert_eq!(
18787            e.next_scroll_position,
18788            NextScrollCursorCenterTopBottom::Top,
18789            "Scrolling continues if retriggered fast enough"
18790        );
18791    });
18792
18793    cx.executor()
18794        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18795    cx.executor().run_until_parked();
18796    cx.update_editor(|e, _, _| {
18797        assert_eq!(
18798            e.next_scroll_position,
18799            NextScrollCursorCenterTopBottom::Center,
18800            "If scrolling is not triggered fast enough, it should reset"
18801        );
18802    });
18803}
18804
18805#[gpui::test]
18806async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18807    init_test(cx, |_| {});
18808    let mut cx = EditorLspTestContext::new_rust(
18809        lsp::ServerCapabilities {
18810            definition_provider: Some(lsp::OneOf::Left(true)),
18811            references_provider: Some(lsp::OneOf::Left(true)),
18812            ..lsp::ServerCapabilities::default()
18813        },
18814        cx,
18815    )
18816    .await;
18817
18818    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18819        let go_to_definition = cx
18820            .lsp
18821            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18822                move |params, _| async move {
18823                    if empty_go_to_definition {
18824                        Ok(None)
18825                    } else {
18826                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18827                            uri: params.text_document_position_params.text_document.uri,
18828                            range: lsp::Range::new(
18829                                lsp::Position::new(4, 3),
18830                                lsp::Position::new(4, 6),
18831                            ),
18832                        })))
18833                    }
18834                },
18835            );
18836        let references = cx
18837            .lsp
18838            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18839                Ok(Some(vec![lsp::Location {
18840                    uri: params.text_document_position.text_document.uri,
18841                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18842                }]))
18843            });
18844        (go_to_definition, references)
18845    };
18846
18847    cx.set_state(
18848        &r#"fn one() {
18849            let mut a = ˇtwo();
18850        }
18851
18852        fn two() {}"#
18853            .unindent(),
18854    );
18855    set_up_lsp_handlers(false, &mut cx);
18856    let navigated = cx
18857        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18858        .await
18859        .expect("Failed to navigate to definition");
18860    assert_eq!(
18861        navigated,
18862        Navigated::Yes,
18863        "Should have navigated to definition from the GetDefinition response"
18864    );
18865    cx.assert_editor_state(
18866        &r#"fn one() {
18867            let mut a = two();
18868        }
18869
18870        fn «twoˇ»() {}"#
18871            .unindent(),
18872    );
18873
18874    let editors = cx.update_workspace(|workspace, _, cx| {
18875        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18876    });
18877    cx.update_editor(|_, _, test_editor_cx| {
18878        assert_eq!(
18879            editors.len(),
18880            1,
18881            "Initially, only one, test, editor should be open in the workspace"
18882        );
18883        assert_eq!(
18884            test_editor_cx.entity(),
18885            editors.last().expect("Asserted len is 1").clone()
18886        );
18887    });
18888
18889    set_up_lsp_handlers(true, &mut cx);
18890    let navigated = cx
18891        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18892        .await
18893        .expect("Failed to navigate to lookup references");
18894    assert_eq!(
18895        navigated,
18896        Navigated::Yes,
18897        "Should have navigated to references as a fallback after empty GoToDefinition response"
18898    );
18899    // We should not change the selections in the existing file,
18900    // if opening another milti buffer with the references
18901    cx.assert_editor_state(
18902        &r#"fn one() {
18903            let mut a = two();
18904        }
18905
18906        fn «twoˇ»() {}"#
18907            .unindent(),
18908    );
18909    let editors = cx.update_workspace(|workspace, _, cx| {
18910        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18911    });
18912    cx.update_editor(|_, _, test_editor_cx| {
18913        assert_eq!(
18914            editors.len(),
18915            2,
18916            "After falling back to references search, we open a new editor with the results"
18917        );
18918        let references_fallback_text = editors
18919            .into_iter()
18920            .find(|new_editor| *new_editor != test_editor_cx.entity())
18921            .expect("Should have one non-test editor now")
18922            .read(test_editor_cx)
18923            .text(test_editor_cx);
18924        assert_eq!(
18925            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18926            "Should use the range from the references response and not the GoToDefinition one"
18927        );
18928    });
18929}
18930
18931#[gpui::test]
18932async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18933    init_test(cx, |_| {});
18934    cx.update(|cx| {
18935        let mut editor_settings = EditorSettings::get_global(cx).clone();
18936        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18937        EditorSettings::override_global(editor_settings, cx);
18938    });
18939    let mut cx = EditorLspTestContext::new_rust(
18940        lsp::ServerCapabilities {
18941            definition_provider: Some(lsp::OneOf::Left(true)),
18942            references_provider: Some(lsp::OneOf::Left(true)),
18943            ..lsp::ServerCapabilities::default()
18944        },
18945        cx,
18946    )
18947    .await;
18948    let original_state = r#"fn one() {
18949        let mut a = ˇtwo();
18950    }
18951
18952    fn two() {}"#
18953        .unindent();
18954    cx.set_state(&original_state);
18955
18956    let mut go_to_definition = cx
18957        .lsp
18958        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18959            move |_, _| async move { Ok(None) },
18960        );
18961    let _references = cx
18962        .lsp
18963        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18964            panic!("Should not call for references with no go to definition fallback")
18965        });
18966
18967    let navigated = cx
18968        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18969        .await
18970        .expect("Failed to navigate to lookup references");
18971    go_to_definition
18972        .next()
18973        .await
18974        .expect("Should have called the go_to_definition handler");
18975
18976    assert_eq!(
18977        navigated,
18978        Navigated::No,
18979        "Should have navigated to references as a fallback after empty GoToDefinition response"
18980    );
18981    cx.assert_editor_state(&original_state);
18982    let editors = cx.update_workspace(|workspace, _, cx| {
18983        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18984    });
18985    cx.update_editor(|_, _, _| {
18986        assert_eq!(
18987            editors.len(),
18988            1,
18989            "After unsuccessful fallback, no other editor should have been opened"
18990        );
18991    });
18992}
18993
18994#[gpui::test]
18995async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18996    init_test(cx, |_| {});
18997
18998    let language = Arc::new(Language::new(
18999        LanguageConfig::default(),
19000        Some(tree_sitter_rust::LANGUAGE.into()),
19001    ));
19002
19003    let text = r#"
19004        #[cfg(test)]
19005        mod tests() {
19006            #[test]
19007            fn runnable_1() {
19008                let a = 1;
19009            }
19010
19011            #[test]
19012            fn runnable_2() {
19013                let a = 1;
19014                let b = 2;
19015            }
19016        }
19017    "#
19018    .unindent();
19019
19020    let fs = FakeFs::new(cx.executor());
19021    fs.insert_file("/file.rs", Default::default()).await;
19022
19023    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19024    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19025    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19026    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19027    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19028
19029    let editor = cx.new_window_entity(|window, cx| {
19030        Editor::new(
19031            EditorMode::full(),
19032            multi_buffer,
19033            Some(project.clone()),
19034            window,
19035            cx,
19036        )
19037    });
19038
19039    editor.update_in(cx, |editor, window, cx| {
19040        let snapshot = editor.buffer().read(cx).snapshot(cx);
19041        editor.tasks.insert(
19042            (buffer.read(cx).remote_id(), 3),
19043            RunnableTasks {
19044                templates: vec![],
19045                offset: snapshot.anchor_before(43),
19046                column: 0,
19047                extra_variables: HashMap::default(),
19048                context_range: BufferOffset(43)..BufferOffset(85),
19049            },
19050        );
19051        editor.tasks.insert(
19052            (buffer.read(cx).remote_id(), 8),
19053            RunnableTasks {
19054                templates: vec![],
19055                offset: snapshot.anchor_before(86),
19056                column: 0,
19057                extra_variables: HashMap::default(),
19058                context_range: BufferOffset(86)..BufferOffset(191),
19059            },
19060        );
19061
19062        // Test finding task when cursor is inside function body
19063        editor.change_selections(None, window, cx, |s| {
19064            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19065        });
19066        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19067        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19068
19069        // Test finding task when cursor is on function name
19070        editor.change_selections(None, window, cx, |s| {
19071            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19072        });
19073        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19074        assert_eq!(row, 8, "Should find task when cursor is on function name");
19075    });
19076}
19077
19078#[gpui::test]
19079async fn test_folding_buffers(cx: &mut TestAppContext) {
19080    init_test(cx, |_| {});
19081
19082    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19083    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19084    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19085
19086    let fs = FakeFs::new(cx.executor());
19087    fs.insert_tree(
19088        path!("/a"),
19089        json!({
19090            "first.rs": sample_text_1,
19091            "second.rs": sample_text_2,
19092            "third.rs": sample_text_3,
19093        }),
19094    )
19095    .await;
19096    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19097    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19098    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19099    let worktree = project.update(cx, |project, cx| {
19100        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19101        assert_eq!(worktrees.len(), 1);
19102        worktrees.pop().unwrap()
19103    });
19104    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19105
19106    let buffer_1 = project
19107        .update(cx, |project, cx| {
19108            project.open_buffer((worktree_id, "first.rs"), cx)
19109        })
19110        .await
19111        .unwrap();
19112    let buffer_2 = project
19113        .update(cx, |project, cx| {
19114            project.open_buffer((worktree_id, "second.rs"), cx)
19115        })
19116        .await
19117        .unwrap();
19118    let buffer_3 = project
19119        .update(cx, |project, cx| {
19120            project.open_buffer((worktree_id, "third.rs"), cx)
19121        })
19122        .await
19123        .unwrap();
19124
19125    let multi_buffer = cx.new(|cx| {
19126        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19127        multi_buffer.push_excerpts(
19128            buffer_1.clone(),
19129            [
19130                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19131                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19132                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19133            ],
19134            cx,
19135        );
19136        multi_buffer.push_excerpts(
19137            buffer_2.clone(),
19138            [
19139                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19140                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19141                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19142            ],
19143            cx,
19144        );
19145        multi_buffer.push_excerpts(
19146            buffer_3.clone(),
19147            [
19148                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19149                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19150                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19151            ],
19152            cx,
19153        );
19154        multi_buffer
19155    });
19156    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19157        Editor::new(
19158            EditorMode::full(),
19159            multi_buffer.clone(),
19160            Some(project.clone()),
19161            window,
19162            cx,
19163        )
19164    });
19165
19166    assert_eq!(
19167        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19168        "\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",
19169    );
19170
19171    multi_buffer_editor.update(cx, |editor, cx| {
19172        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19173    });
19174    assert_eq!(
19175        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19176        "\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",
19177        "After folding the first buffer, its text should not be displayed"
19178    );
19179
19180    multi_buffer_editor.update(cx, |editor, cx| {
19181        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19182    });
19183    assert_eq!(
19184        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19185        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19186        "After folding the second buffer, its text should not be displayed"
19187    );
19188
19189    multi_buffer_editor.update(cx, |editor, cx| {
19190        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19191    });
19192    assert_eq!(
19193        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19194        "\n\n\n\n\n",
19195        "After folding the third buffer, its text should not be displayed"
19196    );
19197
19198    // Emulate selection inside the fold logic, that should work
19199    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19200        editor
19201            .snapshot(window, cx)
19202            .next_line_boundary(Point::new(0, 4));
19203    });
19204
19205    multi_buffer_editor.update(cx, |editor, cx| {
19206        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19207    });
19208    assert_eq!(
19209        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19210        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19211        "After unfolding the second buffer, its text should be displayed"
19212    );
19213
19214    // Typing inside of buffer 1 causes that buffer to be unfolded.
19215    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19216        assert_eq!(
19217            multi_buffer
19218                .read(cx)
19219                .snapshot(cx)
19220                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19221                .collect::<String>(),
19222            "bbbb"
19223        );
19224        editor.change_selections(None, window, cx, |selections| {
19225            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19226        });
19227        editor.handle_input("B", window, cx);
19228    });
19229
19230    assert_eq!(
19231        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19232        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19233        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19234    );
19235
19236    multi_buffer_editor.update(cx, |editor, cx| {
19237        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19238    });
19239    assert_eq!(
19240        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19241        "\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",
19242        "After unfolding the all buffers, all original text should be displayed"
19243    );
19244}
19245
19246#[gpui::test]
19247async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19248    init_test(cx, |_| {});
19249
19250    let sample_text_1 = "1111\n2222\n3333".to_string();
19251    let sample_text_2 = "4444\n5555\n6666".to_string();
19252    let sample_text_3 = "7777\n8888\n9999".to_string();
19253
19254    let fs = FakeFs::new(cx.executor());
19255    fs.insert_tree(
19256        path!("/a"),
19257        json!({
19258            "first.rs": sample_text_1,
19259            "second.rs": sample_text_2,
19260            "third.rs": sample_text_3,
19261        }),
19262    )
19263    .await;
19264    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19265    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19266    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19267    let worktree = project.update(cx, |project, cx| {
19268        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19269        assert_eq!(worktrees.len(), 1);
19270        worktrees.pop().unwrap()
19271    });
19272    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19273
19274    let buffer_1 = project
19275        .update(cx, |project, cx| {
19276            project.open_buffer((worktree_id, "first.rs"), cx)
19277        })
19278        .await
19279        .unwrap();
19280    let buffer_2 = project
19281        .update(cx, |project, cx| {
19282            project.open_buffer((worktree_id, "second.rs"), cx)
19283        })
19284        .await
19285        .unwrap();
19286    let buffer_3 = project
19287        .update(cx, |project, cx| {
19288            project.open_buffer((worktree_id, "third.rs"), cx)
19289        })
19290        .await
19291        .unwrap();
19292
19293    let multi_buffer = cx.new(|cx| {
19294        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19295        multi_buffer.push_excerpts(
19296            buffer_1.clone(),
19297            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19298            cx,
19299        );
19300        multi_buffer.push_excerpts(
19301            buffer_2.clone(),
19302            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19303            cx,
19304        );
19305        multi_buffer.push_excerpts(
19306            buffer_3.clone(),
19307            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19308            cx,
19309        );
19310        multi_buffer
19311    });
19312
19313    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19314        Editor::new(
19315            EditorMode::full(),
19316            multi_buffer,
19317            Some(project.clone()),
19318            window,
19319            cx,
19320        )
19321    });
19322
19323    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19324    assert_eq!(
19325        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19326        full_text,
19327    );
19328
19329    multi_buffer_editor.update(cx, |editor, cx| {
19330        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19331    });
19332    assert_eq!(
19333        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19334        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19335        "After folding the first buffer, its text should not be displayed"
19336    );
19337
19338    multi_buffer_editor.update(cx, |editor, cx| {
19339        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19340    });
19341
19342    assert_eq!(
19343        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19344        "\n\n\n\n\n\n7777\n8888\n9999",
19345        "After folding the second buffer, its text should not be displayed"
19346    );
19347
19348    multi_buffer_editor.update(cx, |editor, cx| {
19349        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19350    });
19351    assert_eq!(
19352        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19353        "\n\n\n\n\n",
19354        "After folding the third buffer, its text should not be displayed"
19355    );
19356
19357    multi_buffer_editor.update(cx, |editor, cx| {
19358        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19359    });
19360    assert_eq!(
19361        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19362        "\n\n\n\n4444\n5555\n6666\n\n",
19363        "After unfolding the second buffer, its text should be displayed"
19364    );
19365
19366    multi_buffer_editor.update(cx, |editor, cx| {
19367        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19368    });
19369    assert_eq!(
19370        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19371        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19372        "After unfolding the first buffer, its text should be displayed"
19373    );
19374
19375    multi_buffer_editor.update(cx, |editor, cx| {
19376        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19377    });
19378    assert_eq!(
19379        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19380        full_text,
19381        "After unfolding all buffers, all original text should be displayed"
19382    );
19383}
19384
19385#[gpui::test]
19386async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19387    init_test(cx, |_| {});
19388
19389    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19390
19391    let fs = FakeFs::new(cx.executor());
19392    fs.insert_tree(
19393        path!("/a"),
19394        json!({
19395            "main.rs": sample_text,
19396        }),
19397    )
19398    .await;
19399    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19400    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19401    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19402    let worktree = project.update(cx, |project, cx| {
19403        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19404        assert_eq!(worktrees.len(), 1);
19405        worktrees.pop().unwrap()
19406    });
19407    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19408
19409    let buffer_1 = project
19410        .update(cx, |project, cx| {
19411            project.open_buffer((worktree_id, "main.rs"), cx)
19412        })
19413        .await
19414        .unwrap();
19415
19416    let multi_buffer = cx.new(|cx| {
19417        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19418        multi_buffer.push_excerpts(
19419            buffer_1.clone(),
19420            [ExcerptRange::new(
19421                Point::new(0, 0)
19422                    ..Point::new(
19423                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19424                        0,
19425                    ),
19426            )],
19427            cx,
19428        );
19429        multi_buffer
19430    });
19431    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19432        Editor::new(
19433            EditorMode::full(),
19434            multi_buffer,
19435            Some(project.clone()),
19436            window,
19437            cx,
19438        )
19439    });
19440
19441    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19442    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19443        enum TestHighlight {}
19444        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19445        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19446        editor.highlight_text::<TestHighlight>(
19447            vec![highlight_range.clone()],
19448            HighlightStyle::color(Hsla::green()),
19449            cx,
19450        );
19451        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19452    });
19453
19454    let full_text = format!("\n\n{sample_text}");
19455    assert_eq!(
19456        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19457        full_text,
19458    );
19459}
19460
19461#[gpui::test]
19462async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19463    init_test(cx, |_| {});
19464    cx.update(|cx| {
19465        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19466            "keymaps/default-linux.json",
19467            cx,
19468        )
19469        .unwrap();
19470        cx.bind_keys(default_key_bindings);
19471    });
19472
19473    let (editor, cx) = cx.add_window_view(|window, cx| {
19474        let multi_buffer = MultiBuffer::build_multi(
19475            [
19476                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19477                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19478                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19479                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19480            ],
19481            cx,
19482        );
19483        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19484
19485        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19486        // fold all but the second buffer, so that we test navigating between two
19487        // adjacent folded buffers, as well as folded buffers at the start and
19488        // end the multibuffer
19489        editor.fold_buffer(buffer_ids[0], cx);
19490        editor.fold_buffer(buffer_ids[2], cx);
19491        editor.fold_buffer(buffer_ids[3], cx);
19492
19493        editor
19494    });
19495    cx.simulate_resize(size(px(1000.), px(1000.)));
19496
19497    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19498    cx.assert_excerpts_with_selections(indoc! {"
19499        [EXCERPT]
19500        ˇ[FOLDED]
19501        [EXCERPT]
19502        a1
19503        b1
19504        [EXCERPT]
19505        [FOLDED]
19506        [EXCERPT]
19507        [FOLDED]
19508        "
19509    });
19510    cx.simulate_keystroke("down");
19511    cx.assert_excerpts_with_selections(indoc! {"
19512        [EXCERPT]
19513        [FOLDED]
19514        [EXCERPT]
19515        ˇa1
19516        b1
19517        [EXCERPT]
19518        [FOLDED]
19519        [EXCERPT]
19520        [FOLDED]
19521        "
19522    });
19523    cx.simulate_keystroke("down");
19524    cx.assert_excerpts_with_selections(indoc! {"
19525        [EXCERPT]
19526        [FOLDED]
19527        [EXCERPT]
19528        a1
19529        ˇb1
19530        [EXCERPT]
19531        [FOLDED]
19532        [EXCERPT]
19533        [FOLDED]
19534        "
19535    });
19536    cx.simulate_keystroke("down");
19537    cx.assert_excerpts_with_selections(indoc! {"
19538        [EXCERPT]
19539        [FOLDED]
19540        [EXCERPT]
19541        a1
19542        b1
19543        ˇ[EXCERPT]
19544        [FOLDED]
19545        [EXCERPT]
19546        [FOLDED]
19547        "
19548    });
19549    cx.simulate_keystroke("down");
19550    cx.assert_excerpts_with_selections(indoc! {"
19551        [EXCERPT]
19552        [FOLDED]
19553        [EXCERPT]
19554        a1
19555        b1
19556        [EXCERPT]
19557        ˇ[FOLDED]
19558        [EXCERPT]
19559        [FOLDED]
19560        "
19561    });
19562    for _ in 0..5 {
19563        cx.simulate_keystroke("down");
19564        cx.assert_excerpts_with_selections(indoc! {"
19565            [EXCERPT]
19566            [FOLDED]
19567            [EXCERPT]
19568            a1
19569            b1
19570            [EXCERPT]
19571            [FOLDED]
19572            [EXCERPT]
19573            ˇ[FOLDED]
19574            "
19575        });
19576    }
19577
19578    cx.simulate_keystroke("up");
19579    cx.assert_excerpts_with_selections(indoc! {"
19580        [EXCERPT]
19581        [FOLDED]
19582        [EXCERPT]
19583        a1
19584        b1
19585        [EXCERPT]
19586        ˇ[FOLDED]
19587        [EXCERPT]
19588        [FOLDED]
19589        "
19590    });
19591    cx.simulate_keystroke("up");
19592    cx.assert_excerpts_with_selections(indoc! {"
19593        [EXCERPT]
19594        [FOLDED]
19595        [EXCERPT]
19596        a1
19597        b1
19598        ˇ[EXCERPT]
19599        [FOLDED]
19600        [EXCERPT]
19601        [FOLDED]
19602        "
19603    });
19604    cx.simulate_keystroke("up");
19605    cx.assert_excerpts_with_selections(indoc! {"
19606        [EXCERPT]
19607        [FOLDED]
19608        [EXCERPT]
19609        a1
19610        ˇb1
19611        [EXCERPT]
19612        [FOLDED]
19613        [EXCERPT]
19614        [FOLDED]
19615        "
19616    });
19617    cx.simulate_keystroke("up");
19618    cx.assert_excerpts_with_selections(indoc! {"
19619        [EXCERPT]
19620        [FOLDED]
19621        [EXCERPT]
19622        ˇa1
19623        b1
19624        [EXCERPT]
19625        [FOLDED]
19626        [EXCERPT]
19627        [FOLDED]
19628        "
19629    });
19630    for _ in 0..5 {
19631        cx.simulate_keystroke("up");
19632        cx.assert_excerpts_with_selections(indoc! {"
19633            [EXCERPT]
19634            ˇ[FOLDED]
19635            [EXCERPT]
19636            a1
19637            b1
19638            [EXCERPT]
19639            [FOLDED]
19640            [EXCERPT]
19641            [FOLDED]
19642            "
19643        });
19644    }
19645}
19646
19647#[gpui::test]
19648async fn test_inline_completion_text(cx: &mut TestAppContext) {
19649    init_test(cx, |_| {});
19650
19651    // Simple insertion
19652    assert_highlighted_edits(
19653        "Hello, world!",
19654        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19655        true,
19656        cx,
19657        |highlighted_edits, cx| {
19658            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19659            assert_eq!(highlighted_edits.highlights.len(), 1);
19660            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19661            assert_eq!(
19662                highlighted_edits.highlights[0].1.background_color,
19663                Some(cx.theme().status().created_background)
19664            );
19665        },
19666    )
19667    .await;
19668
19669    // Replacement
19670    assert_highlighted_edits(
19671        "This is a test.",
19672        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19673        false,
19674        cx,
19675        |highlighted_edits, cx| {
19676            assert_eq!(highlighted_edits.text, "That is a test.");
19677            assert_eq!(highlighted_edits.highlights.len(), 1);
19678            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19679            assert_eq!(
19680                highlighted_edits.highlights[0].1.background_color,
19681                Some(cx.theme().status().created_background)
19682            );
19683        },
19684    )
19685    .await;
19686
19687    // Multiple edits
19688    assert_highlighted_edits(
19689        "Hello, world!",
19690        vec![
19691            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19692            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19693        ],
19694        false,
19695        cx,
19696        |highlighted_edits, cx| {
19697            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19698            assert_eq!(highlighted_edits.highlights.len(), 2);
19699            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19700            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19701            assert_eq!(
19702                highlighted_edits.highlights[0].1.background_color,
19703                Some(cx.theme().status().created_background)
19704            );
19705            assert_eq!(
19706                highlighted_edits.highlights[1].1.background_color,
19707                Some(cx.theme().status().created_background)
19708            );
19709        },
19710    )
19711    .await;
19712
19713    // Multiple lines with edits
19714    assert_highlighted_edits(
19715        "First line\nSecond line\nThird line\nFourth line",
19716        vec![
19717            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19718            (
19719                Point::new(2, 0)..Point::new(2, 10),
19720                "New third line".to_string(),
19721            ),
19722            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19723        ],
19724        false,
19725        cx,
19726        |highlighted_edits, cx| {
19727            assert_eq!(
19728                highlighted_edits.text,
19729                "Second modified\nNew third line\nFourth updated line"
19730            );
19731            assert_eq!(highlighted_edits.highlights.len(), 3);
19732            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19733            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19734            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19735            for highlight in &highlighted_edits.highlights {
19736                assert_eq!(
19737                    highlight.1.background_color,
19738                    Some(cx.theme().status().created_background)
19739                );
19740            }
19741        },
19742    )
19743    .await;
19744}
19745
19746#[gpui::test]
19747async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19748    init_test(cx, |_| {});
19749
19750    // Deletion
19751    assert_highlighted_edits(
19752        "Hello, world!",
19753        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19754        true,
19755        cx,
19756        |highlighted_edits, cx| {
19757            assert_eq!(highlighted_edits.text, "Hello, world!");
19758            assert_eq!(highlighted_edits.highlights.len(), 1);
19759            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19760            assert_eq!(
19761                highlighted_edits.highlights[0].1.background_color,
19762                Some(cx.theme().status().deleted_background)
19763            );
19764        },
19765    )
19766    .await;
19767
19768    // Insertion
19769    assert_highlighted_edits(
19770        "Hello, world!",
19771        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19772        true,
19773        cx,
19774        |highlighted_edits, cx| {
19775            assert_eq!(highlighted_edits.highlights.len(), 1);
19776            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19777            assert_eq!(
19778                highlighted_edits.highlights[0].1.background_color,
19779                Some(cx.theme().status().created_background)
19780            );
19781        },
19782    )
19783    .await;
19784}
19785
19786async fn assert_highlighted_edits(
19787    text: &str,
19788    edits: Vec<(Range<Point>, String)>,
19789    include_deletions: bool,
19790    cx: &mut TestAppContext,
19791    assertion_fn: impl Fn(HighlightedText, &App),
19792) {
19793    let window = cx.add_window(|window, cx| {
19794        let buffer = MultiBuffer::build_simple(text, cx);
19795        Editor::new(EditorMode::full(), buffer, None, window, cx)
19796    });
19797    let cx = &mut VisualTestContext::from_window(*window, cx);
19798
19799    let (buffer, snapshot) = window
19800        .update(cx, |editor, _window, cx| {
19801            (
19802                editor.buffer().clone(),
19803                editor.buffer().read(cx).snapshot(cx),
19804            )
19805        })
19806        .unwrap();
19807
19808    let edits = edits
19809        .into_iter()
19810        .map(|(range, edit)| {
19811            (
19812                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19813                edit,
19814            )
19815        })
19816        .collect::<Vec<_>>();
19817
19818    let text_anchor_edits = edits
19819        .clone()
19820        .into_iter()
19821        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19822        .collect::<Vec<_>>();
19823
19824    let edit_preview = window
19825        .update(cx, |_, _window, cx| {
19826            buffer
19827                .read(cx)
19828                .as_singleton()
19829                .unwrap()
19830                .read(cx)
19831                .preview_edits(text_anchor_edits.into(), cx)
19832        })
19833        .unwrap()
19834        .await;
19835
19836    cx.update(|_window, cx| {
19837        let highlighted_edits = inline_completion_edit_text(
19838            &snapshot.as_singleton().unwrap().2,
19839            &edits,
19840            &edit_preview,
19841            include_deletions,
19842            cx,
19843        );
19844        assertion_fn(highlighted_edits, cx)
19845    });
19846}
19847
19848#[track_caller]
19849fn assert_breakpoint(
19850    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19851    path: &Arc<Path>,
19852    expected: Vec<(u32, Breakpoint)>,
19853) {
19854    if expected.len() == 0usize {
19855        assert!(!breakpoints.contains_key(path), "{}", path.display());
19856    } else {
19857        let mut breakpoint = breakpoints
19858            .get(path)
19859            .unwrap()
19860            .into_iter()
19861            .map(|breakpoint| {
19862                (
19863                    breakpoint.row,
19864                    Breakpoint {
19865                        message: breakpoint.message.clone(),
19866                        state: breakpoint.state,
19867                        condition: breakpoint.condition.clone(),
19868                        hit_condition: breakpoint.hit_condition.clone(),
19869                    },
19870                )
19871            })
19872            .collect::<Vec<_>>();
19873
19874        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19875
19876        assert_eq!(expected, breakpoint);
19877    }
19878}
19879
19880fn add_log_breakpoint_at_cursor(
19881    editor: &mut Editor,
19882    log_message: &str,
19883    window: &mut Window,
19884    cx: &mut Context<Editor>,
19885) {
19886    let (anchor, bp) = editor
19887        .breakpoints_at_cursors(window, cx)
19888        .first()
19889        .and_then(|(anchor, bp)| {
19890            if let Some(bp) = bp {
19891                Some((*anchor, bp.clone()))
19892            } else {
19893                None
19894            }
19895        })
19896        .unwrap_or_else(|| {
19897            let cursor_position: Point = editor.selections.newest(cx).head();
19898
19899            let breakpoint_position = editor
19900                .snapshot(window, cx)
19901                .display_snapshot
19902                .buffer_snapshot
19903                .anchor_before(Point::new(cursor_position.row, 0));
19904
19905            (breakpoint_position, Breakpoint::new_log(&log_message))
19906        });
19907
19908    editor.edit_breakpoint_at_anchor(
19909        anchor,
19910        bp,
19911        BreakpointEditAction::EditLogMessage(log_message.into()),
19912        cx,
19913    );
19914}
19915
19916#[gpui::test]
19917async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19918    init_test(cx, |_| {});
19919
19920    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19921    let fs = FakeFs::new(cx.executor());
19922    fs.insert_tree(
19923        path!("/a"),
19924        json!({
19925            "main.rs": sample_text,
19926        }),
19927    )
19928    .await;
19929    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19930    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19931    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19932
19933    let fs = FakeFs::new(cx.executor());
19934    fs.insert_tree(
19935        path!("/a"),
19936        json!({
19937            "main.rs": sample_text,
19938        }),
19939    )
19940    .await;
19941    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19942    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19943    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19944    let worktree_id = workspace
19945        .update(cx, |workspace, _window, cx| {
19946            workspace.project().update(cx, |project, cx| {
19947                project.worktrees(cx).next().unwrap().read(cx).id()
19948            })
19949        })
19950        .unwrap();
19951
19952    let buffer = project
19953        .update(cx, |project, cx| {
19954            project.open_buffer((worktree_id, "main.rs"), cx)
19955        })
19956        .await
19957        .unwrap();
19958
19959    let (editor, cx) = cx.add_window_view(|window, cx| {
19960        Editor::new(
19961            EditorMode::full(),
19962            MultiBuffer::build_from_buffer(buffer, cx),
19963            Some(project.clone()),
19964            window,
19965            cx,
19966        )
19967    });
19968
19969    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19970    let abs_path = project.read_with(cx, |project, cx| {
19971        project
19972            .absolute_path(&project_path, cx)
19973            .map(|path_buf| Arc::from(path_buf.to_owned()))
19974            .unwrap()
19975    });
19976
19977    // assert we can add breakpoint on the first line
19978    editor.update_in(cx, |editor, window, cx| {
19979        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19980        editor.move_to_end(&MoveToEnd, window, cx);
19981        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19982    });
19983
19984    let breakpoints = editor.update(cx, |editor, cx| {
19985        editor
19986            .breakpoint_store()
19987            .as_ref()
19988            .unwrap()
19989            .read(cx)
19990            .all_source_breakpoints(cx)
19991            .clone()
19992    });
19993
19994    assert_eq!(1, breakpoints.len());
19995    assert_breakpoint(
19996        &breakpoints,
19997        &abs_path,
19998        vec![
19999            (0, Breakpoint::new_standard()),
20000            (3, Breakpoint::new_standard()),
20001        ],
20002    );
20003
20004    editor.update_in(cx, |editor, window, cx| {
20005        editor.move_to_beginning(&MoveToBeginning, window, cx);
20006        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20007    });
20008
20009    let breakpoints = editor.update(cx, |editor, cx| {
20010        editor
20011            .breakpoint_store()
20012            .as_ref()
20013            .unwrap()
20014            .read(cx)
20015            .all_source_breakpoints(cx)
20016            .clone()
20017    });
20018
20019    assert_eq!(1, breakpoints.len());
20020    assert_breakpoint(
20021        &breakpoints,
20022        &abs_path,
20023        vec![(3, Breakpoint::new_standard())],
20024    );
20025
20026    editor.update_in(cx, |editor, window, cx| {
20027        editor.move_to_end(&MoveToEnd, window, cx);
20028        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20029    });
20030
20031    let breakpoints = editor.update(cx, |editor, cx| {
20032        editor
20033            .breakpoint_store()
20034            .as_ref()
20035            .unwrap()
20036            .read(cx)
20037            .all_source_breakpoints(cx)
20038            .clone()
20039    });
20040
20041    assert_eq!(0, breakpoints.len());
20042    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20043}
20044
20045#[gpui::test]
20046async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20047    init_test(cx, |_| {});
20048
20049    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20050
20051    let fs = FakeFs::new(cx.executor());
20052    fs.insert_tree(
20053        path!("/a"),
20054        json!({
20055            "main.rs": sample_text,
20056        }),
20057    )
20058    .await;
20059    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20060    let (workspace, cx) =
20061        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20062
20063    let worktree_id = workspace.update(cx, |workspace, cx| {
20064        workspace.project().update(cx, |project, cx| {
20065            project.worktrees(cx).next().unwrap().read(cx).id()
20066        })
20067    });
20068
20069    let buffer = project
20070        .update(cx, |project, cx| {
20071            project.open_buffer((worktree_id, "main.rs"), cx)
20072        })
20073        .await
20074        .unwrap();
20075
20076    let (editor, cx) = cx.add_window_view(|window, cx| {
20077        Editor::new(
20078            EditorMode::full(),
20079            MultiBuffer::build_from_buffer(buffer, cx),
20080            Some(project.clone()),
20081            window,
20082            cx,
20083        )
20084    });
20085
20086    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20087    let abs_path = project.read_with(cx, |project, cx| {
20088        project
20089            .absolute_path(&project_path, cx)
20090            .map(|path_buf| Arc::from(path_buf.to_owned()))
20091            .unwrap()
20092    });
20093
20094    editor.update_in(cx, |editor, window, cx| {
20095        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20096    });
20097
20098    let breakpoints = editor.update(cx, |editor, cx| {
20099        editor
20100            .breakpoint_store()
20101            .as_ref()
20102            .unwrap()
20103            .read(cx)
20104            .all_source_breakpoints(cx)
20105            .clone()
20106    });
20107
20108    assert_breakpoint(
20109        &breakpoints,
20110        &abs_path,
20111        vec![(0, Breakpoint::new_log("hello world"))],
20112    );
20113
20114    // Removing a log message from a log breakpoint should remove it
20115    editor.update_in(cx, |editor, window, cx| {
20116        add_log_breakpoint_at_cursor(editor, "", window, cx);
20117    });
20118
20119    let breakpoints = editor.update(cx, |editor, cx| {
20120        editor
20121            .breakpoint_store()
20122            .as_ref()
20123            .unwrap()
20124            .read(cx)
20125            .all_source_breakpoints(cx)
20126            .clone()
20127    });
20128
20129    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20130
20131    editor.update_in(cx, |editor, window, cx| {
20132        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20133        editor.move_to_end(&MoveToEnd, window, cx);
20134        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20135        // Not adding a log message to a standard breakpoint shouldn't remove it
20136        add_log_breakpoint_at_cursor(editor, "", window, cx);
20137    });
20138
20139    let breakpoints = editor.update(cx, |editor, cx| {
20140        editor
20141            .breakpoint_store()
20142            .as_ref()
20143            .unwrap()
20144            .read(cx)
20145            .all_source_breakpoints(cx)
20146            .clone()
20147    });
20148
20149    assert_breakpoint(
20150        &breakpoints,
20151        &abs_path,
20152        vec![
20153            (0, Breakpoint::new_standard()),
20154            (3, Breakpoint::new_standard()),
20155        ],
20156    );
20157
20158    editor.update_in(cx, |editor, window, cx| {
20159        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20160    });
20161
20162    let breakpoints = editor.update(cx, |editor, cx| {
20163        editor
20164            .breakpoint_store()
20165            .as_ref()
20166            .unwrap()
20167            .read(cx)
20168            .all_source_breakpoints(cx)
20169            .clone()
20170    });
20171
20172    assert_breakpoint(
20173        &breakpoints,
20174        &abs_path,
20175        vec![
20176            (0, Breakpoint::new_standard()),
20177            (3, Breakpoint::new_log("hello world")),
20178        ],
20179    );
20180
20181    editor.update_in(cx, |editor, window, cx| {
20182        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20183    });
20184
20185    let breakpoints = editor.update(cx, |editor, cx| {
20186        editor
20187            .breakpoint_store()
20188            .as_ref()
20189            .unwrap()
20190            .read(cx)
20191            .all_source_breakpoints(cx)
20192            .clone()
20193    });
20194
20195    assert_breakpoint(
20196        &breakpoints,
20197        &abs_path,
20198        vec![
20199            (0, Breakpoint::new_standard()),
20200            (3, Breakpoint::new_log("hello Earth!!")),
20201        ],
20202    );
20203}
20204
20205/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20206/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20207/// or when breakpoints were placed out of order. This tests for a regression too
20208#[gpui::test]
20209async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20210    init_test(cx, |_| {});
20211
20212    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20213    let fs = FakeFs::new(cx.executor());
20214    fs.insert_tree(
20215        path!("/a"),
20216        json!({
20217            "main.rs": sample_text,
20218        }),
20219    )
20220    .await;
20221    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20222    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20223    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20224
20225    let fs = FakeFs::new(cx.executor());
20226    fs.insert_tree(
20227        path!("/a"),
20228        json!({
20229            "main.rs": sample_text,
20230        }),
20231    )
20232    .await;
20233    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20234    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20235    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20236    let worktree_id = workspace
20237        .update(cx, |workspace, _window, cx| {
20238            workspace.project().update(cx, |project, cx| {
20239                project.worktrees(cx).next().unwrap().read(cx).id()
20240            })
20241        })
20242        .unwrap();
20243
20244    let buffer = project
20245        .update(cx, |project, cx| {
20246            project.open_buffer((worktree_id, "main.rs"), cx)
20247        })
20248        .await
20249        .unwrap();
20250
20251    let (editor, cx) = cx.add_window_view(|window, cx| {
20252        Editor::new(
20253            EditorMode::full(),
20254            MultiBuffer::build_from_buffer(buffer, cx),
20255            Some(project.clone()),
20256            window,
20257            cx,
20258        )
20259    });
20260
20261    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20262    let abs_path = project.read_with(cx, |project, cx| {
20263        project
20264            .absolute_path(&project_path, cx)
20265            .map(|path_buf| Arc::from(path_buf.to_owned()))
20266            .unwrap()
20267    });
20268
20269    // assert we can add breakpoint on the first line
20270    editor.update_in(cx, |editor, window, cx| {
20271        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20272        editor.move_to_end(&MoveToEnd, window, cx);
20273        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20274        editor.move_up(&MoveUp, window, cx);
20275        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20276    });
20277
20278    let breakpoints = editor.update(cx, |editor, cx| {
20279        editor
20280            .breakpoint_store()
20281            .as_ref()
20282            .unwrap()
20283            .read(cx)
20284            .all_source_breakpoints(cx)
20285            .clone()
20286    });
20287
20288    assert_eq!(1, breakpoints.len());
20289    assert_breakpoint(
20290        &breakpoints,
20291        &abs_path,
20292        vec![
20293            (0, Breakpoint::new_standard()),
20294            (2, Breakpoint::new_standard()),
20295            (3, Breakpoint::new_standard()),
20296        ],
20297    );
20298
20299    editor.update_in(cx, |editor, window, cx| {
20300        editor.move_to_beginning(&MoveToBeginning, window, cx);
20301        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20302        editor.move_to_end(&MoveToEnd, window, cx);
20303        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20304        // Disabling a breakpoint that doesn't exist should do nothing
20305        editor.move_up(&MoveUp, window, cx);
20306        editor.move_up(&MoveUp, window, cx);
20307        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20308    });
20309
20310    let breakpoints = editor.update(cx, |editor, cx| {
20311        editor
20312            .breakpoint_store()
20313            .as_ref()
20314            .unwrap()
20315            .read(cx)
20316            .all_source_breakpoints(cx)
20317            .clone()
20318    });
20319
20320    let disable_breakpoint = {
20321        let mut bp = Breakpoint::new_standard();
20322        bp.state = BreakpointState::Disabled;
20323        bp
20324    };
20325
20326    assert_eq!(1, breakpoints.len());
20327    assert_breakpoint(
20328        &breakpoints,
20329        &abs_path,
20330        vec![
20331            (0, disable_breakpoint.clone()),
20332            (2, Breakpoint::new_standard()),
20333            (3, disable_breakpoint.clone()),
20334        ],
20335    );
20336
20337    editor.update_in(cx, |editor, window, cx| {
20338        editor.move_to_beginning(&MoveToBeginning, window, cx);
20339        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20340        editor.move_to_end(&MoveToEnd, window, cx);
20341        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20342        editor.move_up(&MoveUp, window, cx);
20343        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20344    });
20345
20346    let breakpoints = editor.update(cx, |editor, cx| {
20347        editor
20348            .breakpoint_store()
20349            .as_ref()
20350            .unwrap()
20351            .read(cx)
20352            .all_source_breakpoints(cx)
20353            .clone()
20354    });
20355
20356    assert_eq!(1, breakpoints.len());
20357    assert_breakpoint(
20358        &breakpoints,
20359        &abs_path,
20360        vec![
20361            (0, Breakpoint::new_standard()),
20362            (2, disable_breakpoint),
20363            (3, Breakpoint::new_standard()),
20364        ],
20365    );
20366}
20367
20368#[gpui::test]
20369async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20370    init_test(cx, |_| {});
20371    let capabilities = lsp::ServerCapabilities {
20372        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20373            prepare_provider: Some(true),
20374            work_done_progress_options: Default::default(),
20375        })),
20376        ..Default::default()
20377    };
20378    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20379
20380    cx.set_state(indoc! {"
20381        struct Fˇoo {}
20382    "});
20383
20384    cx.update_editor(|editor, _, cx| {
20385        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20386        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20387        editor.highlight_background::<DocumentHighlightRead>(
20388            &[highlight_range],
20389            |theme| theme.colors().editor_document_highlight_read_background,
20390            cx,
20391        );
20392    });
20393
20394    let mut prepare_rename_handler = cx
20395        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20396            move |_, _, _| async move {
20397                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20398                    start: lsp::Position {
20399                        line: 0,
20400                        character: 7,
20401                    },
20402                    end: lsp::Position {
20403                        line: 0,
20404                        character: 10,
20405                    },
20406                })))
20407            },
20408        );
20409    let prepare_rename_task = cx
20410        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20411        .expect("Prepare rename was not started");
20412    prepare_rename_handler.next().await.unwrap();
20413    prepare_rename_task.await.expect("Prepare rename failed");
20414
20415    let mut rename_handler =
20416        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20417            let edit = lsp::TextEdit {
20418                range: lsp::Range {
20419                    start: lsp::Position {
20420                        line: 0,
20421                        character: 7,
20422                    },
20423                    end: lsp::Position {
20424                        line: 0,
20425                        character: 10,
20426                    },
20427                },
20428                new_text: "FooRenamed".to_string(),
20429            };
20430            Ok(Some(lsp::WorkspaceEdit::new(
20431                // Specify the same edit twice
20432                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20433            )))
20434        });
20435    let rename_task = cx
20436        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20437        .expect("Confirm rename was not started");
20438    rename_handler.next().await.unwrap();
20439    rename_task.await.expect("Confirm rename failed");
20440    cx.run_until_parked();
20441
20442    // Despite two edits, only one is actually applied as those are identical
20443    cx.assert_editor_state(indoc! {"
20444        struct FooRenamedˇ {}
20445    "});
20446}
20447
20448#[gpui::test]
20449async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20450    init_test(cx, |_| {});
20451    // These capabilities indicate that the server does not support prepare rename.
20452    let capabilities = lsp::ServerCapabilities {
20453        rename_provider: Some(lsp::OneOf::Left(true)),
20454        ..Default::default()
20455    };
20456    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20457
20458    cx.set_state(indoc! {"
20459        struct Fˇoo {}
20460    "});
20461
20462    cx.update_editor(|editor, _window, cx| {
20463        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20464        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20465        editor.highlight_background::<DocumentHighlightRead>(
20466            &[highlight_range],
20467            |theme| theme.colors().editor_document_highlight_read_background,
20468            cx,
20469        );
20470    });
20471
20472    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20473        .expect("Prepare rename was not started")
20474        .await
20475        .expect("Prepare rename failed");
20476
20477    let mut rename_handler =
20478        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20479            let edit = lsp::TextEdit {
20480                range: lsp::Range {
20481                    start: lsp::Position {
20482                        line: 0,
20483                        character: 7,
20484                    },
20485                    end: lsp::Position {
20486                        line: 0,
20487                        character: 10,
20488                    },
20489                },
20490                new_text: "FooRenamed".to_string(),
20491            };
20492            Ok(Some(lsp::WorkspaceEdit::new(
20493                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20494            )))
20495        });
20496    let rename_task = cx
20497        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20498        .expect("Confirm rename was not started");
20499    rename_handler.next().await.unwrap();
20500    rename_task.await.expect("Confirm rename failed");
20501    cx.run_until_parked();
20502
20503    // Correct range is renamed, as `surrounding_word` is used to find it.
20504    cx.assert_editor_state(indoc! {"
20505        struct FooRenamedˇ {}
20506    "});
20507}
20508
20509#[gpui::test]
20510async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20511    init_test(cx, |_| {});
20512    let mut cx = EditorTestContext::new(cx).await;
20513
20514    let language = Arc::new(
20515        Language::new(
20516            LanguageConfig::default(),
20517            Some(tree_sitter_html::LANGUAGE.into()),
20518        )
20519        .with_brackets_query(
20520            r#"
20521            ("<" @open "/>" @close)
20522            ("</" @open ">" @close)
20523            ("<" @open ">" @close)
20524            ("\"" @open "\"" @close)
20525            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20526        "#,
20527        )
20528        .unwrap(),
20529    );
20530    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20531
20532    cx.set_state(indoc! {"
20533        <span>ˇ</span>
20534    "});
20535    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20536    cx.assert_editor_state(indoc! {"
20537        <span>
20538        ˇ
20539        </span>
20540    "});
20541
20542    cx.set_state(indoc! {"
20543        <span><span></span>ˇ</span>
20544    "});
20545    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20546    cx.assert_editor_state(indoc! {"
20547        <span><span></span>
20548        ˇ</span>
20549    "});
20550
20551    cx.set_state(indoc! {"
20552        <span>ˇ
20553        </span>
20554    "});
20555    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20556    cx.assert_editor_state(indoc! {"
20557        <span>
20558        ˇ
20559        </span>
20560    "});
20561}
20562
20563#[gpui::test(iterations = 10)]
20564async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20565    init_test(cx, |_| {});
20566
20567    let fs = FakeFs::new(cx.executor());
20568    fs.insert_tree(
20569        path!("/dir"),
20570        json!({
20571            "a.ts": "a",
20572        }),
20573    )
20574    .await;
20575
20576    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20577    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20578    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20579
20580    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20581    language_registry.add(Arc::new(Language::new(
20582        LanguageConfig {
20583            name: "TypeScript".into(),
20584            matcher: LanguageMatcher {
20585                path_suffixes: vec!["ts".to_string()],
20586                ..Default::default()
20587            },
20588            ..Default::default()
20589        },
20590        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20591    )));
20592    let mut fake_language_servers = language_registry.register_fake_lsp(
20593        "TypeScript",
20594        FakeLspAdapter {
20595            capabilities: lsp::ServerCapabilities {
20596                code_lens_provider: Some(lsp::CodeLensOptions {
20597                    resolve_provider: Some(true),
20598                }),
20599                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20600                    commands: vec!["_the/command".to_string()],
20601                    ..lsp::ExecuteCommandOptions::default()
20602                }),
20603                ..lsp::ServerCapabilities::default()
20604            },
20605            ..FakeLspAdapter::default()
20606        },
20607    );
20608
20609    let (buffer, _handle) = project
20610        .update(cx, |p, cx| {
20611            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20612        })
20613        .await
20614        .unwrap();
20615    cx.executor().run_until_parked();
20616
20617    let fake_server = fake_language_servers.next().await.unwrap();
20618
20619    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20620    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20621    drop(buffer_snapshot);
20622    let actions = cx
20623        .update_window(*workspace, |_, window, cx| {
20624            project.code_actions(&buffer, anchor..anchor, window, cx)
20625        })
20626        .unwrap();
20627
20628    fake_server
20629        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20630            Ok(Some(vec![
20631                lsp::CodeLens {
20632                    range: lsp::Range::default(),
20633                    command: Some(lsp::Command {
20634                        title: "Code lens command".to_owned(),
20635                        command: "_the/command".to_owned(),
20636                        arguments: None,
20637                    }),
20638                    data: None,
20639                },
20640                lsp::CodeLens {
20641                    range: lsp::Range::default(),
20642                    command: Some(lsp::Command {
20643                        title: "Command not in capabilities".to_owned(),
20644                        command: "not in capabilities".to_owned(),
20645                        arguments: None,
20646                    }),
20647                    data: None,
20648                },
20649                lsp::CodeLens {
20650                    range: lsp::Range {
20651                        start: lsp::Position {
20652                            line: 1,
20653                            character: 1,
20654                        },
20655                        end: lsp::Position {
20656                            line: 1,
20657                            character: 1,
20658                        },
20659                    },
20660                    command: Some(lsp::Command {
20661                        title: "Command not in range".to_owned(),
20662                        command: "_the/command".to_owned(),
20663                        arguments: None,
20664                    }),
20665                    data: None,
20666                },
20667            ]))
20668        })
20669        .next()
20670        .await;
20671
20672    let actions = actions.await.unwrap();
20673    assert_eq!(
20674        actions.len(),
20675        1,
20676        "Should have only one valid action for the 0..0 range"
20677    );
20678    let action = actions[0].clone();
20679    let apply = project.update(cx, |project, cx| {
20680        project.apply_code_action(buffer.clone(), action, true, cx)
20681    });
20682
20683    // Resolving the code action does not populate its edits. In absence of
20684    // edits, we must execute the given command.
20685    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20686        |mut lens, _| async move {
20687            let lens_command = lens.command.as_mut().expect("should have a command");
20688            assert_eq!(lens_command.title, "Code lens command");
20689            lens_command.arguments = Some(vec![json!("the-argument")]);
20690            Ok(lens)
20691        },
20692    );
20693
20694    // While executing the command, the language server sends the editor
20695    // a `workspaceEdit` request.
20696    fake_server
20697        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20698            let fake = fake_server.clone();
20699            move |params, _| {
20700                assert_eq!(params.command, "_the/command");
20701                let fake = fake.clone();
20702                async move {
20703                    fake.server
20704                        .request::<lsp::request::ApplyWorkspaceEdit>(
20705                            lsp::ApplyWorkspaceEditParams {
20706                                label: None,
20707                                edit: lsp::WorkspaceEdit {
20708                                    changes: Some(
20709                                        [(
20710                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20711                                            vec![lsp::TextEdit {
20712                                                range: lsp::Range::new(
20713                                                    lsp::Position::new(0, 0),
20714                                                    lsp::Position::new(0, 0),
20715                                                ),
20716                                                new_text: "X".into(),
20717                                            }],
20718                                        )]
20719                                        .into_iter()
20720                                        .collect(),
20721                                    ),
20722                                    ..Default::default()
20723                                },
20724                            },
20725                        )
20726                        .await
20727                        .into_response()
20728                        .unwrap();
20729                    Ok(Some(json!(null)))
20730                }
20731            }
20732        })
20733        .next()
20734        .await;
20735
20736    // Applying the code lens command returns a project transaction containing the edits
20737    // sent by the language server in its `workspaceEdit` request.
20738    let transaction = apply.await.unwrap();
20739    assert!(transaction.0.contains_key(&buffer));
20740    buffer.update(cx, |buffer, cx| {
20741        assert_eq!(buffer.text(), "Xa");
20742        buffer.undo(cx);
20743        assert_eq!(buffer.text(), "a");
20744    });
20745}
20746
20747#[gpui::test]
20748async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20749    init_test(cx, |_| {});
20750
20751    let fs = FakeFs::new(cx.executor());
20752    let main_text = r#"fn main() {
20753println!("1");
20754println!("2");
20755println!("3");
20756println!("4");
20757println!("5");
20758}"#;
20759    let lib_text = "mod foo {}";
20760    fs.insert_tree(
20761        path!("/a"),
20762        json!({
20763            "lib.rs": lib_text,
20764            "main.rs": main_text,
20765        }),
20766    )
20767    .await;
20768
20769    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20770    let (workspace, cx) =
20771        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20772    let worktree_id = workspace.update(cx, |workspace, cx| {
20773        workspace.project().update(cx, |project, cx| {
20774            project.worktrees(cx).next().unwrap().read(cx).id()
20775        })
20776    });
20777
20778    let expected_ranges = vec![
20779        Point::new(0, 0)..Point::new(0, 0),
20780        Point::new(1, 0)..Point::new(1, 1),
20781        Point::new(2, 0)..Point::new(2, 2),
20782        Point::new(3, 0)..Point::new(3, 3),
20783    ];
20784
20785    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20786    let editor_1 = workspace
20787        .update_in(cx, |workspace, window, cx| {
20788            workspace.open_path(
20789                (worktree_id, "main.rs"),
20790                Some(pane_1.downgrade()),
20791                true,
20792                window,
20793                cx,
20794            )
20795        })
20796        .unwrap()
20797        .await
20798        .downcast::<Editor>()
20799        .unwrap();
20800    pane_1.update(cx, |pane, cx| {
20801        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20802        open_editor.update(cx, |editor, cx| {
20803            assert_eq!(
20804                editor.display_text(cx),
20805                main_text,
20806                "Original main.rs text on initial open",
20807            );
20808            assert_eq!(
20809                editor
20810                    .selections
20811                    .all::<Point>(cx)
20812                    .into_iter()
20813                    .map(|s| s.range())
20814                    .collect::<Vec<_>>(),
20815                vec![Point::zero()..Point::zero()],
20816                "Default selections on initial open",
20817            );
20818        })
20819    });
20820    editor_1.update_in(cx, |editor, window, cx| {
20821        editor.change_selections(None, window, cx, |s| {
20822            s.select_ranges(expected_ranges.clone());
20823        });
20824    });
20825
20826    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20827        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20828    });
20829    let editor_2 = workspace
20830        .update_in(cx, |workspace, window, cx| {
20831            workspace.open_path(
20832                (worktree_id, "main.rs"),
20833                Some(pane_2.downgrade()),
20834                true,
20835                window,
20836                cx,
20837            )
20838        })
20839        .unwrap()
20840        .await
20841        .downcast::<Editor>()
20842        .unwrap();
20843    pane_2.update(cx, |pane, cx| {
20844        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20845        open_editor.update(cx, |editor, cx| {
20846            assert_eq!(
20847                editor.display_text(cx),
20848                main_text,
20849                "Original main.rs text on initial open in another panel",
20850            );
20851            assert_eq!(
20852                editor
20853                    .selections
20854                    .all::<Point>(cx)
20855                    .into_iter()
20856                    .map(|s| s.range())
20857                    .collect::<Vec<_>>(),
20858                vec![Point::zero()..Point::zero()],
20859                "Default selections on initial open in another panel",
20860            );
20861        })
20862    });
20863
20864    editor_2.update_in(cx, |editor, window, cx| {
20865        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20866    });
20867
20868    let _other_editor_1 = workspace
20869        .update_in(cx, |workspace, window, cx| {
20870            workspace.open_path(
20871                (worktree_id, "lib.rs"),
20872                Some(pane_1.downgrade()),
20873                true,
20874                window,
20875                cx,
20876            )
20877        })
20878        .unwrap()
20879        .await
20880        .downcast::<Editor>()
20881        .unwrap();
20882    pane_1
20883        .update_in(cx, |pane, window, cx| {
20884            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20885        })
20886        .await
20887        .unwrap();
20888    drop(editor_1);
20889    pane_1.update(cx, |pane, cx| {
20890        pane.active_item()
20891            .unwrap()
20892            .downcast::<Editor>()
20893            .unwrap()
20894            .update(cx, |editor, cx| {
20895                assert_eq!(
20896                    editor.display_text(cx),
20897                    lib_text,
20898                    "Other file should be open and active",
20899                );
20900            });
20901        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20902    });
20903
20904    let _other_editor_2 = workspace
20905        .update_in(cx, |workspace, window, cx| {
20906            workspace.open_path(
20907                (worktree_id, "lib.rs"),
20908                Some(pane_2.downgrade()),
20909                true,
20910                window,
20911                cx,
20912            )
20913        })
20914        .unwrap()
20915        .await
20916        .downcast::<Editor>()
20917        .unwrap();
20918    pane_2
20919        .update_in(cx, |pane, window, cx| {
20920            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20921        })
20922        .await
20923        .unwrap();
20924    drop(editor_2);
20925    pane_2.update(cx, |pane, cx| {
20926        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20927        open_editor.update(cx, |editor, cx| {
20928            assert_eq!(
20929                editor.display_text(cx),
20930                lib_text,
20931                "Other file should be open and active in another panel too",
20932            );
20933        });
20934        assert_eq!(
20935            pane.items().count(),
20936            1,
20937            "No other editors should be open in another pane",
20938        );
20939    });
20940
20941    let _editor_1_reopened = workspace
20942        .update_in(cx, |workspace, window, cx| {
20943            workspace.open_path(
20944                (worktree_id, "main.rs"),
20945                Some(pane_1.downgrade()),
20946                true,
20947                window,
20948                cx,
20949            )
20950        })
20951        .unwrap()
20952        .await
20953        .downcast::<Editor>()
20954        .unwrap();
20955    let _editor_2_reopened = workspace
20956        .update_in(cx, |workspace, window, cx| {
20957            workspace.open_path(
20958                (worktree_id, "main.rs"),
20959                Some(pane_2.downgrade()),
20960                true,
20961                window,
20962                cx,
20963            )
20964        })
20965        .unwrap()
20966        .await
20967        .downcast::<Editor>()
20968        .unwrap();
20969    pane_1.update(cx, |pane, cx| {
20970        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20971        open_editor.update(cx, |editor, cx| {
20972            assert_eq!(
20973                editor.display_text(cx),
20974                main_text,
20975                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20976            );
20977            assert_eq!(
20978                editor
20979                    .selections
20980                    .all::<Point>(cx)
20981                    .into_iter()
20982                    .map(|s| s.range())
20983                    .collect::<Vec<_>>(),
20984                expected_ranges,
20985                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20986            );
20987        })
20988    });
20989    pane_2.update(cx, |pane, cx| {
20990        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20991        open_editor.update(cx, |editor, cx| {
20992            assert_eq!(
20993                editor.display_text(cx),
20994                r#"fn main() {
20995⋯rintln!("1");
20996⋯intln!("2");
20997⋯ntln!("3");
20998println!("4");
20999println!("5");
21000}"#,
21001                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21002            );
21003            assert_eq!(
21004                editor
21005                    .selections
21006                    .all::<Point>(cx)
21007                    .into_iter()
21008                    .map(|s| s.range())
21009                    .collect::<Vec<_>>(),
21010                vec![Point::zero()..Point::zero()],
21011                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21012            );
21013        })
21014    });
21015}
21016
21017#[gpui::test]
21018async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21019    init_test(cx, |_| {});
21020
21021    let fs = FakeFs::new(cx.executor());
21022    let main_text = r#"fn main() {
21023println!("1");
21024println!("2");
21025println!("3");
21026println!("4");
21027println!("5");
21028}"#;
21029    let lib_text = "mod foo {}";
21030    fs.insert_tree(
21031        path!("/a"),
21032        json!({
21033            "lib.rs": lib_text,
21034            "main.rs": main_text,
21035        }),
21036    )
21037    .await;
21038
21039    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21040    let (workspace, cx) =
21041        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21042    let worktree_id = workspace.update(cx, |workspace, cx| {
21043        workspace.project().update(cx, |project, cx| {
21044            project.worktrees(cx).next().unwrap().read(cx).id()
21045        })
21046    });
21047
21048    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21049    let editor = workspace
21050        .update_in(cx, |workspace, window, cx| {
21051            workspace.open_path(
21052                (worktree_id, "main.rs"),
21053                Some(pane.downgrade()),
21054                true,
21055                window,
21056                cx,
21057            )
21058        })
21059        .unwrap()
21060        .await
21061        .downcast::<Editor>()
21062        .unwrap();
21063    pane.update(cx, |pane, cx| {
21064        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21065        open_editor.update(cx, |editor, cx| {
21066            assert_eq!(
21067                editor.display_text(cx),
21068                main_text,
21069                "Original main.rs text on initial open",
21070            );
21071        })
21072    });
21073    editor.update_in(cx, |editor, window, cx| {
21074        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21075    });
21076
21077    cx.update_global(|store: &mut SettingsStore, cx| {
21078        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21079            s.restore_on_file_reopen = Some(false);
21080        });
21081    });
21082    editor.update_in(cx, |editor, window, cx| {
21083        editor.fold_ranges(
21084            vec![
21085                Point::new(1, 0)..Point::new(1, 1),
21086                Point::new(2, 0)..Point::new(2, 2),
21087                Point::new(3, 0)..Point::new(3, 3),
21088            ],
21089            false,
21090            window,
21091            cx,
21092        );
21093    });
21094    pane.update_in(cx, |pane, window, cx| {
21095        pane.close_all_items(&CloseAllItems::default(), window, cx)
21096    })
21097    .await
21098    .unwrap();
21099    pane.update(cx, |pane, _| {
21100        assert!(pane.active_item().is_none());
21101    });
21102    cx.update_global(|store: &mut SettingsStore, cx| {
21103        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21104            s.restore_on_file_reopen = Some(true);
21105        });
21106    });
21107
21108    let _editor_reopened = workspace
21109        .update_in(cx, |workspace, window, cx| {
21110            workspace.open_path(
21111                (worktree_id, "main.rs"),
21112                Some(pane.downgrade()),
21113                true,
21114                window,
21115                cx,
21116            )
21117        })
21118        .unwrap()
21119        .await
21120        .downcast::<Editor>()
21121        .unwrap();
21122    pane.update(cx, |pane, cx| {
21123        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21124        open_editor.update(cx, |editor, cx| {
21125            assert_eq!(
21126                editor.display_text(cx),
21127                main_text,
21128                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21129            );
21130        })
21131    });
21132}
21133
21134#[gpui::test]
21135async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21136    struct EmptyModalView {
21137        focus_handle: gpui::FocusHandle,
21138    }
21139    impl EventEmitter<DismissEvent> for EmptyModalView {}
21140    impl Render for EmptyModalView {
21141        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21142            div()
21143        }
21144    }
21145    impl Focusable for EmptyModalView {
21146        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21147            self.focus_handle.clone()
21148        }
21149    }
21150    impl workspace::ModalView for EmptyModalView {}
21151    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21152        EmptyModalView {
21153            focus_handle: cx.focus_handle(),
21154        }
21155    }
21156
21157    init_test(cx, |_| {});
21158
21159    let fs = FakeFs::new(cx.executor());
21160    let project = Project::test(fs, [], cx).await;
21161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21162    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21163    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21164    let editor = cx.new_window_entity(|window, cx| {
21165        Editor::new(
21166            EditorMode::full(),
21167            buffer,
21168            Some(project.clone()),
21169            window,
21170            cx,
21171        )
21172    });
21173    workspace
21174        .update(cx, |workspace, window, cx| {
21175            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21176        })
21177        .unwrap();
21178    editor.update_in(cx, |editor, window, cx| {
21179        editor.open_context_menu(&OpenContextMenu, window, cx);
21180        assert!(editor.mouse_context_menu.is_some());
21181    });
21182    workspace
21183        .update(cx, |workspace, window, cx| {
21184            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21185        })
21186        .unwrap();
21187    cx.read(|cx| {
21188        assert!(editor.read(cx).mouse_context_menu.is_none());
21189    });
21190}
21191
21192#[gpui::test]
21193async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21194    init_test(cx, |_| {});
21195
21196    let fs = FakeFs::new(cx.executor());
21197    fs.insert_file(path!("/file.html"), Default::default())
21198        .await;
21199
21200    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21201
21202    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21203    let html_language = Arc::new(Language::new(
21204        LanguageConfig {
21205            name: "HTML".into(),
21206            matcher: LanguageMatcher {
21207                path_suffixes: vec!["html".to_string()],
21208                ..LanguageMatcher::default()
21209            },
21210            brackets: BracketPairConfig {
21211                pairs: vec![BracketPair {
21212                    start: "<".into(),
21213                    end: ">".into(),
21214                    close: true,
21215                    ..Default::default()
21216                }],
21217                ..Default::default()
21218            },
21219            ..Default::default()
21220        },
21221        Some(tree_sitter_html::LANGUAGE.into()),
21222    ));
21223    language_registry.add(html_language);
21224    let mut fake_servers = language_registry.register_fake_lsp(
21225        "HTML",
21226        FakeLspAdapter {
21227            capabilities: lsp::ServerCapabilities {
21228                completion_provider: Some(lsp::CompletionOptions {
21229                    resolve_provider: Some(true),
21230                    ..Default::default()
21231                }),
21232                ..Default::default()
21233            },
21234            ..Default::default()
21235        },
21236    );
21237
21238    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21239    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21240
21241    let worktree_id = workspace
21242        .update(cx, |workspace, _window, cx| {
21243            workspace.project().update(cx, |project, cx| {
21244                project.worktrees(cx).next().unwrap().read(cx).id()
21245            })
21246        })
21247        .unwrap();
21248    project
21249        .update(cx, |project, cx| {
21250            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21251        })
21252        .await
21253        .unwrap();
21254    let editor = workspace
21255        .update(cx, |workspace, window, cx| {
21256            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21257        })
21258        .unwrap()
21259        .await
21260        .unwrap()
21261        .downcast::<Editor>()
21262        .unwrap();
21263
21264    let fake_server = fake_servers.next().await.unwrap();
21265    editor.update_in(cx, |editor, window, cx| {
21266        editor.set_text("<ad></ad>", window, cx);
21267        editor.change_selections(None, window, cx, |selections| {
21268            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21269        });
21270        let Some((buffer, _)) = editor
21271            .buffer
21272            .read(cx)
21273            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21274        else {
21275            panic!("Failed to get buffer for selection position");
21276        };
21277        let buffer = buffer.read(cx);
21278        let buffer_id = buffer.remote_id();
21279        let opening_range =
21280            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21281        let closing_range =
21282            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21283        let mut linked_ranges = HashMap::default();
21284        linked_ranges.insert(
21285            buffer_id,
21286            vec![(opening_range.clone(), vec![closing_range.clone()])],
21287        );
21288        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21289    });
21290    let mut completion_handle =
21291        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21292            Ok(Some(lsp::CompletionResponse::Array(vec![
21293                lsp::CompletionItem {
21294                    label: "head".to_string(),
21295                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21296                        lsp::InsertReplaceEdit {
21297                            new_text: "head".to_string(),
21298                            insert: lsp::Range::new(
21299                                lsp::Position::new(0, 1),
21300                                lsp::Position::new(0, 3),
21301                            ),
21302                            replace: lsp::Range::new(
21303                                lsp::Position::new(0, 1),
21304                                lsp::Position::new(0, 3),
21305                            ),
21306                        },
21307                    )),
21308                    ..Default::default()
21309                },
21310            ])))
21311        });
21312    editor.update_in(cx, |editor, window, cx| {
21313        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21314    });
21315    cx.run_until_parked();
21316    completion_handle.next().await.unwrap();
21317    editor.update(cx, |editor, _| {
21318        assert!(
21319            editor.context_menu_visible(),
21320            "Completion menu should be visible"
21321        );
21322    });
21323    editor.update_in(cx, |editor, window, cx| {
21324        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21325    });
21326    cx.executor().run_until_parked();
21327    editor.update(cx, |editor, cx| {
21328        assert_eq!(editor.text(cx), "<head></head>");
21329    });
21330}
21331
21332#[gpui::test]
21333async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21334    init_test(cx, |_| {});
21335
21336    let fs = FakeFs::new(cx.executor());
21337    fs.insert_tree(
21338        path!("/root"),
21339        json!({
21340            "a": {
21341                "main.rs": "fn main() {}",
21342            },
21343            "foo": {
21344                "bar": {
21345                    "external_file.rs": "pub mod external {}",
21346                }
21347            }
21348        }),
21349    )
21350    .await;
21351
21352    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21353    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21354    language_registry.add(rust_lang());
21355    let _fake_servers = language_registry.register_fake_lsp(
21356        "Rust",
21357        FakeLspAdapter {
21358            ..FakeLspAdapter::default()
21359        },
21360    );
21361    let (workspace, cx) =
21362        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21363    let worktree_id = workspace.update(cx, |workspace, cx| {
21364        workspace.project().update(cx, |project, cx| {
21365            project.worktrees(cx).next().unwrap().read(cx).id()
21366        })
21367    });
21368
21369    let assert_language_servers_count =
21370        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21371            project.update(cx, |project, cx| {
21372                let current = project
21373                    .lsp_store()
21374                    .read(cx)
21375                    .as_local()
21376                    .unwrap()
21377                    .language_servers
21378                    .len();
21379                assert_eq!(expected, current, "{context}");
21380            });
21381        };
21382
21383    assert_language_servers_count(
21384        0,
21385        "No servers should be running before any file is open",
21386        cx,
21387    );
21388    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21389    let main_editor = workspace
21390        .update_in(cx, |workspace, window, cx| {
21391            workspace.open_path(
21392                (worktree_id, "main.rs"),
21393                Some(pane.downgrade()),
21394                true,
21395                window,
21396                cx,
21397            )
21398        })
21399        .unwrap()
21400        .await
21401        .downcast::<Editor>()
21402        .unwrap();
21403    pane.update(cx, |pane, cx| {
21404        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21405        open_editor.update(cx, |editor, cx| {
21406            assert_eq!(
21407                editor.display_text(cx),
21408                "fn main() {}",
21409                "Original main.rs text on initial open",
21410            );
21411        });
21412        assert_eq!(open_editor, main_editor);
21413    });
21414    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21415
21416    let external_editor = workspace
21417        .update_in(cx, |workspace, window, cx| {
21418            workspace.open_abs_path(
21419                PathBuf::from("/root/foo/bar/external_file.rs"),
21420                OpenOptions::default(),
21421                window,
21422                cx,
21423            )
21424        })
21425        .await
21426        .expect("opening external file")
21427        .downcast::<Editor>()
21428        .expect("downcasted external file's open element to editor");
21429    pane.update(cx, |pane, cx| {
21430        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21431        open_editor.update(cx, |editor, cx| {
21432            assert_eq!(
21433                editor.display_text(cx),
21434                "pub mod external {}",
21435                "External file is open now",
21436            );
21437        });
21438        assert_eq!(open_editor, external_editor);
21439    });
21440    assert_language_servers_count(
21441        1,
21442        "Second, external, *.rs file should join the existing server",
21443        cx,
21444    );
21445
21446    pane.update_in(cx, |pane, window, cx| {
21447        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21448    })
21449    .await
21450    .unwrap();
21451    pane.update_in(cx, |pane, window, cx| {
21452        pane.navigate_backward(window, cx);
21453    });
21454    cx.run_until_parked();
21455    pane.update(cx, |pane, cx| {
21456        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21457        open_editor.update(cx, |editor, cx| {
21458            assert_eq!(
21459                editor.display_text(cx),
21460                "pub mod external {}",
21461                "External file is open now",
21462            );
21463        });
21464    });
21465    assert_language_servers_count(
21466        1,
21467        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21468        cx,
21469    );
21470
21471    cx.update(|_, cx| {
21472        workspace::reload(&workspace::Reload::default(), cx);
21473    });
21474    assert_language_servers_count(
21475        1,
21476        "After reloading the worktree with local and external files opened, only one project should be started",
21477        cx,
21478    );
21479}
21480
21481#[gpui::test]
21482async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21483    init_test(cx, |_| {});
21484
21485    let mut cx = EditorTestContext::new(cx).await;
21486    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21488
21489    // test cursor move to start of each line on tab
21490    // for `if`, `elif`, `else`, `while`, `with` and `for`
21491    cx.set_state(indoc! {"
21492        def main():
21493        ˇ    for item in items:
21494        ˇ        while item.active:
21495        ˇ            if item.value > 10:
21496        ˇ                continue
21497        ˇ            elif item.value < 0:
21498        ˇ                break
21499        ˇ            else:
21500        ˇ                with item.context() as ctx:
21501        ˇ                    yield count
21502        ˇ        else:
21503        ˇ            log('while else')
21504        ˇ    else:
21505        ˇ        log('for else')
21506    "});
21507    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21508    cx.assert_editor_state(indoc! {"
21509        def main():
21510            ˇfor item in items:
21511                ˇwhile item.active:
21512                    ˇif item.value > 10:
21513                        ˇcontinue
21514                    ˇelif item.value < 0:
21515                        ˇbreak
21516                    ˇelse:
21517                        ˇwith item.context() as ctx:
21518                            ˇyield count
21519                ˇelse:
21520                    ˇlog('while else')
21521            ˇelse:
21522                ˇlog('for else')
21523    "});
21524    // test relative indent is preserved when tab
21525    // for `if`, `elif`, `else`, `while`, `with` and `for`
21526    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21527    cx.assert_editor_state(indoc! {"
21528        def main():
21529                ˇfor item in items:
21530                    ˇwhile item.active:
21531                        ˇif item.value > 10:
21532                            ˇcontinue
21533                        ˇelif item.value < 0:
21534                            ˇbreak
21535                        ˇelse:
21536                            ˇwith item.context() as ctx:
21537                                ˇyield count
21538                    ˇelse:
21539                        ˇlog('while else')
21540                ˇelse:
21541                    ˇlog('for else')
21542    "});
21543
21544    // test cursor move to start of each line on tab
21545    // for `try`, `except`, `else`, `finally`, `match` and `def`
21546    cx.set_state(indoc! {"
21547        def main():
21548        ˇ    try:
21549        ˇ       fetch()
21550        ˇ    except ValueError:
21551        ˇ       handle_error()
21552        ˇ    else:
21553        ˇ        match value:
21554        ˇ            case _:
21555        ˇ    finally:
21556        ˇ        def status():
21557        ˇ            return 0
21558    "});
21559    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21560    cx.assert_editor_state(indoc! {"
21561        def main():
21562            ˇtry:
21563                ˇfetch()
21564            ˇexcept ValueError:
21565                ˇhandle_error()
21566            ˇelse:
21567                ˇmatch value:
21568                    ˇcase _:
21569            ˇfinally:
21570                ˇdef status():
21571                    ˇreturn 0
21572    "});
21573    // test relative indent is preserved when tab
21574    // for `try`, `except`, `else`, `finally`, `match` and `def`
21575    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21576    cx.assert_editor_state(indoc! {"
21577        def main():
21578                ˇtry:
21579                    ˇfetch()
21580                ˇexcept ValueError:
21581                    ˇhandle_error()
21582                ˇelse:
21583                    ˇmatch value:
21584                        ˇcase _:
21585                ˇfinally:
21586                    ˇdef status():
21587                        ˇreturn 0
21588    "});
21589}
21590
21591#[gpui::test]
21592async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21593    init_test(cx, |_| {});
21594
21595    let mut cx = EditorTestContext::new(cx).await;
21596    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21597    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21598
21599    // test `else` auto outdents when typed inside `if` block
21600    cx.set_state(indoc! {"
21601        def main():
21602            if i == 2:
21603                return
21604                ˇ
21605    "});
21606    cx.update_editor(|editor, window, cx| {
21607        editor.handle_input("else:", window, cx);
21608    });
21609    cx.assert_editor_state(indoc! {"
21610        def main():
21611            if i == 2:
21612                return
21613            else:ˇ
21614    "});
21615
21616    // test `except` auto outdents when typed inside `try` block
21617    cx.set_state(indoc! {"
21618        def main():
21619            try:
21620                i = 2
21621                ˇ
21622    "});
21623    cx.update_editor(|editor, window, cx| {
21624        editor.handle_input("except:", window, cx);
21625    });
21626    cx.assert_editor_state(indoc! {"
21627        def main():
21628            try:
21629                i = 2
21630            except:ˇ
21631    "});
21632
21633    // test `else` auto outdents when typed inside `except` block
21634    cx.set_state(indoc! {"
21635        def main():
21636            try:
21637                i = 2
21638            except:
21639                j = 2
21640                ˇ
21641    "});
21642    cx.update_editor(|editor, window, cx| {
21643        editor.handle_input("else:", window, cx);
21644    });
21645    cx.assert_editor_state(indoc! {"
21646        def main():
21647            try:
21648                i = 2
21649            except:
21650                j = 2
21651            else:ˇ
21652    "});
21653
21654    // test `finally` auto outdents when typed inside `else` block
21655    cx.set_state(indoc! {"
21656        def main():
21657            try:
21658                i = 2
21659            except:
21660                j = 2
21661            else:
21662                k = 2
21663                ˇ
21664    "});
21665    cx.update_editor(|editor, window, cx| {
21666        editor.handle_input("finally:", window, cx);
21667    });
21668    cx.assert_editor_state(indoc! {"
21669        def main():
21670            try:
21671                i = 2
21672            except:
21673                j = 2
21674            else:
21675                k = 2
21676            finally:ˇ
21677    "});
21678
21679    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21680    // cx.set_state(indoc! {"
21681    //     def main():
21682    //         try:
21683    //             for i in range(n):
21684    //                 pass
21685    //             ˇ
21686    // "});
21687    // cx.update_editor(|editor, window, cx| {
21688    //     editor.handle_input("except:", window, cx);
21689    // });
21690    // cx.assert_editor_state(indoc! {"
21691    //     def main():
21692    //         try:
21693    //             for i in range(n):
21694    //                 pass
21695    //         except:ˇ
21696    // "});
21697
21698    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21699    // cx.set_state(indoc! {"
21700    //     def main():
21701    //         try:
21702    //             i = 2
21703    //         except:
21704    //             for i in range(n):
21705    //                 pass
21706    //             ˇ
21707    // "});
21708    // cx.update_editor(|editor, window, cx| {
21709    //     editor.handle_input("else:", window, cx);
21710    // });
21711    // cx.assert_editor_state(indoc! {"
21712    //     def main():
21713    //         try:
21714    //             i = 2
21715    //         except:
21716    //             for i in range(n):
21717    //                 pass
21718    //         else:ˇ
21719    // "});
21720
21721    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21722    // cx.set_state(indoc! {"
21723    //     def main():
21724    //         try:
21725    //             i = 2
21726    //         except:
21727    //             j = 2
21728    //         else:
21729    //             for i in range(n):
21730    //                 pass
21731    //             ˇ
21732    // "});
21733    // cx.update_editor(|editor, window, cx| {
21734    //     editor.handle_input("finally:", window, cx);
21735    // });
21736    // cx.assert_editor_state(indoc! {"
21737    //     def main():
21738    //         try:
21739    //             i = 2
21740    //         except:
21741    //             j = 2
21742    //         else:
21743    //             for i in range(n):
21744    //                 pass
21745    //         finally:ˇ
21746    // "});
21747
21748    // test `else` stays at correct indent when typed after `for` block
21749    cx.set_state(indoc! {"
21750        def main():
21751            for i in range(10):
21752                if i == 3:
21753                    break
21754            ˇ
21755    "});
21756    cx.update_editor(|editor, window, cx| {
21757        editor.handle_input("else:", window, cx);
21758    });
21759    cx.assert_editor_state(indoc! {"
21760        def main():
21761            for i in range(10):
21762                if i == 3:
21763                    break
21764            else:ˇ
21765    "});
21766
21767    // test does not outdent on typing after line with square brackets
21768    cx.set_state(indoc! {"
21769        def f() -> list[str]:
21770            ˇ
21771    "});
21772    cx.update_editor(|editor, window, cx| {
21773        editor.handle_input("a", window, cx);
21774    });
21775    cx.assert_editor_state(indoc! {"
21776        def f() -> list[str]:
2177721778    "});
21779}
21780
21781#[gpui::test]
21782async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21783    init_test(cx, |_| {});
21784    update_test_language_settings(cx, |settings| {
21785        settings.defaults.extend_comment_on_newline = Some(false);
21786    });
21787    let mut cx = EditorTestContext::new(cx).await;
21788    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21789    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21790
21791    // test correct indent after newline on comment
21792    cx.set_state(indoc! {"
21793        # COMMENT:ˇ
21794    "});
21795    cx.update_editor(|editor, window, cx| {
21796        editor.newline(&Newline, window, cx);
21797    });
21798    cx.assert_editor_state(indoc! {"
21799        # COMMENT:
21800        ˇ
21801    "});
21802
21803    // test correct indent after newline in brackets
21804    cx.set_state(indoc! {"
21805        {ˇ}
21806    "});
21807    cx.update_editor(|editor, window, cx| {
21808        editor.newline(&Newline, window, cx);
21809    });
21810    cx.run_until_parked();
21811    cx.assert_editor_state(indoc! {"
21812        {
21813            ˇ
21814        }
21815    "});
21816
21817    cx.set_state(indoc! {"
21818        (ˇ)
21819    "});
21820    cx.update_editor(|editor, window, cx| {
21821        editor.newline(&Newline, window, cx);
21822    });
21823    cx.run_until_parked();
21824    cx.assert_editor_state(indoc! {"
21825        (
21826            ˇ
21827        )
21828    "});
21829
21830    // do not indent after empty lists or dictionaries
21831    cx.set_state(indoc! {"
21832        a = []ˇ
21833    "});
21834    cx.update_editor(|editor, window, cx| {
21835        editor.newline(&Newline, window, cx);
21836    });
21837    cx.run_until_parked();
21838    cx.assert_editor_state(indoc! {"
21839        a = []
21840        ˇ
21841    "});
21842}
21843
21844fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21845    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21846    point..point
21847}
21848
21849#[track_caller]
21850fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21851    let (text, ranges) = marked_text_ranges(marked_text, true);
21852    assert_eq!(editor.text(cx), text);
21853    assert_eq!(
21854        editor.selections.ranges(cx),
21855        ranges,
21856        "Assert selections are {}",
21857        marked_text
21858    );
21859}
21860
21861pub fn handle_signature_help_request(
21862    cx: &mut EditorLspTestContext,
21863    mocked_response: lsp::SignatureHelp,
21864) -> impl Future<Output = ()> + use<> {
21865    let mut request =
21866        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21867            let mocked_response = mocked_response.clone();
21868            async move { Ok(Some(mocked_response)) }
21869        });
21870
21871    async move {
21872        request.next().await;
21873    }
21874}
21875
21876#[track_caller]
21877pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21878    cx.update_editor(|editor, _, _| {
21879        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21880            let entries = menu.entries.borrow();
21881            let entries = entries
21882                .iter()
21883                .map(|entry| entry.string.as_str())
21884                .collect::<Vec<_>>();
21885            assert_eq!(entries, expected);
21886        } else {
21887            panic!("Expected completions menu");
21888        }
21889    });
21890}
21891
21892/// Handle completion request passing a marked string specifying where the completion
21893/// should be triggered from using '|' character, what range should be replaced, and what completions
21894/// should be returned using '<' and '>' to delimit the range.
21895///
21896/// Also see `handle_completion_request_with_insert_and_replace`.
21897#[track_caller]
21898pub fn handle_completion_request(
21899    marked_string: &str,
21900    completions: Vec<&'static str>,
21901    is_incomplete: bool,
21902    counter: Arc<AtomicUsize>,
21903    cx: &mut EditorLspTestContext,
21904) -> impl Future<Output = ()> {
21905    let complete_from_marker: TextRangeMarker = '|'.into();
21906    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21907    let (_, mut marked_ranges) = marked_text_ranges_by(
21908        marked_string,
21909        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21910    );
21911
21912    let complete_from_position =
21913        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21914    let replace_range =
21915        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21916
21917    let mut request =
21918        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21919            let completions = completions.clone();
21920            counter.fetch_add(1, atomic::Ordering::Release);
21921            async move {
21922                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21923                assert_eq!(
21924                    params.text_document_position.position,
21925                    complete_from_position
21926                );
21927                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21928                    is_incomplete: is_incomplete,
21929                    item_defaults: None,
21930                    items: completions
21931                        .iter()
21932                        .map(|completion_text| lsp::CompletionItem {
21933                            label: completion_text.to_string(),
21934                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21935                                range: replace_range,
21936                                new_text: completion_text.to_string(),
21937                            })),
21938                            ..Default::default()
21939                        })
21940                        .collect(),
21941                })))
21942            }
21943        });
21944
21945    async move {
21946        request.next().await;
21947    }
21948}
21949
21950/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21951/// given instead, which also contains an `insert` range.
21952///
21953/// This function uses markers to define ranges:
21954/// - `|` marks the cursor position
21955/// - `<>` marks the replace range
21956/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21957pub fn handle_completion_request_with_insert_and_replace(
21958    cx: &mut EditorLspTestContext,
21959    marked_string: &str,
21960    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21961    counter: Arc<AtomicUsize>,
21962) -> impl Future<Output = ()> {
21963    let complete_from_marker: TextRangeMarker = '|'.into();
21964    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21965    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21966
21967    let (_, mut marked_ranges) = marked_text_ranges_by(
21968        marked_string,
21969        vec![
21970            complete_from_marker.clone(),
21971            replace_range_marker.clone(),
21972            insert_range_marker.clone(),
21973        ],
21974    );
21975
21976    let complete_from_position =
21977        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21978    let replace_range =
21979        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21980
21981    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21982        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21983        _ => lsp::Range {
21984            start: replace_range.start,
21985            end: complete_from_position,
21986        },
21987    };
21988
21989    let mut request =
21990        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21991            let completions = completions.clone();
21992            counter.fetch_add(1, atomic::Ordering::Release);
21993            async move {
21994                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21995                assert_eq!(
21996                    params.text_document_position.position, complete_from_position,
21997                    "marker `|` position doesn't match",
21998                );
21999                Ok(Some(lsp::CompletionResponse::Array(
22000                    completions
22001                        .iter()
22002                        .map(|(label, new_text)| lsp::CompletionItem {
22003                            label: label.to_string(),
22004                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22005                                lsp::InsertReplaceEdit {
22006                                    insert: insert_range,
22007                                    replace: replace_range,
22008                                    new_text: new_text.to_string(),
22009                                },
22010                            )),
22011                            ..Default::default()
22012                        })
22013                        .collect(),
22014                )))
22015            }
22016        });
22017
22018    async move {
22019        request.next().await;
22020    }
22021}
22022
22023fn handle_resolve_completion_request(
22024    cx: &mut EditorLspTestContext,
22025    edits: Option<Vec<(&'static str, &'static str)>>,
22026) -> impl Future<Output = ()> {
22027    let edits = edits.map(|edits| {
22028        edits
22029            .iter()
22030            .map(|(marked_string, new_text)| {
22031                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22032                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22033                lsp::TextEdit::new(replace_range, new_text.to_string())
22034            })
22035            .collect::<Vec<_>>()
22036    });
22037
22038    let mut request =
22039        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22040            let edits = edits.clone();
22041            async move {
22042                Ok(lsp::CompletionItem {
22043                    additional_text_edits: edits,
22044                    ..Default::default()
22045                })
22046            }
22047        });
22048
22049    async move {
22050        request.next().await;
22051    }
22052}
22053
22054pub(crate) fn update_test_language_settings(
22055    cx: &mut TestAppContext,
22056    f: impl Fn(&mut AllLanguageSettingsContent),
22057) {
22058    cx.update(|cx| {
22059        SettingsStore::update_global(cx, |store, cx| {
22060            store.update_user_settings::<AllLanguageSettings>(cx, f);
22061        });
22062    });
22063}
22064
22065pub(crate) fn update_test_project_settings(
22066    cx: &mut TestAppContext,
22067    f: impl Fn(&mut ProjectSettings),
22068) {
22069    cx.update(|cx| {
22070        SettingsStore::update_global(cx, |store, cx| {
22071            store.update_user_settings::<ProjectSettings>(cx, f);
22072        });
22073    });
22074}
22075
22076pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22077    cx.update(|cx| {
22078        assets::Assets.load_test_fonts(cx);
22079        let store = SettingsStore::test(cx);
22080        cx.set_global(store);
22081        theme::init(theme::LoadThemes::JustBase, cx);
22082        release_channel::init(SemanticVersion::default(), cx);
22083        client::init_settings(cx);
22084        language::init(cx);
22085        Project::init_settings(cx);
22086        workspace::init_settings(cx);
22087        crate::init(cx);
22088    });
22089
22090    update_test_language_settings(cx, f);
22091}
22092
22093#[track_caller]
22094fn assert_hunk_revert(
22095    not_reverted_text_with_selections: &str,
22096    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22097    expected_reverted_text_with_selections: &str,
22098    base_text: &str,
22099    cx: &mut EditorLspTestContext,
22100) {
22101    cx.set_state(not_reverted_text_with_selections);
22102    cx.set_head_text(base_text);
22103    cx.executor().run_until_parked();
22104
22105    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22106        let snapshot = editor.snapshot(window, cx);
22107        let reverted_hunk_statuses = snapshot
22108            .buffer_snapshot
22109            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22110            .map(|hunk| hunk.status().kind)
22111            .collect::<Vec<_>>();
22112
22113        editor.git_restore(&Default::default(), window, cx);
22114        reverted_hunk_statuses
22115    });
22116    cx.executor().run_until_parked();
22117    cx.assert_editor_state(expected_reverted_text_with_selections);
22118    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22119}
22120
22121#[gpui::test(iterations = 10)]
22122async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22123    init_test(cx, |_| {});
22124
22125    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22126    let counter = diagnostic_requests.clone();
22127
22128    let fs = FakeFs::new(cx.executor());
22129    fs.insert_tree(
22130        path!("/a"),
22131        json!({
22132            "first.rs": "fn main() { let a = 5; }",
22133            "second.rs": "// Test file",
22134        }),
22135    )
22136    .await;
22137
22138    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22139    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22140    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22141
22142    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22143    language_registry.add(rust_lang());
22144    let mut fake_servers = language_registry.register_fake_lsp(
22145        "Rust",
22146        FakeLspAdapter {
22147            capabilities: lsp::ServerCapabilities {
22148                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22149                    lsp::DiagnosticOptions {
22150                        identifier: None,
22151                        inter_file_dependencies: true,
22152                        workspace_diagnostics: true,
22153                        work_done_progress_options: Default::default(),
22154                    },
22155                )),
22156                ..Default::default()
22157            },
22158            ..Default::default()
22159        },
22160    );
22161
22162    let editor = workspace
22163        .update(cx, |workspace, window, cx| {
22164            workspace.open_abs_path(
22165                PathBuf::from(path!("/a/first.rs")),
22166                OpenOptions::default(),
22167                window,
22168                cx,
22169            )
22170        })
22171        .unwrap()
22172        .await
22173        .unwrap()
22174        .downcast::<Editor>()
22175        .unwrap();
22176    let fake_server = fake_servers.next().await.unwrap();
22177    let server_id = fake_server.server.server_id();
22178    let mut first_request = fake_server
22179        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22180            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22181            let result_id = Some(new_result_id.to_string());
22182            assert_eq!(
22183                params.text_document.uri,
22184                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22185            );
22186            async move {
22187                Ok(lsp::DocumentDiagnosticReportResult::Report(
22188                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22189                        related_documents: None,
22190                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22191                            items: Vec::new(),
22192                            result_id,
22193                        },
22194                    }),
22195                ))
22196            }
22197        });
22198
22199    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22200        project.update(cx, |project, cx| {
22201            let buffer_id = editor
22202                .read(cx)
22203                .buffer()
22204                .read(cx)
22205                .as_singleton()
22206                .expect("created a singleton buffer")
22207                .read(cx)
22208                .remote_id();
22209            let buffer_result_id = project
22210                .lsp_store()
22211                .read(cx)
22212                .result_id(server_id, buffer_id, cx);
22213            assert_eq!(expected, buffer_result_id);
22214        });
22215    };
22216
22217    ensure_result_id(None, cx);
22218    cx.executor().advance_clock(Duration::from_millis(60));
22219    cx.executor().run_until_parked();
22220    assert_eq!(
22221        diagnostic_requests.load(atomic::Ordering::Acquire),
22222        1,
22223        "Opening file should trigger diagnostic request"
22224    );
22225    first_request
22226        .next()
22227        .await
22228        .expect("should have sent the first diagnostics pull request");
22229    ensure_result_id(Some("1".to_string()), cx);
22230
22231    // Editing should trigger diagnostics
22232    editor.update_in(cx, |editor, window, cx| {
22233        editor.handle_input("2", window, cx)
22234    });
22235    cx.executor().advance_clock(Duration::from_millis(60));
22236    cx.executor().run_until_parked();
22237    assert_eq!(
22238        diagnostic_requests.load(atomic::Ordering::Acquire),
22239        2,
22240        "Editing should trigger diagnostic request"
22241    );
22242    ensure_result_id(Some("2".to_string()), cx);
22243
22244    // Moving cursor should not trigger diagnostic request
22245    editor.update_in(cx, |editor, window, cx| {
22246        editor.change_selections(None, window, cx, |s| {
22247            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22248        });
22249    });
22250    cx.executor().advance_clock(Duration::from_millis(60));
22251    cx.executor().run_until_parked();
22252    assert_eq!(
22253        diagnostic_requests.load(atomic::Ordering::Acquire),
22254        2,
22255        "Cursor movement should not trigger diagnostic request"
22256    );
22257    ensure_result_id(Some("2".to_string()), cx);
22258    // Multiple rapid edits should be debounced
22259    for _ in 0..5 {
22260        editor.update_in(cx, |editor, window, cx| {
22261            editor.handle_input("x", window, cx)
22262        });
22263    }
22264    cx.executor().advance_clock(Duration::from_millis(60));
22265    cx.executor().run_until_parked();
22266
22267    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22268    assert!(
22269        final_requests <= 4,
22270        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22271    );
22272    ensure_result_id(Some(final_requests.to_string()), cx);
22273}
22274
22275#[gpui::test]
22276async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22277    // Regression test for issue #11671
22278    // Previously, adding a cursor after moving multiple cursors would reset
22279    // the cursor count instead of adding to the existing cursors.
22280    init_test(cx, |_| {});
22281    let mut cx = EditorTestContext::new(cx).await;
22282
22283    // Create a simple buffer with cursor at start
22284    cx.set_state(indoc! {"
22285        ˇaaaa
22286        bbbb
22287        cccc
22288        dddd
22289        eeee
22290        ffff
22291        gggg
22292        hhhh"});
22293
22294    // Add 2 cursors below (so we have 3 total)
22295    cx.update_editor(|editor, window, cx| {
22296        editor.add_selection_below(&Default::default(), window, cx);
22297        editor.add_selection_below(&Default::default(), window, cx);
22298    });
22299
22300    // Verify we have 3 cursors
22301    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22302    assert_eq!(
22303        initial_count, 3,
22304        "Should have 3 cursors after adding 2 below"
22305    );
22306
22307    // Move down one line
22308    cx.update_editor(|editor, window, cx| {
22309        editor.move_down(&MoveDown, window, cx);
22310    });
22311
22312    // Add another cursor below
22313    cx.update_editor(|editor, window, cx| {
22314        editor.add_selection_below(&Default::default(), window, cx);
22315    });
22316
22317    // Should now have 4 cursors (3 original + 1 new)
22318    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22319    assert_eq!(
22320        final_count, 4,
22321        "Should have 4 cursors after moving and adding another"
22322    );
22323}
22324
22325#[gpui::test]
22326async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
22327    let expected_color = Rgba {
22328        r: 0.33,
22329        g: 0.33,
22330        b: 0.33,
22331        a: 0.33,
22332    };
22333
22334    init_test(cx, |_| {});
22335
22336    let fs = FakeFs::new(cx.executor());
22337    fs.insert_tree(
22338        path!("/a"),
22339        json!({
22340            "first.rs": "fn main() { let a = 5; }",
22341        }),
22342    )
22343    .await;
22344
22345    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22346    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22347    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22348
22349    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22350    language_registry.add(rust_lang());
22351    let mut fake_servers = language_registry.register_fake_lsp(
22352        "Rust",
22353        FakeLspAdapter {
22354            capabilities: lsp::ServerCapabilities {
22355                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22356                ..lsp::ServerCapabilities::default()
22357            },
22358            ..FakeLspAdapter::default()
22359        },
22360    );
22361
22362    let editor = workspace
22363        .update(cx, |workspace, window, cx| {
22364            workspace.open_abs_path(
22365                PathBuf::from(path!("/a/first.rs")),
22366                OpenOptions::default(),
22367                window,
22368                cx,
22369            )
22370        })
22371        .unwrap()
22372        .await
22373        .unwrap()
22374        .downcast::<Editor>()
22375        .unwrap();
22376    let fake_language_server = fake_servers.next().await.unwrap();
22377    let requests_made = Arc::new(AtomicUsize::new(0));
22378    let closure_requests_made = Arc::clone(&requests_made);
22379    let mut color_request_handle = fake_language_server
22380        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22381            let requests_made = Arc::clone(&closure_requests_made);
22382            async move {
22383                assert_eq!(
22384                    params.text_document.uri,
22385                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22386                );
22387                requests_made.fetch_add(1, atomic::Ordering::Release);
22388                Ok(vec![lsp::ColorInformation {
22389                    range: lsp::Range {
22390                        start: lsp::Position {
22391                            line: 0,
22392                            character: 0,
22393                        },
22394                        end: lsp::Position {
22395                            line: 0,
22396                            character: 1,
22397                        },
22398                    },
22399                    color: lsp::Color {
22400                        red: 0.33,
22401                        green: 0.33,
22402                        blue: 0.33,
22403                        alpha: 0.33,
22404                    },
22405                }])
22406            }
22407        });
22408    color_request_handle.next().await.unwrap();
22409    cx.run_until_parked();
22410    color_request_handle.next().await.unwrap();
22411    cx.run_until_parked();
22412    assert_eq!(
22413        2,
22414        requests_made.load(atomic::Ordering::Acquire),
22415        "Should query for colors once per editor open and once after the language server startup"
22416    );
22417
22418    cx.executor().advance_clock(Duration::from_millis(500));
22419    let save = editor.update_in(cx, |editor, window, cx| {
22420        assert_eq!(
22421            vec![expected_color],
22422            extract_color_inlays(editor, cx),
22423            "Should have an initial inlay"
22424        );
22425
22426        editor.move_to_end(&MoveToEnd, window, cx);
22427        editor.handle_input("dirty", window, cx);
22428        editor.save(
22429            SaveOptions {
22430                format: true,
22431                autosave: true,
22432            },
22433            project.clone(),
22434            window,
22435            cx,
22436        )
22437    });
22438    save.await.unwrap();
22439
22440    color_request_handle.next().await.unwrap();
22441    cx.run_until_parked();
22442    color_request_handle.next().await.unwrap();
22443    cx.run_until_parked();
22444    assert_eq!(
22445        4,
22446        requests_made.load(atomic::Ordering::Acquire),
22447        "Should query for colors once per save and once per formatting after save"
22448    );
22449
22450    drop(editor);
22451    let close = workspace
22452        .update(cx, |workspace, window, cx| {
22453            workspace.active_pane().update(cx, |pane, cx| {
22454                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22455            })
22456        })
22457        .unwrap();
22458    close.await.unwrap();
22459    assert_eq!(
22460        4,
22461        requests_made.load(atomic::Ordering::Acquire),
22462        "After saving and closing the editor, no extra requests should be made"
22463    );
22464
22465    workspace
22466        .update(cx, |workspace, window, cx| {
22467            workspace.active_pane().update(cx, |pane, cx| {
22468                pane.navigate_backward(window, cx);
22469            })
22470        })
22471        .unwrap();
22472    color_request_handle.next().await.unwrap();
22473    cx.run_until_parked();
22474    assert_eq!(
22475        5,
22476        requests_made.load(atomic::Ordering::Acquire),
22477        "After navigating back to an editor and reopening it, another color request should be made"
22478    );
22479    let editor = workspace
22480        .update(cx, |workspace, _, cx| {
22481            workspace
22482                .active_item(cx)
22483                .expect("Should have reopened the editor again after navigating back")
22484                .downcast::<Editor>()
22485                .expect("Should be an editor")
22486        })
22487        .unwrap();
22488    editor.update(cx, |editor, cx| {
22489        assert_eq!(
22490            vec![expected_color],
22491            extract_color_inlays(editor, cx),
22492            "Should have an initial inlay"
22493        );
22494    });
22495}
22496
22497#[track_caller]
22498fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22499    editor
22500        .all_inlays(cx)
22501        .into_iter()
22502        .filter_map(|inlay| inlay.get_color())
22503        .map(Rgba::from)
22504        .collect()
22505}