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_immutable_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_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4025    // Since all methods calling manipulate_immutable_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_immutable_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_immutable_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_immutable_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_immutable_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_immutable_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_immutable_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_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4314    init_test(cx, |settings| {
 4315        settings.defaults.tab_size = NonZeroU32::new(3)
 4316    });
 4317
 4318    let mut cx = EditorTestContext::new(cx).await;
 4319
 4320    // MULTI SELECTION
 4321    // Ln.1 "«" tests empty lines
 4322    // Ln.9 tests just leading whitespace
 4323    cx.set_state(indoc! {"
 4324        «
 4325        abc                 // No indentationˇ»
 4326        «\tabc              // 1 tabˇ»
 4327        \t\tabc «      ˇ»   // 2 tabs
 4328        \t ab«c             // Tab followed by space
 4329         \tabc              // Space followed by tab (3 spaces should be the result)
 4330        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4331           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4332        \t
 4333        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4334    "});
 4335    cx.update_editor(|e, window, cx| {
 4336        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4337    });
 4338    cx.assert_editor_state(indoc! {"
 4339        «
 4340        abc                 // No indentation
 4341           abc              // 1 tab
 4342              abc          // 2 tabs
 4343            abc             // Tab followed by space
 4344           abc              // Space followed by tab (3 spaces should be the result)
 4345                       abc   // Mixed indentation (tab conversion depends on the column)
 4346           abc         // Already space indented
 4347           
 4348           abc\tdef          // Only the leading tab is manipulatedˇ»
 4349    "});
 4350
 4351    // Test on just a few lines, the others should remain unchanged
 4352    // Only lines (3, 5, 10, 11) should change
 4353    cx.set_state(indoc! {"
 4354        
 4355        abc                 // No indentation
 4356        \tabcˇ               // 1 tab
 4357        \t\tabc             // 2 tabs
 4358        \t abcˇ              // Tab followed by space
 4359         \tabc              // Space followed by tab (3 spaces should be the result)
 4360        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4361           abc              // Already space indented
 4362        «\t
 4363        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4364    "});
 4365    cx.update_editor(|e, window, cx| {
 4366        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4367    });
 4368    cx.assert_editor_state(indoc! {"
 4369        
 4370        abc                 // No indentation
 4371        «   abc               // 1 tabˇ»
 4372        \t\tabc             // 2 tabs
 4373        «    abc              // Tab followed by spaceˇ»
 4374         \tabc              // Space followed by tab (3 spaces should be the result)
 4375        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4376           abc              // Already space indented
 4377        «   
 4378           abc\tdef          // Only the leading tab is manipulatedˇ»
 4379    "});
 4380
 4381    // SINGLE SELECTION
 4382    // Ln.1 "«" tests empty lines
 4383    // Ln.9 tests just leading whitespace
 4384    cx.set_state(indoc! {"
 4385        «
 4386        abc                 // No indentation
 4387        \tabc               // 1 tab
 4388        \t\tabc             // 2 tabs
 4389        \t abc              // Tab followed by space
 4390         \tabc              // Space followed by tab (3 spaces should be the result)
 4391        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4392           abc              // Already space indented
 4393        \t
 4394        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4395    "});
 4396    cx.update_editor(|e, window, cx| {
 4397        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4398    });
 4399    cx.assert_editor_state(indoc! {"
 4400        «
 4401        abc                 // No indentation
 4402           abc               // 1 tab
 4403              abc             // 2 tabs
 4404            abc              // Tab followed by space
 4405           abc              // Space followed by tab (3 spaces should be the result)
 4406                       abc   // Mixed indentation (tab conversion depends on the column)
 4407           abc              // Already space indented
 4408           
 4409           abc\tdef          // Only the leading tab is manipulatedˇ»
 4410    "});
 4411}
 4412
 4413#[gpui::test]
 4414async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4415    init_test(cx, |settings| {
 4416        settings.defaults.tab_size = NonZeroU32::new(3)
 4417    });
 4418
 4419    let mut cx = EditorTestContext::new(cx).await;
 4420
 4421    // MULTI SELECTION
 4422    // Ln.1 "«" tests empty lines
 4423    // Ln.11 tests just leading whitespace
 4424    cx.set_state(indoc! {"
 4425        «
 4426        abˇ»ˇc                 // No indentation
 4427         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4428          abc  «             // 2 spaces (< 3 so dont convert)
 4429           abc              // 3 spaces (convert)
 4430             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4431        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4432        «\t abc              // Tab followed by space
 4433         \tabc              // Space followed by tab (should be consumed due to tab)
 4434        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4435           \tˇ»  «\t
 4436           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4437    "});
 4438    cx.update_editor(|e, window, cx| {
 4439        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4440    });
 4441    cx.assert_editor_state(indoc! {"
 4442        «
 4443        abc                 // No indentation
 4444         abc                // 1 space (< 3 so dont convert)
 4445          abc               // 2 spaces (< 3 so dont convert)
 4446        \tabc              // 3 spaces (convert)
 4447        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4448        \t\t\tabc           // Already tab indented
 4449        \t abc              // Tab followed by space
 4450        \tabc              // Space followed by tab (should be consumed due to tab)
 4451        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4452        \t\t\t
 4453        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4454    "});
 4455
 4456    // Test on just a few lines, the other should remain unchanged
 4457    // Only lines (4, 8, 11, 12) should change
 4458    cx.set_state(indoc! {"
 4459        
 4460        abc                 // No indentation
 4461         abc                // 1 space (< 3 so dont convert)
 4462          abc               // 2 spaces (< 3 so dont convert)
 4463        «   abc              // 3 spaces (convert)ˇ»
 4464             abc            // 5 spaces (1 tab + 2 spaces)
 4465        \t\t\tabc           // Already tab indented
 4466        \t abc              // Tab followed by space
 4467         \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4468           \t\t  \tabc      // Mixed indentation
 4469        \t \t  \t   \tabc   // Mixed indentation
 4470           \t  \tˇ
 4471        «   abc   \t         // Only the leading spaces should be convertedˇ»
 4472    "});
 4473    cx.update_editor(|e, window, cx| {
 4474        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4475    });
 4476    cx.assert_editor_state(indoc! {"
 4477        
 4478        abc                 // No indentation
 4479         abc                // 1 space (< 3 so dont convert)
 4480          abc               // 2 spaces (< 3 so dont convert)
 4481        «\tabc              // 3 spaces (convert)ˇ»
 4482             abc            // 5 spaces (1 tab + 2 spaces)
 4483        \t\t\tabc           // Already tab indented
 4484        \t abc              // Tab followed by space
 4485        «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4486           \t\t  \tabc      // Mixed indentation
 4487        \t \t  \t   \tabc   // Mixed indentation
 4488        «\t\t\t
 4489        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4490    "});
 4491
 4492    // SINGLE SELECTION
 4493    // Ln.1 "«" tests empty lines
 4494    // Ln.11 tests just leading whitespace
 4495    cx.set_state(indoc! {"
 4496        «
 4497        abc                 // No indentation
 4498         abc                // 1 space (< 3 so dont convert)
 4499          abc               // 2 spaces (< 3 so dont convert)
 4500           abc              // 3 spaces (convert)
 4501             abc            // 5 spaces (1 tab + 2 spaces)
 4502        \t\t\tabc           // Already tab indented
 4503        \t abc              // Tab followed by space
 4504         \tabc              // Space followed by tab (should be consumed due to tab)
 4505        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4506           \t  \t
 4507           abc   \t         // Only the leading spaces should be convertedˇ»
 4508    "});
 4509    cx.update_editor(|e, window, cx| {
 4510        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4511    });
 4512    cx.assert_editor_state(indoc! {"
 4513        «
 4514        abc                 // No indentation
 4515         abc                // 1 space (< 3 so dont convert)
 4516          abc               // 2 spaces (< 3 so dont convert)
 4517        \tabc              // 3 spaces (convert)
 4518        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4519        \t\t\tabc           // Already tab indented
 4520        \t abc              // Tab followed by space
 4521        \tabc              // Space followed by tab (should be consumed due to tab)
 4522        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4523        \t\t\t
 4524        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4525    "});
 4526}
 4527
 4528#[gpui::test]
 4529async fn test_toggle_case(cx: &mut TestAppContext) {
 4530    init_test(cx, |_| {});
 4531
 4532    let mut cx = EditorTestContext::new(cx).await;
 4533
 4534    // If all lower case -> upper case
 4535    cx.set_state(indoc! {"
 4536        «hello worldˇ»
 4537    "});
 4538    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4539    cx.assert_editor_state(indoc! {"
 4540        «HELLO WORLDˇ»
 4541    "});
 4542
 4543    // If all upper case -> lower case
 4544    cx.set_state(indoc! {"
 4545        «HELLO WORLDˇ»
 4546    "});
 4547    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4548    cx.assert_editor_state(indoc! {"
 4549        «hello worldˇ»
 4550    "});
 4551
 4552    // If any upper case characters are identified -> lower case
 4553    // This matches JetBrains IDEs
 4554    cx.set_state(indoc! {"
 4555        «hEllo worldˇ»
 4556    "});
 4557    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4558    cx.assert_editor_state(indoc! {"
 4559        «hello worldˇ»
 4560    "});
 4561}
 4562
 4563#[gpui::test]
 4564async fn test_manipulate_text(cx: &mut TestAppContext) {
 4565    init_test(cx, |_| {});
 4566
 4567    let mut cx = EditorTestContext::new(cx).await;
 4568
 4569    // Test convert_to_upper_case()
 4570    cx.set_state(indoc! {"
 4571        «hello worldˇ»
 4572    "});
 4573    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4574    cx.assert_editor_state(indoc! {"
 4575        «HELLO WORLDˇ»
 4576    "});
 4577
 4578    // Test convert_to_lower_case()
 4579    cx.set_state(indoc! {"
 4580        «HELLO WORLDˇ»
 4581    "});
 4582    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4583    cx.assert_editor_state(indoc! {"
 4584        «hello worldˇ»
 4585    "});
 4586
 4587    // Test multiple line, single selection case
 4588    cx.set_state(indoc! {"
 4589        «The quick brown
 4590        fox jumps over
 4591        the lazy dogˇ»
 4592    "});
 4593    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4594    cx.assert_editor_state(indoc! {"
 4595        «The Quick Brown
 4596        Fox Jumps Over
 4597        The Lazy Dogˇ»
 4598    "});
 4599
 4600    // Test multiple line, single selection case
 4601    cx.set_state(indoc! {"
 4602        «The quick brown
 4603        fox jumps over
 4604        the lazy dogˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| {
 4607        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4608    });
 4609    cx.assert_editor_state(indoc! {"
 4610        «TheQuickBrown
 4611        FoxJumpsOver
 4612        TheLazyDogˇ»
 4613    "});
 4614
 4615    // From here on out, test more complex cases of manipulate_text()
 4616
 4617    // Test no selection case - should affect words cursors are in
 4618    // Cursor at beginning, middle, and end of word
 4619    cx.set_state(indoc! {"
 4620        ˇhello big beauˇtiful worldˇ
 4621    "});
 4622    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4623    cx.assert_editor_state(indoc! {"
 4624        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4625    "});
 4626
 4627    // Test multiple selections on a single line and across multiple lines
 4628    cx.set_state(indoc! {"
 4629        «Theˇ» quick «brown
 4630        foxˇ» jumps «overˇ»
 4631        the «lazyˇ» dog
 4632    "});
 4633    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4634    cx.assert_editor_state(indoc! {"
 4635        «THEˇ» quick «BROWN
 4636        FOXˇ» jumps «OVERˇ»
 4637        the «LAZYˇ» dog
 4638    "});
 4639
 4640    // Test case where text length grows
 4641    cx.set_state(indoc! {"
 4642        «tschüߡ»
 4643    "});
 4644    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4645    cx.assert_editor_state(indoc! {"
 4646        «TSCHÜSSˇ»
 4647    "});
 4648
 4649    // Test to make sure we don't crash when text shrinks
 4650    cx.set_state(indoc! {"
 4651        aaa_bbbˇ
 4652    "});
 4653    cx.update_editor(|e, window, cx| {
 4654        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4655    });
 4656    cx.assert_editor_state(indoc! {"
 4657        «aaaBbbˇ»
 4658    "});
 4659
 4660    // Test to make sure we all aware of the fact that each word can grow and shrink
 4661    // Final selections should be aware of this fact
 4662    cx.set_state(indoc! {"
 4663        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4664    "});
 4665    cx.update_editor(|e, window, cx| {
 4666        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4667    });
 4668    cx.assert_editor_state(indoc! {"
 4669        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4670    "});
 4671
 4672    cx.set_state(indoc! {"
 4673        «hElLo, WoRld!ˇ»
 4674    "});
 4675    cx.update_editor(|e, window, cx| {
 4676        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4677    });
 4678    cx.assert_editor_state(indoc! {"
 4679        «HeLlO, wOrLD!ˇ»
 4680    "});
 4681}
 4682
 4683#[gpui::test]
 4684fn test_duplicate_line(cx: &mut TestAppContext) {
 4685    init_test(cx, |_| {});
 4686
 4687    let editor = cx.add_window(|window, cx| {
 4688        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4689        build_editor(buffer, window, cx)
 4690    });
 4691    _ = editor.update(cx, |editor, window, cx| {
 4692        editor.change_selections(None, window, cx, |s| {
 4693            s.select_display_ranges([
 4694                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4695                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4696                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4697                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4698            ])
 4699        });
 4700        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4701        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4702        assert_eq!(
 4703            editor.selections.display_ranges(cx),
 4704            vec![
 4705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4707                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4708                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4709            ]
 4710        );
 4711    });
 4712
 4713    let editor = cx.add_window(|window, cx| {
 4714        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4715        build_editor(buffer, window, cx)
 4716    });
 4717    _ = editor.update(cx, |editor, window, cx| {
 4718        editor.change_selections(None, window, cx, |s| {
 4719            s.select_display_ranges([
 4720                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4721                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4722            ])
 4723        });
 4724        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4725        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4726        assert_eq!(
 4727            editor.selections.display_ranges(cx),
 4728            vec![
 4729                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4730                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4731            ]
 4732        );
 4733    });
 4734
 4735    // With `move_upwards` the selections stay in place, except for
 4736    // the lines inserted above them
 4737    let editor = cx.add_window(|window, cx| {
 4738        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4739        build_editor(buffer, window, cx)
 4740    });
 4741    _ = editor.update(cx, |editor, window, cx| {
 4742        editor.change_selections(None, window, cx, |s| {
 4743            s.select_display_ranges([
 4744                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4745                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4746                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4747                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4748            ])
 4749        });
 4750        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4751        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4752        assert_eq!(
 4753            editor.selections.display_ranges(cx),
 4754            vec![
 4755                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4756                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4757                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4758                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4759            ]
 4760        );
 4761    });
 4762
 4763    let editor = cx.add_window(|window, cx| {
 4764        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4765        build_editor(buffer, window, cx)
 4766    });
 4767    _ = editor.update(cx, |editor, window, cx| {
 4768        editor.change_selections(None, window, cx, |s| {
 4769            s.select_display_ranges([
 4770                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4771                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4772            ])
 4773        });
 4774        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4775        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4776        assert_eq!(
 4777            editor.selections.display_ranges(cx),
 4778            vec![
 4779                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4780                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4781            ]
 4782        );
 4783    });
 4784
 4785    let editor = cx.add_window(|window, cx| {
 4786        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4787        build_editor(buffer, window, cx)
 4788    });
 4789    _ = editor.update(cx, |editor, window, cx| {
 4790        editor.change_selections(None, window, cx, |s| {
 4791            s.select_display_ranges([
 4792                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4793                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4794            ])
 4795        });
 4796        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4797        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4798        assert_eq!(
 4799            editor.selections.display_ranges(cx),
 4800            vec![
 4801                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4802                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4803            ]
 4804        );
 4805    });
 4806}
 4807
 4808#[gpui::test]
 4809fn test_move_line_up_down(cx: &mut TestAppContext) {
 4810    init_test(cx, |_| {});
 4811
 4812    let editor = cx.add_window(|window, cx| {
 4813        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4814        build_editor(buffer, window, cx)
 4815    });
 4816    _ = editor.update(cx, |editor, window, cx| {
 4817        editor.fold_creases(
 4818            vec![
 4819                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4820                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4821                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4822            ],
 4823            true,
 4824            window,
 4825            cx,
 4826        );
 4827        editor.change_selections(None, window, cx, |s| {
 4828            s.select_display_ranges([
 4829                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4830                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4831                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4832                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4833            ])
 4834        });
 4835        assert_eq!(
 4836            editor.display_text(cx),
 4837            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4838        );
 4839
 4840        editor.move_line_up(&MoveLineUp, window, cx);
 4841        assert_eq!(
 4842            editor.display_text(cx),
 4843            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4844        );
 4845        assert_eq!(
 4846            editor.selections.display_ranges(cx),
 4847            vec![
 4848                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4849                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4850                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4851                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4852            ]
 4853        );
 4854    });
 4855
 4856    _ = editor.update(cx, |editor, window, cx| {
 4857        editor.move_line_down(&MoveLineDown, window, cx);
 4858        assert_eq!(
 4859            editor.display_text(cx),
 4860            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4861        );
 4862        assert_eq!(
 4863            editor.selections.display_ranges(cx),
 4864            vec![
 4865                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4866                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4867                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4868                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4869            ]
 4870        );
 4871    });
 4872
 4873    _ = editor.update(cx, |editor, window, cx| {
 4874        editor.move_line_down(&MoveLineDown, window, cx);
 4875        assert_eq!(
 4876            editor.display_text(cx),
 4877            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4878        );
 4879        assert_eq!(
 4880            editor.selections.display_ranges(cx),
 4881            vec![
 4882                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4883                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4884                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4885                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4886            ]
 4887        );
 4888    });
 4889
 4890    _ = editor.update(cx, |editor, window, cx| {
 4891        editor.move_line_up(&MoveLineUp, window, cx);
 4892        assert_eq!(
 4893            editor.display_text(cx),
 4894            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4895        );
 4896        assert_eq!(
 4897            editor.selections.display_ranges(cx),
 4898            vec![
 4899                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4900                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4901                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4902                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4903            ]
 4904        );
 4905    });
 4906}
 4907
 4908#[gpui::test]
 4909fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4910    init_test(cx, |_| {});
 4911
 4912    let editor = cx.add_window(|window, cx| {
 4913        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4914        build_editor(buffer, window, cx)
 4915    });
 4916    _ = editor.update(cx, |editor, window, cx| {
 4917        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4918        editor.insert_blocks(
 4919            [BlockProperties {
 4920                style: BlockStyle::Fixed,
 4921                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4922                height: Some(1),
 4923                render: Arc::new(|_| div().into_any()),
 4924                priority: 0,
 4925                render_in_minimap: true,
 4926            }],
 4927            Some(Autoscroll::fit()),
 4928            cx,
 4929        );
 4930        editor.change_selections(None, window, cx, |s| {
 4931            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4932        });
 4933        editor.move_line_down(&MoveLineDown, window, cx);
 4934    });
 4935}
 4936
 4937#[gpui::test]
 4938async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4939    init_test(cx, |_| {});
 4940
 4941    let mut cx = EditorTestContext::new(cx).await;
 4942    cx.set_state(
 4943        &"
 4944            ˇzero
 4945            one
 4946            two
 4947            three
 4948            four
 4949            five
 4950        "
 4951        .unindent(),
 4952    );
 4953
 4954    // Create a four-line block that replaces three lines of text.
 4955    cx.update_editor(|editor, window, cx| {
 4956        let snapshot = editor.snapshot(window, cx);
 4957        let snapshot = &snapshot.buffer_snapshot;
 4958        let placement = BlockPlacement::Replace(
 4959            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4960        );
 4961        editor.insert_blocks(
 4962            [BlockProperties {
 4963                placement,
 4964                height: Some(4),
 4965                style: BlockStyle::Sticky,
 4966                render: Arc::new(|_| gpui::div().into_any_element()),
 4967                priority: 0,
 4968                render_in_minimap: true,
 4969            }],
 4970            None,
 4971            cx,
 4972        );
 4973    });
 4974
 4975    // Move down so that the cursor touches the block.
 4976    cx.update_editor(|editor, window, cx| {
 4977        editor.move_down(&Default::default(), window, cx);
 4978    });
 4979    cx.assert_editor_state(
 4980        &"
 4981            zero
 4982            «one
 4983            two
 4984            threeˇ»
 4985            four
 4986            five
 4987        "
 4988        .unindent(),
 4989    );
 4990
 4991    // Move down past the block.
 4992    cx.update_editor(|editor, window, cx| {
 4993        editor.move_down(&Default::default(), window, cx);
 4994    });
 4995    cx.assert_editor_state(
 4996        &"
 4997            zero
 4998            one
 4999            two
 5000            three
 5001            ˇfour
 5002            five
 5003        "
 5004        .unindent(),
 5005    );
 5006}
 5007
 5008#[gpui::test]
 5009fn test_transpose(cx: &mut TestAppContext) {
 5010    init_test(cx, |_| {});
 5011
 5012    _ = cx.add_window(|window, cx| {
 5013        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5014        editor.set_style(EditorStyle::default(), window, cx);
 5015        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 5016        editor.transpose(&Default::default(), window, cx);
 5017        assert_eq!(editor.text(cx), "bac");
 5018        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5019
 5020        editor.transpose(&Default::default(), window, cx);
 5021        assert_eq!(editor.text(cx), "bca");
 5022        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5023
 5024        editor.transpose(&Default::default(), window, cx);
 5025        assert_eq!(editor.text(cx), "bac");
 5026        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5027
 5028        editor
 5029    });
 5030
 5031    _ = cx.add_window(|window, cx| {
 5032        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5033        editor.set_style(EditorStyle::default(), window, cx);
 5034        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 5035        editor.transpose(&Default::default(), window, cx);
 5036        assert_eq!(editor.text(cx), "acb\nde");
 5037        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5038
 5039        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 5040        editor.transpose(&Default::default(), window, cx);
 5041        assert_eq!(editor.text(cx), "acbd\ne");
 5042        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5043
 5044        editor.transpose(&Default::default(), window, cx);
 5045        assert_eq!(editor.text(cx), "acbde\n");
 5046        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5047
 5048        editor.transpose(&Default::default(), window, cx);
 5049        assert_eq!(editor.text(cx), "acbd\ne");
 5050        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5051
 5052        editor
 5053    });
 5054
 5055    _ = cx.add_window(|window, cx| {
 5056        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5057        editor.set_style(EditorStyle::default(), window, cx);
 5058        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 5059        editor.transpose(&Default::default(), window, cx);
 5060        assert_eq!(editor.text(cx), "bacd\ne");
 5061        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5062
 5063        editor.transpose(&Default::default(), window, cx);
 5064        assert_eq!(editor.text(cx), "bcade\n");
 5065        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5066
 5067        editor.transpose(&Default::default(), window, cx);
 5068        assert_eq!(editor.text(cx), "bcda\ne");
 5069        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5070
 5071        editor.transpose(&Default::default(), window, cx);
 5072        assert_eq!(editor.text(cx), "bcade\n");
 5073        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5074
 5075        editor.transpose(&Default::default(), window, cx);
 5076        assert_eq!(editor.text(cx), "bcaed\n");
 5077        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5078
 5079        editor
 5080    });
 5081
 5082    _ = cx.add_window(|window, cx| {
 5083        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5084        editor.set_style(EditorStyle::default(), window, cx);
 5085        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 5086        editor.transpose(&Default::default(), window, cx);
 5087        assert_eq!(editor.text(cx), "🏀🍐✋");
 5088        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5089
 5090        editor.transpose(&Default::default(), window, cx);
 5091        assert_eq!(editor.text(cx), "🏀✋🍐");
 5092        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5093
 5094        editor.transpose(&Default::default(), window, cx);
 5095        assert_eq!(editor.text(cx), "🏀🍐✋");
 5096        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5097
 5098        editor
 5099    });
 5100}
 5101
 5102#[gpui::test]
 5103async fn test_rewrap(cx: &mut TestAppContext) {
 5104    init_test(cx, |settings| {
 5105        settings.languages.extend([
 5106            (
 5107                "Markdown".into(),
 5108                LanguageSettingsContent {
 5109                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5110                    ..Default::default()
 5111                },
 5112            ),
 5113            (
 5114                "Plain Text".into(),
 5115                LanguageSettingsContent {
 5116                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5117                    ..Default::default()
 5118                },
 5119            ),
 5120        ])
 5121    });
 5122
 5123    let mut cx = EditorTestContext::new(cx).await;
 5124
 5125    let language_with_c_comments = Arc::new(Language::new(
 5126        LanguageConfig {
 5127            line_comments: vec!["// ".into()],
 5128            ..LanguageConfig::default()
 5129        },
 5130        None,
 5131    ));
 5132    let language_with_pound_comments = Arc::new(Language::new(
 5133        LanguageConfig {
 5134            line_comments: vec!["# ".into()],
 5135            ..LanguageConfig::default()
 5136        },
 5137        None,
 5138    ));
 5139    let markdown_language = Arc::new(Language::new(
 5140        LanguageConfig {
 5141            name: "Markdown".into(),
 5142            ..LanguageConfig::default()
 5143        },
 5144        None,
 5145    ));
 5146    let language_with_doc_comments = Arc::new(Language::new(
 5147        LanguageConfig {
 5148            line_comments: vec!["// ".into(), "/// ".into()],
 5149            ..LanguageConfig::default()
 5150        },
 5151        Some(tree_sitter_rust::LANGUAGE.into()),
 5152    ));
 5153
 5154    let plaintext_language = Arc::new(Language::new(
 5155        LanguageConfig {
 5156            name: "Plain Text".into(),
 5157            ..LanguageConfig::default()
 5158        },
 5159        None,
 5160    ));
 5161
 5162    assert_rewrap(
 5163        indoc! {"
 5164            // ˇ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.
 5165        "},
 5166        indoc! {"
 5167            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5168            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5169            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5170            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5171            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5172            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5173            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5174            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5175            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5176            // porttitor id. Aliquam id accumsan eros.
 5177        "},
 5178        language_with_c_comments.clone(),
 5179        &mut cx,
 5180    );
 5181
 5182    // Test that rewrapping works inside of a selection
 5183    assert_rewrap(
 5184        indoc! {"
 5185            «// 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.ˇ»
 5186        "},
 5187        indoc! {"
 5188            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5189            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5190            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5191            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5192            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5193            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5194            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5195            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5196            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5197            // porttitor id. Aliquam id accumsan eros.ˇ»
 5198        "},
 5199        language_with_c_comments.clone(),
 5200        &mut cx,
 5201    );
 5202
 5203    // Test that cursors that expand to the same region are collapsed.
 5204    assert_rewrap(
 5205        indoc! {"
 5206            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5207            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5208            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5209            // ˇ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.
 5210        "},
 5211        indoc! {"
 5212            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5213            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5214            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 5215            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5216            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5217            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5218            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5219            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5220            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5221            // porttitor id. Aliquam id accumsan eros.
 5222        "},
 5223        language_with_c_comments.clone(),
 5224        &mut cx,
 5225    );
 5226
 5227    // Test that non-contiguous selections are treated separately.
 5228    assert_rewrap(
 5229        indoc! {"
 5230            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5231            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5232            //
 5233            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5234            // ˇ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.
 5235        "},
 5236        indoc! {"
 5237            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5238            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5239            // auctor, eu lacinia sapien scelerisque.
 5240            //
 5241            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5242            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5243            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5244            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5245            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5246            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5247            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5248        "},
 5249        language_with_c_comments.clone(),
 5250        &mut cx,
 5251    );
 5252
 5253    // Test that different comment prefixes are supported.
 5254    assert_rewrap(
 5255        indoc! {"
 5256            # ˇ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.
 5257        "},
 5258        indoc! {"
 5259            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5260            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5261            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5262            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5263            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5264            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5265            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5266            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5267            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5268            # accumsan eros.
 5269        "},
 5270        language_with_pound_comments.clone(),
 5271        &mut cx,
 5272    );
 5273
 5274    // Test that rewrapping is ignored outside of comments in most languages.
 5275    assert_rewrap(
 5276        indoc! {"
 5277            /// Adds two numbers.
 5278            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5279            fn add(a: u32, b: u32) -> u32 {
 5280                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ˇ
 5281            }
 5282        "},
 5283        indoc! {"
 5284            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5285            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5286            fn add(a: u32, b: u32) -> u32 {
 5287                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ˇ
 5288            }
 5289        "},
 5290        language_with_doc_comments.clone(),
 5291        &mut cx,
 5292    );
 5293
 5294    // Test that rewrapping works in Markdown and Plain Text languages.
 5295    assert_rewrap(
 5296        indoc! {"
 5297            # Hello
 5298
 5299            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.
 5300        "},
 5301        indoc! {"
 5302            # Hello
 5303
 5304            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5305            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5306            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5307            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5308            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5309            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5310            Integer sit amet scelerisque nisi.
 5311        "},
 5312        markdown_language,
 5313        &mut cx,
 5314    );
 5315
 5316    assert_rewrap(
 5317        indoc! {"
 5318            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.
 5319        "},
 5320        indoc! {"
 5321            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5322            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5323            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5324            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5325            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5326            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5327            Integer sit amet scelerisque nisi.
 5328        "},
 5329        plaintext_language.clone(),
 5330        &mut cx,
 5331    );
 5332
 5333    // Test rewrapping unaligned comments in a selection.
 5334    assert_rewrap(
 5335        indoc! {"
 5336            fn foo() {
 5337                if true {
 5338            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5339            // Praesent semper egestas tellus id dignissim.ˇ»
 5340                    do_something();
 5341                } else {
 5342                    //
 5343                }
 5344            }
 5345        "},
 5346        indoc! {"
 5347            fn foo() {
 5348                if true {
 5349            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5350                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5351                    // egestas tellus id dignissim.ˇ»
 5352                    do_something();
 5353                } else {
 5354                    //
 5355                }
 5356            }
 5357        "},
 5358        language_with_doc_comments.clone(),
 5359        &mut cx,
 5360    );
 5361
 5362    assert_rewrap(
 5363        indoc! {"
 5364            fn foo() {
 5365                if true {
 5366            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5367            // Praesent semper egestas tellus id dignissim.»
 5368                    do_something();
 5369                } else {
 5370                    //
 5371                }
 5372
 5373            }
 5374        "},
 5375        indoc! {"
 5376            fn foo() {
 5377                if true {
 5378            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5379                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5380                    // egestas tellus id dignissim.»
 5381                    do_something();
 5382                } else {
 5383                    //
 5384                }
 5385
 5386            }
 5387        "},
 5388        language_with_doc_comments.clone(),
 5389        &mut cx,
 5390    );
 5391
 5392    assert_rewrap(
 5393        indoc! {"
 5394            «ˇ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
 5395
 5396            two»
 5397
 5398            three
 5399
 5400            «ˇ\t
 5401
 5402            four four four four four four four four four four four four four four four four four four four four»
 5403
 5404            «ˇfive five five five five five five five five five five five five five five five five five five five
 5405            \t»
 5406            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
 5407        "},
 5408        indoc! {"
 5409            «ˇone one one one one one one one one one one one one one one one one one one one
 5410            one one one one one
 5411
 5412            two»
 5413
 5414            three
 5415
 5416            «ˇ\t
 5417
 5418            four four four four four four four four four four four four four four four four
 5419            four four four four»
 5420
 5421            «ˇfive five five five five five five five five five five five five five five five
 5422            five five five five
 5423            \t»
 5424            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
 5425        "},
 5426        plaintext_language.clone(),
 5427        &mut cx,
 5428    );
 5429
 5430    assert_rewrap(
 5431        indoc! {"
 5432            //ˇ 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
 5433            //ˇ
 5434            //ˇ 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
 5435            //ˇ short short short
 5436            int main(void) {
 5437                return 17;
 5438            }
 5439        "},
 5440        indoc! {"
 5441            //ˇ long long long long long long long long long long long long long long long
 5442            // long long long long long long long long long long long long long
 5443            //ˇ
 5444            //ˇ long long long long long long long long long long long long long long long
 5445            //ˇ long long long long long long long long long long long long long short short
 5446            // short
 5447            int main(void) {
 5448                return 17;
 5449            }
 5450        "},
 5451        language_with_c_comments,
 5452        &mut cx,
 5453    );
 5454
 5455    #[track_caller]
 5456    fn assert_rewrap(
 5457        unwrapped_text: &str,
 5458        wrapped_text: &str,
 5459        language: Arc<Language>,
 5460        cx: &mut EditorTestContext,
 5461    ) {
 5462        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5463        cx.set_state(unwrapped_text);
 5464        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5465        cx.assert_editor_state(wrapped_text);
 5466    }
 5467}
 5468
 5469#[gpui::test]
 5470async fn test_hard_wrap(cx: &mut TestAppContext) {
 5471    init_test(cx, |_| {});
 5472    let mut cx = EditorTestContext::new(cx).await;
 5473
 5474    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5475    cx.update_editor(|editor, _, cx| {
 5476        editor.set_hard_wrap(Some(14), cx);
 5477    });
 5478
 5479    cx.set_state(indoc!(
 5480        "
 5481        one two three ˇ
 5482        "
 5483    ));
 5484    cx.simulate_input("four");
 5485    cx.run_until_parked();
 5486
 5487    cx.assert_editor_state(indoc!(
 5488        "
 5489        one two three
 5490        fourˇ
 5491        "
 5492    ));
 5493
 5494    cx.update_editor(|editor, window, cx| {
 5495        editor.newline(&Default::default(), window, cx);
 5496    });
 5497    cx.run_until_parked();
 5498    cx.assert_editor_state(indoc!(
 5499        "
 5500        one two three
 5501        four
 5502        ˇ
 5503        "
 5504    ));
 5505
 5506    cx.simulate_input("five");
 5507    cx.run_until_parked();
 5508    cx.assert_editor_state(indoc!(
 5509        "
 5510        one two three
 5511        four
 5512        fiveˇ
 5513        "
 5514    ));
 5515
 5516    cx.update_editor(|editor, window, cx| {
 5517        editor.newline(&Default::default(), window, cx);
 5518    });
 5519    cx.run_until_parked();
 5520    cx.simulate_input("# ");
 5521    cx.run_until_parked();
 5522    cx.assert_editor_state(indoc!(
 5523        "
 5524        one two three
 5525        four
 5526        five
 5527        # ˇ
 5528        "
 5529    ));
 5530
 5531    cx.update_editor(|editor, window, cx| {
 5532        editor.newline(&Default::default(), window, cx);
 5533    });
 5534    cx.run_until_parked();
 5535    cx.assert_editor_state(indoc!(
 5536        "
 5537        one two three
 5538        four
 5539        five
 5540        #\x20
 5541 5542        "
 5543    ));
 5544
 5545    cx.simulate_input(" 6");
 5546    cx.run_until_parked();
 5547    cx.assert_editor_state(indoc!(
 5548        "
 5549        one two three
 5550        four
 5551        five
 5552        #
 5553        # 6ˇ
 5554        "
 5555    ));
 5556}
 5557
 5558#[gpui::test]
 5559async fn test_clipboard(cx: &mut TestAppContext) {
 5560    init_test(cx, |_| {});
 5561
 5562    let mut cx = EditorTestContext::new(cx).await;
 5563
 5564    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5565    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5566    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5567
 5568    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5569    cx.set_state("two ˇfour ˇsix ˇ");
 5570    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5571    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5572
 5573    // Paste again but with only two cursors. Since the number of cursors doesn't
 5574    // match the number of slices in the clipboard, the entire clipboard text
 5575    // is pasted at each cursor.
 5576    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5577    cx.update_editor(|e, window, cx| {
 5578        e.handle_input("( ", window, cx);
 5579        e.paste(&Paste, window, cx);
 5580        e.handle_input(") ", window, cx);
 5581    });
 5582    cx.assert_editor_state(
 5583        &([
 5584            "( one✅ ",
 5585            "three ",
 5586            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5587            "three ",
 5588            "five ) ˇ",
 5589        ]
 5590        .join("\n")),
 5591    );
 5592
 5593    // Cut with three selections, one of which is full-line.
 5594    cx.set_state(indoc! {"
 5595        1«2ˇ»3
 5596        4ˇ567
 5597        «8ˇ»9"});
 5598    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5599    cx.assert_editor_state(indoc! {"
 5600        1ˇ3
 5601        ˇ9"});
 5602
 5603    // Paste with three selections, noticing how the copied selection that was full-line
 5604    // gets inserted before the second cursor.
 5605    cx.set_state(indoc! {"
 5606        1ˇ3
 5607 5608        «oˇ»ne"});
 5609    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5610    cx.assert_editor_state(indoc! {"
 5611        12ˇ3
 5612        4567
 5613 5614        8ˇne"});
 5615
 5616    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5617    cx.set_state(indoc! {"
 5618        The quick brown
 5619        fox juˇmps over
 5620        the lazy dog"});
 5621    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5622    assert_eq!(
 5623        cx.read_from_clipboard()
 5624            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5625        Some("fox jumps over\n".to_string())
 5626    );
 5627
 5628    // Paste with three selections, noticing how the copied full-line selection is inserted
 5629    // before the empty selections but replaces the selection that is non-empty.
 5630    cx.set_state(indoc! {"
 5631        Tˇhe quick brown
 5632        «foˇ»x jumps over
 5633        tˇhe lazy dog"});
 5634    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5635    cx.assert_editor_state(indoc! {"
 5636        fox jumps over
 5637        Tˇhe quick brown
 5638        fox jumps over
 5639        ˇx jumps over
 5640        fox jumps over
 5641        tˇhe lazy dog"});
 5642}
 5643
 5644#[gpui::test]
 5645async fn test_copy_trim(cx: &mut TestAppContext) {
 5646    init_test(cx, |_| {});
 5647
 5648    let mut cx = EditorTestContext::new(cx).await;
 5649    cx.set_state(
 5650        r#"            «for selection in selections.iter() {
 5651            let mut start = selection.start;
 5652            let mut end = selection.end;
 5653            let is_entire_line = selection.is_empty();
 5654            if is_entire_line {
 5655                start = Point::new(start.row, 0);ˇ»
 5656                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5657            }
 5658        "#,
 5659    );
 5660    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5661    assert_eq!(
 5662        cx.read_from_clipboard()
 5663            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5664        Some(
 5665            "for selection in selections.iter() {
 5666            let mut start = selection.start;
 5667            let mut end = selection.end;
 5668            let is_entire_line = selection.is_empty();
 5669            if is_entire_line {
 5670                start = Point::new(start.row, 0);"
 5671                .to_string()
 5672        ),
 5673        "Regular copying preserves all indentation selected",
 5674    );
 5675    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5676    assert_eq!(
 5677        cx.read_from_clipboard()
 5678            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5679        Some(
 5680            "for selection in selections.iter() {
 5681let mut start = selection.start;
 5682let mut end = selection.end;
 5683let is_entire_line = selection.is_empty();
 5684if is_entire_line {
 5685    start = Point::new(start.row, 0);"
 5686                .to_string()
 5687        ),
 5688        "Copying with stripping should strip all leading whitespaces"
 5689    );
 5690
 5691    cx.set_state(
 5692        r#"       «     for selection in selections.iter() {
 5693            let mut start = selection.start;
 5694            let mut end = selection.end;
 5695            let is_entire_line = selection.is_empty();
 5696            if is_entire_line {
 5697                start = Point::new(start.row, 0);ˇ»
 5698                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5699            }
 5700        "#,
 5701    );
 5702    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5703    assert_eq!(
 5704        cx.read_from_clipboard()
 5705            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5706        Some(
 5707            "     for selection in selections.iter() {
 5708            let mut start = selection.start;
 5709            let mut end = selection.end;
 5710            let is_entire_line = selection.is_empty();
 5711            if is_entire_line {
 5712                start = Point::new(start.row, 0);"
 5713                .to_string()
 5714        ),
 5715        "Regular copying preserves all indentation selected",
 5716    );
 5717    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5718    assert_eq!(
 5719        cx.read_from_clipboard()
 5720            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5721        Some(
 5722            "for selection in selections.iter() {
 5723let mut start = selection.start;
 5724let mut end = selection.end;
 5725let is_entire_line = selection.is_empty();
 5726if is_entire_line {
 5727    start = Point::new(start.row, 0);"
 5728                .to_string()
 5729        ),
 5730        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5731    );
 5732
 5733    cx.set_state(
 5734        r#"       «ˇ     for selection in selections.iter() {
 5735            let mut start = selection.start;
 5736            let mut end = selection.end;
 5737            let is_entire_line = selection.is_empty();
 5738            if is_entire_line {
 5739                start = Point::new(start.row, 0);»
 5740                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5741            }
 5742        "#,
 5743    );
 5744    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5745    assert_eq!(
 5746        cx.read_from_clipboard()
 5747            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5748        Some(
 5749            "     for selection in selections.iter() {
 5750            let mut start = selection.start;
 5751            let mut end = selection.end;
 5752            let is_entire_line = selection.is_empty();
 5753            if is_entire_line {
 5754                start = Point::new(start.row, 0);"
 5755                .to_string()
 5756        ),
 5757        "Regular copying for reverse selection works the same",
 5758    );
 5759    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5760    assert_eq!(
 5761        cx.read_from_clipboard()
 5762            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5763        Some(
 5764            "for selection in selections.iter() {
 5765let mut start = selection.start;
 5766let mut end = selection.end;
 5767let is_entire_line = selection.is_empty();
 5768if is_entire_line {
 5769    start = Point::new(start.row, 0);"
 5770                .to_string()
 5771        ),
 5772        "Copying with stripping for reverse selection works the same"
 5773    );
 5774
 5775    cx.set_state(
 5776        r#"            for selection «in selections.iter() {
 5777            let mut start = selection.start;
 5778            let mut end = selection.end;
 5779            let is_entire_line = selection.is_empty();
 5780            if is_entire_line {
 5781                start = Point::new(start.row, 0);ˇ»
 5782                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5783            }
 5784        "#,
 5785    );
 5786    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5787    assert_eq!(
 5788        cx.read_from_clipboard()
 5789            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5790        Some(
 5791            "in selections.iter() {
 5792            let mut start = selection.start;
 5793            let mut end = selection.end;
 5794            let is_entire_line = selection.is_empty();
 5795            if is_entire_line {
 5796                start = Point::new(start.row, 0);"
 5797                .to_string()
 5798        ),
 5799        "When selecting past the indent, the copying works as usual",
 5800    );
 5801    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5802    assert_eq!(
 5803        cx.read_from_clipboard()
 5804            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5805        Some(
 5806            "in selections.iter() {
 5807            let mut start = selection.start;
 5808            let mut end = selection.end;
 5809            let is_entire_line = selection.is_empty();
 5810            if is_entire_line {
 5811                start = Point::new(start.row, 0);"
 5812                .to_string()
 5813        ),
 5814        "When selecting past the indent, nothing is trimmed"
 5815    );
 5816
 5817    cx.set_state(
 5818        r#"            «for selection in selections.iter() {
 5819            let mut start = selection.start;
 5820
 5821            let mut end = selection.end;
 5822            let is_entire_line = selection.is_empty();
 5823            if is_entire_line {
 5824                start = Point::new(start.row, 0);
 5825ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5826            }
 5827        "#,
 5828    );
 5829    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5830    assert_eq!(
 5831        cx.read_from_clipboard()
 5832            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5833        Some(
 5834            "for selection in selections.iter() {
 5835let mut start = selection.start;
 5836
 5837let mut end = selection.end;
 5838let is_entire_line = selection.is_empty();
 5839if is_entire_line {
 5840    start = Point::new(start.row, 0);
 5841"
 5842            .to_string()
 5843        ),
 5844        "Copying with stripping should ignore empty lines"
 5845    );
 5846}
 5847
 5848#[gpui::test]
 5849async fn test_paste_multiline(cx: &mut TestAppContext) {
 5850    init_test(cx, |_| {});
 5851
 5852    let mut cx = EditorTestContext::new(cx).await;
 5853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5854
 5855    // Cut an indented block, without the leading whitespace.
 5856    cx.set_state(indoc! {"
 5857        const a: B = (
 5858            c(),
 5859            «d(
 5860                e,
 5861                f
 5862            )ˇ»
 5863        );
 5864    "});
 5865    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5866    cx.assert_editor_state(indoc! {"
 5867        const a: B = (
 5868            c(),
 5869            ˇ
 5870        );
 5871    "});
 5872
 5873    // Paste it at the same position.
 5874    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5875    cx.assert_editor_state(indoc! {"
 5876        const a: B = (
 5877            c(),
 5878            d(
 5879                e,
 5880                f
 5881 5882        );
 5883    "});
 5884
 5885    // Paste it at a line with a lower indent level.
 5886    cx.set_state(indoc! {"
 5887        ˇ
 5888        const a: B = (
 5889            c(),
 5890        );
 5891    "});
 5892    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5893    cx.assert_editor_state(indoc! {"
 5894        d(
 5895            e,
 5896            f
 5897 5898        const a: B = (
 5899            c(),
 5900        );
 5901    "});
 5902
 5903    // Cut an indented block, with the leading whitespace.
 5904    cx.set_state(indoc! {"
 5905        const a: B = (
 5906            c(),
 5907        «    d(
 5908                e,
 5909                f
 5910            )
 5911        ˇ»);
 5912    "});
 5913    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5914    cx.assert_editor_state(indoc! {"
 5915        const a: B = (
 5916            c(),
 5917        ˇ);
 5918    "});
 5919
 5920    // Paste it at the same position.
 5921    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5922    cx.assert_editor_state(indoc! {"
 5923        const a: B = (
 5924            c(),
 5925            d(
 5926                e,
 5927                f
 5928            )
 5929        ˇ);
 5930    "});
 5931
 5932    // Paste it at a line with a higher indent level.
 5933    cx.set_state(indoc! {"
 5934        const a: B = (
 5935            c(),
 5936            d(
 5937                e,
 5938 5939            )
 5940        );
 5941    "});
 5942    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5943    cx.assert_editor_state(indoc! {"
 5944        const a: B = (
 5945            c(),
 5946            d(
 5947                e,
 5948                f    d(
 5949                    e,
 5950                    f
 5951                )
 5952        ˇ
 5953            )
 5954        );
 5955    "});
 5956
 5957    // Copy an indented block, starting mid-line
 5958    cx.set_state(indoc! {"
 5959        const a: B = (
 5960            c(),
 5961            somethin«g(
 5962                e,
 5963                f
 5964            )ˇ»
 5965        );
 5966    "});
 5967    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5968
 5969    // Paste it on a line with a lower indent level
 5970    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5971    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5972    cx.assert_editor_state(indoc! {"
 5973        const a: B = (
 5974            c(),
 5975            something(
 5976                e,
 5977                f
 5978            )
 5979        );
 5980        g(
 5981            e,
 5982            f
 5983"});
 5984}
 5985
 5986#[gpui::test]
 5987async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5988    init_test(cx, |_| {});
 5989
 5990    cx.write_to_clipboard(ClipboardItem::new_string(
 5991        "    d(\n        e\n    );\n".into(),
 5992    ));
 5993
 5994    let mut cx = EditorTestContext::new(cx).await;
 5995    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5996
 5997    cx.set_state(indoc! {"
 5998        fn a() {
 5999            b();
 6000            if c() {
 6001                ˇ
 6002            }
 6003        }
 6004    "});
 6005
 6006    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6007    cx.assert_editor_state(indoc! {"
 6008        fn a() {
 6009            b();
 6010            if c() {
 6011                d(
 6012                    e
 6013                );
 6014        ˇ
 6015            }
 6016        }
 6017    "});
 6018
 6019    cx.set_state(indoc! {"
 6020        fn a() {
 6021            b();
 6022            ˇ
 6023        }
 6024    "});
 6025
 6026    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6027    cx.assert_editor_state(indoc! {"
 6028        fn a() {
 6029            b();
 6030            d(
 6031                e
 6032            );
 6033        ˇ
 6034        }
 6035    "});
 6036}
 6037
 6038#[gpui::test]
 6039fn test_select_all(cx: &mut TestAppContext) {
 6040    init_test(cx, |_| {});
 6041
 6042    let editor = cx.add_window(|window, cx| {
 6043        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6044        build_editor(buffer, window, cx)
 6045    });
 6046    _ = editor.update(cx, |editor, window, cx| {
 6047        editor.select_all(&SelectAll, window, cx);
 6048        assert_eq!(
 6049            editor.selections.display_ranges(cx),
 6050            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6051        );
 6052    });
 6053}
 6054
 6055#[gpui::test]
 6056fn test_select_line(cx: &mut TestAppContext) {
 6057    init_test(cx, |_| {});
 6058
 6059    let editor = cx.add_window(|window, cx| {
 6060        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6061        build_editor(buffer, window, cx)
 6062    });
 6063    _ = editor.update(cx, |editor, window, cx| {
 6064        editor.change_selections(None, window, cx, |s| {
 6065            s.select_display_ranges([
 6066                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6068                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6069                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6070            ])
 6071        });
 6072        editor.select_line(&SelectLine, window, cx);
 6073        assert_eq!(
 6074            editor.selections.display_ranges(cx),
 6075            vec![
 6076                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6077                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6078            ]
 6079        );
 6080    });
 6081
 6082    _ = editor.update(cx, |editor, window, cx| {
 6083        editor.select_line(&SelectLine, window, cx);
 6084        assert_eq!(
 6085            editor.selections.display_ranges(cx),
 6086            vec![
 6087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6088                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6089            ]
 6090        );
 6091    });
 6092
 6093    _ = editor.update(cx, |editor, window, cx| {
 6094        editor.select_line(&SelectLine, window, cx);
 6095        assert_eq!(
 6096            editor.selections.display_ranges(cx),
 6097            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6098        );
 6099    });
 6100}
 6101
 6102#[gpui::test]
 6103async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6104    init_test(cx, |_| {});
 6105    let mut cx = EditorTestContext::new(cx).await;
 6106
 6107    #[track_caller]
 6108    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6109        cx.set_state(initial_state);
 6110        cx.update_editor(|e, window, cx| {
 6111            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6112        });
 6113        cx.assert_editor_state(expected_state);
 6114    }
 6115
 6116    // Selection starts and ends at the middle of lines, left-to-right
 6117    test(
 6118        &mut cx,
 6119        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6120        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6121    );
 6122    // Same thing, right-to-left
 6123    test(
 6124        &mut cx,
 6125        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6126        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6127    );
 6128
 6129    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6130    test(
 6131        &mut cx,
 6132        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6133        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6134    );
 6135    // Same thing, right-to-left
 6136    test(
 6137        &mut cx,
 6138        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6139        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6140    );
 6141
 6142    // Whole buffer, left-to-right, last line ends with newline
 6143    test(
 6144        &mut cx,
 6145        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6146        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6147    );
 6148    // Same thing, right-to-left
 6149    test(
 6150        &mut cx,
 6151        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6152        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6153    );
 6154
 6155    // Starts at the end of a line, ends at the start of another
 6156    test(
 6157        &mut cx,
 6158        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6159        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6160    );
 6161}
 6162
 6163#[gpui::test]
 6164async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6165    init_test(cx, |_| {});
 6166
 6167    let editor = cx.add_window(|window, cx| {
 6168        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6169        build_editor(buffer, window, cx)
 6170    });
 6171
 6172    // setup
 6173    _ = editor.update(cx, |editor, window, cx| {
 6174        editor.fold_creases(
 6175            vec![
 6176                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6177                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6178                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6179            ],
 6180            true,
 6181            window,
 6182            cx,
 6183        );
 6184        assert_eq!(
 6185            editor.display_text(cx),
 6186            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6187        );
 6188    });
 6189
 6190    _ = editor.update(cx, |editor, window, cx| {
 6191        editor.change_selections(None, window, cx, |s| {
 6192            s.select_display_ranges([
 6193                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6194                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6195                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6196                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6197            ])
 6198        });
 6199        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6200        assert_eq!(
 6201            editor.display_text(cx),
 6202            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6203        );
 6204    });
 6205    EditorTestContext::for_editor(editor, cx)
 6206        .await
 6207        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6208
 6209    _ = editor.update(cx, |editor, window, cx| {
 6210        editor.change_selections(None, window, cx, |s| {
 6211            s.select_display_ranges([
 6212                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6213            ])
 6214        });
 6215        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6216        assert_eq!(
 6217            editor.display_text(cx),
 6218            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6219        );
 6220        assert_eq!(
 6221            editor.selections.display_ranges(cx),
 6222            [
 6223                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6224                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6225                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6226                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6227                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6228                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6229                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6230            ]
 6231        );
 6232    });
 6233    EditorTestContext::for_editor(editor, cx)
 6234        .await
 6235        .assert_editor_state(
 6236            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6237        );
 6238}
 6239
 6240#[gpui::test]
 6241async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6242    init_test(cx, |_| {});
 6243
 6244    let mut cx = EditorTestContext::new(cx).await;
 6245
 6246    cx.set_state(indoc!(
 6247        r#"abc
 6248           defˇghi
 6249
 6250           jk
 6251           nlmo
 6252           "#
 6253    ));
 6254
 6255    cx.update_editor(|editor, window, cx| {
 6256        editor.add_selection_above(&Default::default(), window, cx);
 6257    });
 6258
 6259    cx.assert_editor_state(indoc!(
 6260        r#"abcˇ
 6261           defˇghi
 6262
 6263           jk
 6264           nlmo
 6265           "#
 6266    ));
 6267
 6268    cx.update_editor(|editor, window, cx| {
 6269        editor.add_selection_above(&Default::default(), window, cx);
 6270    });
 6271
 6272    cx.assert_editor_state(indoc!(
 6273        r#"abcˇ
 6274            defˇghi
 6275
 6276            jk
 6277            nlmo
 6278            "#
 6279    ));
 6280
 6281    cx.update_editor(|editor, window, cx| {
 6282        editor.add_selection_below(&Default::default(), window, cx);
 6283    });
 6284
 6285    cx.assert_editor_state(indoc!(
 6286        r#"abc
 6287           defˇghi
 6288
 6289           jk
 6290           nlmo
 6291           "#
 6292    ));
 6293
 6294    cx.update_editor(|editor, window, cx| {
 6295        editor.undo_selection(&Default::default(), window, cx);
 6296    });
 6297
 6298    cx.assert_editor_state(indoc!(
 6299        r#"abcˇ
 6300           defˇghi
 6301
 6302           jk
 6303           nlmo
 6304           "#
 6305    ));
 6306
 6307    cx.update_editor(|editor, window, cx| {
 6308        editor.redo_selection(&Default::default(), window, cx);
 6309    });
 6310
 6311    cx.assert_editor_state(indoc!(
 6312        r#"abc
 6313           defˇghi
 6314
 6315           jk
 6316           nlmo
 6317           "#
 6318    ));
 6319
 6320    cx.update_editor(|editor, window, cx| {
 6321        editor.add_selection_below(&Default::default(), window, cx);
 6322    });
 6323
 6324    cx.assert_editor_state(indoc!(
 6325        r#"abc
 6326           defˇghi
 6327           ˇ
 6328           jk
 6329           nlmo
 6330           "#
 6331    ));
 6332
 6333    cx.update_editor(|editor, window, cx| {
 6334        editor.add_selection_below(&Default::default(), window, cx);
 6335    });
 6336
 6337    cx.assert_editor_state(indoc!(
 6338        r#"abc
 6339           defˇghi
 6340           ˇ
 6341           jkˇ
 6342           nlmo
 6343           "#
 6344    ));
 6345
 6346    cx.update_editor(|editor, window, cx| {
 6347        editor.add_selection_below(&Default::default(), window, cx);
 6348    });
 6349
 6350    cx.assert_editor_state(indoc!(
 6351        r#"abc
 6352           defˇghi
 6353           ˇ
 6354           jkˇ
 6355           nlmˇo
 6356           "#
 6357    ));
 6358
 6359    cx.update_editor(|editor, window, cx| {
 6360        editor.add_selection_below(&Default::default(), window, cx);
 6361    });
 6362
 6363    cx.assert_editor_state(indoc!(
 6364        r#"abc
 6365           defˇghi
 6366           ˇ
 6367           jkˇ
 6368           nlmˇo
 6369           ˇ"#
 6370    ));
 6371
 6372    // change selections
 6373    cx.set_state(indoc!(
 6374        r#"abc
 6375           def«ˇg»hi
 6376
 6377           jk
 6378           nlmo
 6379           "#
 6380    ));
 6381
 6382    cx.update_editor(|editor, window, cx| {
 6383        editor.add_selection_below(&Default::default(), window, cx);
 6384    });
 6385
 6386    cx.assert_editor_state(indoc!(
 6387        r#"abc
 6388           def«ˇg»hi
 6389
 6390           jk
 6391           nlm«ˇo»
 6392           "#
 6393    ));
 6394
 6395    cx.update_editor(|editor, window, cx| {
 6396        editor.add_selection_below(&Default::default(), window, cx);
 6397    });
 6398
 6399    cx.assert_editor_state(indoc!(
 6400        r#"abc
 6401           def«ˇg»hi
 6402
 6403           jk
 6404           nlm«ˇo»
 6405           "#
 6406    ));
 6407
 6408    cx.update_editor(|editor, window, cx| {
 6409        editor.add_selection_above(&Default::default(), window, cx);
 6410    });
 6411
 6412    cx.assert_editor_state(indoc!(
 6413        r#"abc
 6414           def«ˇg»hi
 6415
 6416           jk
 6417           nlmo
 6418           "#
 6419    ));
 6420
 6421    cx.update_editor(|editor, window, cx| {
 6422        editor.add_selection_above(&Default::default(), window, cx);
 6423    });
 6424
 6425    cx.assert_editor_state(indoc!(
 6426        r#"abc
 6427           def«ˇg»hi
 6428
 6429           jk
 6430           nlmo
 6431           "#
 6432    ));
 6433
 6434    // Change selections again
 6435    cx.set_state(indoc!(
 6436        r#"a«bc
 6437           defgˇ»hi
 6438
 6439           jk
 6440           nlmo
 6441           "#
 6442    ));
 6443
 6444    cx.update_editor(|editor, window, cx| {
 6445        editor.add_selection_below(&Default::default(), window, cx);
 6446    });
 6447
 6448    cx.assert_editor_state(indoc!(
 6449        r#"a«bcˇ»
 6450           d«efgˇ»hi
 6451
 6452           j«kˇ»
 6453           nlmo
 6454           "#
 6455    ));
 6456
 6457    cx.update_editor(|editor, window, cx| {
 6458        editor.add_selection_below(&Default::default(), window, cx);
 6459    });
 6460    cx.assert_editor_state(indoc!(
 6461        r#"a«bcˇ»
 6462           d«efgˇ»hi
 6463
 6464           j«kˇ»
 6465           n«lmoˇ»
 6466           "#
 6467    ));
 6468    cx.update_editor(|editor, window, cx| {
 6469        editor.add_selection_above(&Default::default(), window, cx);
 6470    });
 6471
 6472    cx.assert_editor_state(indoc!(
 6473        r#"a«bcˇ»
 6474           d«efgˇ»hi
 6475
 6476           j«kˇ»
 6477           nlmo
 6478           "#
 6479    ));
 6480
 6481    // Change selections again
 6482    cx.set_state(indoc!(
 6483        r#"abc
 6484           d«ˇefghi
 6485
 6486           jk
 6487           nlm»o
 6488           "#
 6489    ));
 6490
 6491    cx.update_editor(|editor, window, cx| {
 6492        editor.add_selection_above(&Default::default(), window, cx);
 6493    });
 6494
 6495    cx.assert_editor_state(indoc!(
 6496        r#"a«ˇbc»
 6497           d«ˇef»ghi
 6498
 6499           j«ˇk»
 6500           n«ˇlm»o
 6501           "#
 6502    ));
 6503
 6504    cx.update_editor(|editor, window, cx| {
 6505        editor.add_selection_below(&Default::default(), window, cx);
 6506    });
 6507
 6508    cx.assert_editor_state(indoc!(
 6509        r#"abc
 6510           d«ˇef»ghi
 6511
 6512           j«ˇk»
 6513           n«ˇlm»o
 6514           "#
 6515    ));
 6516}
 6517
 6518#[gpui::test]
 6519async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6520    init_test(cx, |_| {});
 6521    let mut cx = EditorTestContext::new(cx).await;
 6522
 6523    cx.set_state(indoc!(
 6524        r#"line onˇe
 6525           liˇne two
 6526           line three
 6527           line four"#
 6528    ));
 6529
 6530    cx.update_editor(|editor, window, cx| {
 6531        editor.add_selection_below(&Default::default(), window, cx);
 6532    });
 6533
 6534    // test multiple cursors expand in the same direction
 6535    cx.assert_editor_state(indoc!(
 6536        r#"line onˇe
 6537           liˇne twˇo
 6538           liˇne three
 6539           line four"#
 6540    ));
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.add_selection_below(&Default::default(), window, cx);
 6544    });
 6545
 6546    cx.update_editor(|editor, window, cx| {
 6547        editor.add_selection_below(&Default::default(), window, cx);
 6548    });
 6549
 6550    // test multiple cursors expand below overflow
 6551    cx.assert_editor_state(indoc!(
 6552        r#"line onˇe
 6553           liˇne twˇo
 6554           liˇne thˇree
 6555           liˇne foˇur"#
 6556    ));
 6557
 6558    cx.update_editor(|editor, window, cx| {
 6559        editor.add_selection_above(&Default::default(), window, cx);
 6560    });
 6561
 6562    // test multiple cursors retrieves back correctly
 6563    cx.assert_editor_state(indoc!(
 6564        r#"line onˇe
 6565           liˇne twˇo
 6566           liˇne thˇree
 6567           line four"#
 6568    ));
 6569
 6570    cx.update_editor(|editor, window, cx| {
 6571        editor.add_selection_above(&Default::default(), window, cx);
 6572    });
 6573
 6574    cx.update_editor(|editor, window, cx| {
 6575        editor.add_selection_above(&Default::default(), window, cx);
 6576    });
 6577
 6578    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6579    cx.assert_editor_state(indoc!(
 6580        r#"liˇne onˇe
 6581           liˇne two
 6582           line three
 6583           line four"#
 6584    ));
 6585
 6586    cx.update_editor(|editor, window, cx| {
 6587        editor.undo_selection(&Default::default(), window, cx);
 6588    });
 6589
 6590    // test undo
 6591    cx.assert_editor_state(indoc!(
 6592        r#"line onˇe
 6593           liˇne twˇo
 6594           line three
 6595           line four"#
 6596    ));
 6597
 6598    cx.update_editor(|editor, window, cx| {
 6599        editor.redo_selection(&Default::default(), window, cx);
 6600    });
 6601
 6602    // test redo
 6603    cx.assert_editor_state(indoc!(
 6604        r#"liˇne onˇe
 6605           liˇne two
 6606           line three
 6607           line four"#
 6608    ));
 6609
 6610    cx.set_state(indoc!(
 6611        r#"abcd
 6612           ef«ghˇ»
 6613           ijkl
 6614           «mˇ»nop"#
 6615    ));
 6616
 6617    cx.update_editor(|editor, window, cx| {
 6618        editor.add_selection_above(&Default::default(), window, cx);
 6619    });
 6620
 6621    // test multiple selections expand in the same direction
 6622    cx.assert_editor_state(indoc!(
 6623        r#"ab«cdˇ»
 6624           ef«ghˇ»
 6625           «iˇ»jkl
 6626           «mˇ»nop"#
 6627    ));
 6628
 6629    cx.update_editor(|editor, window, cx| {
 6630        editor.add_selection_above(&Default::default(), window, cx);
 6631    });
 6632
 6633    // test multiple selection upward overflow
 6634    cx.assert_editor_state(indoc!(
 6635        r#"ab«cdˇ»
 6636           «eˇ»f«ghˇ»
 6637           «iˇ»jkl
 6638           «mˇ»nop"#
 6639    ));
 6640
 6641    cx.update_editor(|editor, window, cx| {
 6642        editor.add_selection_below(&Default::default(), window, cx);
 6643    });
 6644
 6645    // test multiple selection retrieves back correctly
 6646    cx.assert_editor_state(indoc!(
 6647        r#"abcd
 6648           ef«ghˇ»
 6649           «iˇ»jkl
 6650           «mˇ»nop"#
 6651    ));
 6652
 6653    cx.update_editor(|editor, window, cx| {
 6654        editor.add_selection_below(&Default::default(), window, cx);
 6655    });
 6656
 6657    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6658    cx.assert_editor_state(indoc!(
 6659        r#"abcd
 6660           ef«ghˇ»
 6661           ij«klˇ»
 6662           «mˇ»nop"#
 6663    ));
 6664
 6665    cx.update_editor(|editor, window, cx| {
 6666        editor.undo_selection(&Default::default(), window, cx);
 6667    });
 6668
 6669    // test undo
 6670    cx.assert_editor_state(indoc!(
 6671        r#"abcd
 6672           ef«ghˇ»
 6673           «iˇ»jkl
 6674           «mˇ»nop"#
 6675    ));
 6676
 6677    cx.update_editor(|editor, window, cx| {
 6678        editor.redo_selection(&Default::default(), window, cx);
 6679    });
 6680
 6681    // test redo
 6682    cx.assert_editor_state(indoc!(
 6683        r#"abcd
 6684           ef«ghˇ»
 6685           ij«klˇ»
 6686           «mˇ»nop"#
 6687    ));
 6688}
 6689
 6690#[gpui::test]
 6691async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6692    init_test(cx, |_| {});
 6693    let mut cx = EditorTestContext::new(cx).await;
 6694
 6695    cx.set_state(indoc!(
 6696        r#"line onˇe
 6697           liˇne two
 6698           line three
 6699           line four"#
 6700    ));
 6701
 6702    cx.update_editor(|editor, window, cx| {
 6703        editor.add_selection_below(&Default::default(), window, cx);
 6704        editor.add_selection_below(&Default::default(), window, cx);
 6705        editor.add_selection_below(&Default::default(), window, cx);
 6706    });
 6707
 6708    // initial state with two multi cursor groups
 6709    cx.assert_editor_state(indoc!(
 6710        r#"line onˇe
 6711           liˇne twˇo
 6712           liˇne thˇree
 6713           liˇne foˇur"#
 6714    ));
 6715
 6716    // add single cursor in middle - simulate opt click
 6717    cx.update_editor(|editor, window, cx| {
 6718        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6719        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6720        editor.end_selection(window, cx);
 6721    });
 6722
 6723    cx.assert_editor_state(indoc!(
 6724        r#"line onˇe
 6725           liˇne twˇo
 6726           liˇneˇ thˇree
 6727           liˇne foˇur"#
 6728    ));
 6729
 6730    cx.update_editor(|editor, window, cx| {
 6731        editor.add_selection_above(&Default::default(), window, cx);
 6732    });
 6733
 6734    // test new added selection expands above and existing selection shrinks
 6735    cx.assert_editor_state(indoc!(
 6736        r#"line onˇe
 6737           liˇneˇ twˇo
 6738           liˇneˇ thˇree
 6739           line four"#
 6740    ));
 6741
 6742    cx.update_editor(|editor, window, cx| {
 6743        editor.add_selection_above(&Default::default(), window, cx);
 6744    });
 6745
 6746    // test new added selection expands above and existing selection shrinks
 6747    cx.assert_editor_state(indoc!(
 6748        r#"lineˇ onˇe
 6749           liˇneˇ twˇo
 6750           lineˇ three
 6751           line four"#
 6752    ));
 6753
 6754    // intial state with two selection groups
 6755    cx.set_state(indoc!(
 6756        r#"abcd
 6757           ef«ghˇ»
 6758           ijkl
 6759           «mˇ»nop"#
 6760    ));
 6761
 6762    cx.update_editor(|editor, window, cx| {
 6763        editor.add_selection_above(&Default::default(), window, cx);
 6764        editor.add_selection_above(&Default::default(), window, cx);
 6765    });
 6766
 6767    cx.assert_editor_state(indoc!(
 6768        r#"ab«cdˇ»
 6769           «eˇ»f«ghˇ»
 6770           «iˇ»jkl
 6771           «mˇ»nop"#
 6772    ));
 6773
 6774    // add single selection in middle - simulate opt drag
 6775    cx.update_editor(|editor, window, cx| {
 6776        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6777        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6778        editor.update_selection(
 6779            DisplayPoint::new(DisplayRow(2), 4),
 6780            0,
 6781            gpui::Point::<f32>::default(),
 6782            window,
 6783            cx,
 6784        );
 6785        editor.end_selection(window, cx);
 6786    });
 6787
 6788    cx.assert_editor_state(indoc!(
 6789        r#"ab«cdˇ»
 6790           «eˇ»f«ghˇ»
 6791           «iˇ»jk«lˇ»
 6792           «mˇ»nop"#
 6793    ));
 6794
 6795    cx.update_editor(|editor, window, cx| {
 6796        editor.add_selection_below(&Default::default(), window, cx);
 6797    });
 6798
 6799    // test new added selection expands below, others shrinks from above
 6800    cx.assert_editor_state(indoc!(
 6801        r#"abcd
 6802           ef«ghˇ»
 6803           «iˇ»jk«lˇ»
 6804           «mˇ»no«pˇ»"#
 6805    ));
 6806}
 6807
 6808#[gpui::test]
 6809async fn test_select_next(cx: &mut TestAppContext) {
 6810    init_test(cx, |_| {});
 6811
 6812    let mut cx = EditorTestContext::new(cx).await;
 6813    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6814
 6815    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6816        .unwrap();
 6817    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6818
 6819    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6820        .unwrap();
 6821    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6822
 6823    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6824    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6825
 6826    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6827    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6828
 6829    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6830        .unwrap();
 6831    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6832
 6833    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6834        .unwrap();
 6835    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6836
 6837    // Test selection direction should be preserved
 6838    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6839
 6840    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6841        .unwrap();
 6842    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6843}
 6844
 6845#[gpui::test]
 6846async fn test_select_all_matches(cx: &mut TestAppContext) {
 6847    init_test(cx, |_| {});
 6848
 6849    let mut cx = EditorTestContext::new(cx).await;
 6850
 6851    // Test caret-only selections
 6852    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6853    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6854        .unwrap();
 6855    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6856
 6857    // Test left-to-right selections
 6858    cx.set_state("abc\n«abcˇ»\nabc");
 6859    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6860        .unwrap();
 6861    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6862
 6863    // Test right-to-left selections
 6864    cx.set_state("abc\n«ˇabc»\nabc");
 6865    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6866        .unwrap();
 6867    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6868
 6869    // Test selecting whitespace with caret selection
 6870    cx.set_state("abc\nˇ   abc\nabc");
 6871    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6872        .unwrap();
 6873    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6874
 6875    // Test selecting whitespace with left-to-right selection
 6876    cx.set_state("abc\n«ˇ  »abc\nabc");
 6877    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6878        .unwrap();
 6879    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6880
 6881    // Test no matches with right-to-left selection
 6882    cx.set_state("abc\n«  ˇ»abc\nabc");
 6883    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6884        .unwrap();
 6885    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6886
 6887    // Test with a single word and clip_at_line_ends=true (#29823)
 6888    cx.set_state("aˇbc");
 6889    cx.update_editor(|e, window, cx| {
 6890        e.set_clip_at_line_ends(true, cx);
 6891        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6892        e.set_clip_at_line_ends(false, cx);
 6893    });
 6894    cx.assert_editor_state("«abcˇ»");
 6895}
 6896
 6897#[gpui::test]
 6898async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6899    init_test(cx, |_| {});
 6900
 6901    let mut cx = EditorTestContext::new(cx).await;
 6902
 6903    let large_body_1 = "\nd".repeat(200);
 6904    let large_body_2 = "\ne".repeat(200);
 6905
 6906    cx.set_state(&format!(
 6907        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6908    ));
 6909    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6910        let scroll_position = editor.scroll_position(cx);
 6911        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6912        scroll_position
 6913    });
 6914
 6915    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6916        .unwrap();
 6917    cx.assert_editor_state(&format!(
 6918        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6919    ));
 6920    let scroll_position_after_selection =
 6921        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6922    assert_eq!(
 6923        initial_scroll_position, scroll_position_after_selection,
 6924        "Scroll position should not change after selecting all matches"
 6925    );
 6926}
 6927
 6928#[gpui::test]
 6929async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6930    init_test(cx, |_| {});
 6931
 6932    let mut cx = EditorLspTestContext::new_rust(
 6933        lsp::ServerCapabilities {
 6934            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6935            ..Default::default()
 6936        },
 6937        cx,
 6938    )
 6939    .await;
 6940
 6941    cx.set_state(indoc! {"
 6942        line 1
 6943        line 2
 6944        linˇe 3
 6945        line 4
 6946        line 5
 6947    "});
 6948
 6949    // Make an edit
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.handle_input("X", window, cx);
 6952    });
 6953
 6954    // Move cursor to a different position
 6955    cx.update_editor(|editor, window, cx| {
 6956        editor.change_selections(None, window, cx, |s| {
 6957            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6958        });
 6959    });
 6960
 6961    cx.assert_editor_state(indoc! {"
 6962        line 1
 6963        line 2
 6964        linXe 3
 6965        line 4
 6966        liˇne 5
 6967    "});
 6968
 6969    cx.lsp
 6970        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6971            Ok(Some(vec![lsp::TextEdit::new(
 6972                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6973                "PREFIX ".to_string(),
 6974            )]))
 6975        });
 6976
 6977    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6978        .unwrap()
 6979        .await
 6980        .unwrap();
 6981
 6982    cx.assert_editor_state(indoc! {"
 6983        PREFIX line 1
 6984        line 2
 6985        linXe 3
 6986        line 4
 6987        liˇne 5
 6988    "});
 6989
 6990    // Undo formatting
 6991    cx.update_editor(|editor, window, cx| {
 6992        editor.undo(&Default::default(), window, cx);
 6993    });
 6994
 6995    // Verify cursor moved back to position after edit
 6996    cx.assert_editor_state(indoc! {"
 6997        line 1
 6998        line 2
 6999        linXˇe 3
 7000        line 4
 7001        line 5
 7002    "});
 7003}
 7004
 7005#[gpui::test]
 7006async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7007    init_test(cx, |_| {});
 7008
 7009    let mut cx = EditorTestContext::new(cx).await;
 7010
 7011    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7012    cx.update_editor(|editor, window, cx| {
 7013        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7014    });
 7015
 7016    cx.set_state(indoc! {"
 7017        line 1
 7018        line 2
 7019        linˇe 3
 7020        line 4
 7021        line 5
 7022        line 6
 7023        line 7
 7024        line 8
 7025        line 9
 7026        line 10
 7027    "});
 7028
 7029    let snapshot = cx.buffer_snapshot();
 7030    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7031
 7032    cx.update(|_, cx| {
 7033        provider.update(cx, |provider, _| {
 7034            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7035                id: None,
 7036                edits: vec![(edit_position..edit_position, "X".into())],
 7037                edit_preview: None,
 7038            }))
 7039        })
 7040    });
 7041
 7042    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7045    });
 7046
 7047    cx.assert_editor_state(indoc! {"
 7048        line 1
 7049        line 2
 7050        lineXˇ 3
 7051        line 4
 7052        line 5
 7053        line 6
 7054        line 7
 7055        line 8
 7056        line 9
 7057        line 10
 7058    "});
 7059
 7060    cx.update_editor(|editor, window, cx| {
 7061        editor.change_selections(None, window, cx, |s| {
 7062            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7063        });
 7064    });
 7065
 7066    cx.assert_editor_state(indoc! {"
 7067        line 1
 7068        line 2
 7069        lineX 3
 7070        line 4
 7071        line 5
 7072        line 6
 7073        line 7
 7074        line 8
 7075        line 9
 7076        liˇne 10
 7077    "});
 7078
 7079    cx.update_editor(|editor, window, cx| {
 7080        editor.undo(&Default::default(), window, cx);
 7081    });
 7082
 7083    cx.assert_editor_state(indoc! {"
 7084        line 1
 7085        line 2
 7086        lineˇ 3
 7087        line 4
 7088        line 5
 7089        line 6
 7090        line 7
 7091        line 8
 7092        line 9
 7093        line 10
 7094    "});
 7095}
 7096
 7097#[gpui::test]
 7098async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7099    init_test(cx, |_| {});
 7100
 7101    let mut cx = EditorTestContext::new(cx).await;
 7102    cx.set_state(
 7103        r#"let foo = 2;
 7104lˇet foo = 2;
 7105let fooˇ = 2;
 7106let foo = 2;
 7107let foo = ˇ2;"#,
 7108    );
 7109
 7110    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7111        .unwrap();
 7112    cx.assert_editor_state(
 7113        r#"let foo = 2;
 7114«letˇ» foo = 2;
 7115let «fooˇ» = 2;
 7116let foo = 2;
 7117let foo = «2ˇ»;"#,
 7118    );
 7119
 7120    // noop for multiple selections with different contents
 7121    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7122        .unwrap();
 7123    cx.assert_editor_state(
 7124        r#"let foo = 2;
 7125«letˇ» foo = 2;
 7126let «fooˇ» = 2;
 7127let foo = 2;
 7128let foo = «2ˇ»;"#,
 7129    );
 7130
 7131    // Test last selection direction should be preserved
 7132    cx.set_state(
 7133        r#"let foo = 2;
 7134let foo = 2;
 7135let «fooˇ» = 2;
 7136let «ˇfoo» = 2;
 7137let foo = 2;"#,
 7138    );
 7139
 7140    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7141        .unwrap();
 7142    cx.assert_editor_state(
 7143        r#"let foo = 2;
 7144let foo = 2;
 7145let «fooˇ» = 2;
 7146let «ˇfoo» = 2;
 7147let «ˇfoo» = 2;"#,
 7148    );
 7149}
 7150
 7151#[gpui::test]
 7152async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7153    init_test(cx, |_| {});
 7154
 7155    let mut cx =
 7156        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7157
 7158    cx.assert_editor_state(indoc! {"
 7159        ˇbbb
 7160        ccc
 7161
 7162        bbb
 7163        ccc
 7164        "});
 7165    cx.dispatch_action(SelectPrevious::default());
 7166    cx.assert_editor_state(indoc! {"
 7167                «bbbˇ»
 7168                ccc
 7169
 7170                bbb
 7171                ccc
 7172                "});
 7173    cx.dispatch_action(SelectPrevious::default());
 7174    cx.assert_editor_state(indoc! {"
 7175                «bbbˇ»
 7176                ccc
 7177
 7178                «bbbˇ»
 7179                ccc
 7180                "});
 7181}
 7182
 7183#[gpui::test]
 7184async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7185    init_test(cx, |_| {});
 7186
 7187    let mut cx = EditorTestContext::new(cx).await;
 7188    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7189
 7190    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7191        .unwrap();
 7192    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7193
 7194    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7195        .unwrap();
 7196    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7197
 7198    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7199    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7200
 7201    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7202    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7203
 7204    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7205        .unwrap();
 7206    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7207
 7208    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7209        .unwrap();
 7210    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7211}
 7212
 7213#[gpui::test]
 7214async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7215    init_test(cx, |_| {});
 7216
 7217    let mut cx = EditorTestContext::new(cx).await;
 7218    cx.set_state("");
 7219
 7220    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7221        .unwrap();
 7222    cx.assert_editor_state("«aˇ»");
 7223    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7224        .unwrap();
 7225    cx.assert_editor_state("«aˇ»");
 7226}
 7227
 7228#[gpui::test]
 7229async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7230    init_test(cx, |_| {});
 7231
 7232    let mut cx = EditorTestContext::new(cx).await;
 7233    cx.set_state(
 7234        r#"let foo = 2;
 7235lˇet foo = 2;
 7236let fooˇ = 2;
 7237let foo = 2;
 7238let foo = ˇ2;"#,
 7239    );
 7240
 7241    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7242        .unwrap();
 7243    cx.assert_editor_state(
 7244        r#"let foo = 2;
 7245«letˇ» foo = 2;
 7246let «fooˇ» = 2;
 7247let foo = 2;
 7248let foo = «2ˇ»;"#,
 7249    );
 7250
 7251    // noop for multiple selections with different contents
 7252    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7253        .unwrap();
 7254    cx.assert_editor_state(
 7255        r#"let foo = 2;
 7256«letˇ» foo = 2;
 7257let «fooˇ» = 2;
 7258let foo = 2;
 7259let foo = «2ˇ»;"#,
 7260    );
 7261}
 7262
 7263#[gpui::test]
 7264async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7265    init_test(cx, |_| {});
 7266
 7267    let mut cx = EditorTestContext::new(cx).await;
 7268    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7269
 7270    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7271        .unwrap();
 7272    // selection direction is preserved
 7273    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7274
 7275    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7276        .unwrap();
 7277    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7278
 7279    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7280    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7281
 7282    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7283    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7284
 7285    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7286        .unwrap();
 7287    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7288
 7289    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7290        .unwrap();
 7291    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7292}
 7293
 7294#[gpui::test]
 7295async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7296    init_test(cx, |_| {});
 7297
 7298    let language = Arc::new(Language::new(
 7299        LanguageConfig::default(),
 7300        Some(tree_sitter_rust::LANGUAGE.into()),
 7301    ));
 7302
 7303    let text = r#"
 7304        use mod1::mod2::{mod3, mod4};
 7305
 7306        fn fn_1(param1: bool, param2: &str) {
 7307            let var1 = "text";
 7308        }
 7309    "#
 7310    .unindent();
 7311
 7312    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7314    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7315
 7316    editor
 7317        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7318        .await;
 7319
 7320    editor.update_in(cx, |editor, window, cx| {
 7321        editor.change_selections(None, window, cx, |s| {
 7322            s.select_display_ranges([
 7323                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7324                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7325                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7326            ]);
 7327        });
 7328        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7329    });
 7330    editor.update(cx, |editor, cx| {
 7331        assert_text_with_selections(
 7332            editor,
 7333            indoc! {r#"
 7334                use mod1::mod2::{mod3, «mod4ˇ»};
 7335
 7336                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7337                    let var1 = "«ˇtext»";
 7338                }
 7339            "#},
 7340            cx,
 7341        );
 7342    });
 7343
 7344    editor.update_in(cx, |editor, window, cx| {
 7345        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7346    });
 7347    editor.update(cx, |editor, cx| {
 7348        assert_text_with_selections(
 7349            editor,
 7350            indoc! {r#"
 7351                use mod1::mod2::«{mod3, mod4}ˇ»;
 7352
 7353                «ˇfn fn_1(param1: bool, param2: &str) {
 7354                    let var1 = "text";
 7355 7356            "#},
 7357            cx,
 7358        );
 7359    });
 7360
 7361    editor.update_in(cx, |editor, window, cx| {
 7362        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7363    });
 7364    assert_eq!(
 7365        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7366        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7367    );
 7368
 7369    // Trying to expand the selected syntax node one more time has no effect.
 7370    editor.update_in(cx, |editor, window, cx| {
 7371        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7372    });
 7373    assert_eq!(
 7374        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7375        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7376    );
 7377
 7378    editor.update_in(cx, |editor, window, cx| {
 7379        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7380    });
 7381    editor.update(cx, |editor, cx| {
 7382        assert_text_with_selections(
 7383            editor,
 7384            indoc! {r#"
 7385                use mod1::mod2::«{mod3, mod4}ˇ»;
 7386
 7387                «ˇfn fn_1(param1: bool, param2: &str) {
 7388                    let var1 = "text";
 7389 7390            "#},
 7391            cx,
 7392        );
 7393    });
 7394
 7395    editor.update_in(cx, |editor, window, cx| {
 7396        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7397    });
 7398    editor.update(cx, |editor, cx| {
 7399        assert_text_with_selections(
 7400            editor,
 7401            indoc! {r#"
 7402                use mod1::mod2::{mod3, «mod4ˇ»};
 7403
 7404                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7405                    let var1 = "«ˇtext»";
 7406                }
 7407            "#},
 7408            cx,
 7409        );
 7410    });
 7411
 7412    editor.update_in(cx, |editor, window, cx| {
 7413        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7414    });
 7415    editor.update(cx, |editor, cx| {
 7416        assert_text_with_selections(
 7417            editor,
 7418            indoc! {r#"
 7419                use mod1::mod2::{mod3, mo«ˇ»d4};
 7420
 7421                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7422                    let var1 = "te«ˇ»xt";
 7423                }
 7424            "#},
 7425            cx,
 7426        );
 7427    });
 7428
 7429    // Trying to shrink the selected syntax node one more time has no effect.
 7430    editor.update_in(cx, |editor, window, cx| {
 7431        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7432    });
 7433    editor.update_in(cx, |editor, _, cx| {
 7434        assert_text_with_selections(
 7435            editor,
 7436            indoc! {r#"
 7437                use mod1::mod2::{mod3, mo«ˇ»d4};
 7438
 7439                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7440                    let var1 = "te«ˇ»xt";
 7441                }
 7442            "#},
 7443            cx,
 7444        );
 7445    });
 7446
 7447    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7448    // a fold.
 7449    editor.update_in(cx, |editor, window, cx| {
 7450        editor.fold_creases(
 7451            vec![
 7452                Crease::simple(
 7453                    Point::new(0, 21)..Point::new(0, 24),
 7454                    FoldPlaceholder::test(),
 7455                ),
 7456                Crease::simple(
 7457                    Point::new(3, 20)..Point::new(3, 22),
 7458                    FoldPlaceholder::test(),
 7459                ),
 7460            ],
 7461            true,
 7462            window,
 7463            cx,
 7464        );
 7465        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7466    });
 7467    editor.update(cx, |editor, 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 = "«ˇtext»";
 7475                }
 7476            "#},
 7477            cx,
 7478        );
 7479    });
 7480}
 7481
 7482#[gpui::test]
 7483async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7484    init_test(cx, |_| {});
 7485
 7486    let language = Arc::new(Language::new(
 7487        LanguageConfig::default(),
 7488        Some(tree_sitter_rust::LANGUAGE.into()),
 7489    ));
 7490
 7491    let text = "let a = 2;";
 7492
 7493    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7494    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7495    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7496
 7497    editor
 7498        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7499        .await;
 7500
 7501    // Test case 1: Cursor at end of word
 7502    editor.update_in(cx, |editor, window, cx| {
 7503        editor.change_selections(None, window, cx, |s| {
 7504            s.select_display_ranges([
 7505                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7506            ]);
 7507        });
 7508    });
 7509    editor.update(cx, |editor, cx| {
 7510        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7511    });
 7512    editor.update_in(cx, |editor, window, cx| {
 7513        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7514    });
 7515    editor.update(cx, |editor, cx| {
 7516        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7517    });
 7518    editor.update_in(cx, |editor, window, cx| {
 7519        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7520    });
 7521    editor.update(cx, |editor, cx| {
 7522        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7523    });
 7524
 7525    // Test case 2: Cursor at end of statement
 7526    editor.update_in(cx, |editor, window, cx| {
 7527        editor.change_selections(None, window, cx, |s| {
 7528            s.select_display_ranges([
 7529                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7530            ]);
 7531        });
 7532    });
 7533    editor.update(cx, |editor, cx| {
 7534        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7535    });
 7536    editor.update_in(cx, |editor, window, cx| {
 7537        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7538    });
 7539    editor.update(cx, |editor, cx| {
 7540        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7541    });
 7542}
 7543
 7544#[gpui::test]
 7545async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7546    init_test(cx, |_| {});
 7547
 7548    let language = Arc::new(Language::new(
 7549        LanguageConfig::default(),
 7550        Some(tree_sitter_rust::LANGUAGE.into()),
 7551    ));
 7552
 7553    let text = r#"
 7554        use mod1::mod2::{mod3, mod4};
 7555
 7556        fn fn_1(param1: bool, param2: &str) {
 7557            let var1 = "hello world";
 7558        }
 7559    "#
 7560    .unindent();
 7561
 7562    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7563    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7564    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7565
 7566    editor
 7567        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7568        .await;
 7569
 7570    // Test 1: Cursor on a letter of a string word
 7571    editor.update_in(cx, |editor, window, cx| {
 7572        editor.change_selections(None, window, cx, |s| {
 7573            s.select_display_ranges([
 7574                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7575            ]);
 7576        });
 7577    });
 7578    editor.update_in(cx, |editor, window, cx| {
 7579        assert_text_with_selections(
 7580            editor,
 7581            indoc! {r#"
 7582                use mod1::mod2::{mod3, mod4};
 7583
 7584                fn fn_1(param1: bool, param2: &str) {
 7585                    let var1 = "hˇello world";
 7586                }
 7587            "#},
 7588            cx,
 7589        );
 7590        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7591        assert_text_with_selections(
 7592            editor,
 7593            indoc! {r#"
 7594                use mod1::mod2::{mod3, mod4};
 7595
 7596                fn fn_1(param1: bool, param2: &str) {
 7597                    let var1 = "«ˇhello» world";
 7598                }
 7599            "#},
 7600            cx,
 7601        );
 7602    });
 7603
 7604    // Test 2: Partial selection within a word
 7605    editor.update_in(cx, |editor, window, cx| {
 7606        editor.change_selections(None, window, cx, |s| {
 7607            s.select_display_ranges([
 7608                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7609            ]);
 7610        });
 7611    });
 7612    editor.update_in(cx, |editor, window, cx| {
 7613        assert_text_with_selections(
 7614            editor,
 7615            indoc! {r#"
 7616                use mod1::mod2::{mod3, mod4};
 7617
 7618                fn fn_1(param1: bool, param2: &str) {
 7619                    let var1 = "h«elˇ»lo world";
 7620                }
 7621            "#},
 7622            cx,
 7623        );
 7624        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7625        assert_text_with_selections(
 7626            editor,
 7627            indoc! {r#"
 7628                use mod1::mod2::{mod3, mod4};
 7629
 7630                fn fn_1(param1: bool, param2: &str) {
 7631                    let var1 = "«ˇhello» world";
 7632                }
 7633            "#},
 7634            cx,
 7635        );
 7636    });
 7637
 7638    // Test 3: Complete word already selected
 7639    editor.update_in(cx, |editor, window, cx| {
 7640        editor.change_selections(None, window, cx, |s| {
 7641            s.select_display_ranges([
 7642                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7643            ]);
 7644        });
 7645    });
 7646    editor.update_in(cx, |editor, window, cx| {
 7647        assert_text_with_selections(
 7648            editor,
 7649            indoc! {r#"
 7650                use mod1::mod2::{mod3, mod4};
 7651
 7652                fn fn_1(param1: bool, param2: &str) {
 7653                    let var1 = "«helloˇ» world";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7659        assert_text_with_selections(
 7660            editor,
 7661            indoc! {r#"
 7662                use mod1::mod2::{mod3, mod4};
 7663
 7664                fn fn_1(param1: bool, param2: &str) {
 7665                    let var1 = "«hello worldˇ»";
 7666                }
 7667            "#},
 7668            cx,
 7669        );
 7670    });
 7671
 7672    // Test 4: Selection spanning across words
 7673    editor.update_in(cx, |editor, window, cx| {
 7674        editor.change_selections(None, window, cx, |s| {
 7675            s.select_display_ranges([
 7676                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7677            ]);
 7678        });
 7679    });
 7680    editor.update_in(cx, |editor, window, cx| {
 7681        assert_text_with_selections(
 7682            editor,
 7683            indoc! {r#"
 7684                use mod1::mod2::{mod3, mod4};
 7685
 7686                fn fn_1(param1: bool, param2: &str) {
 7687                    let var1 = "hel«lo woˇ»rld";
 7688                }
 7689            "#},
 7690            cx,
 7691        );
 7692        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7693        assert_text_with_selections(
 7694            editor,
 7695            indoc! {r#"
 7696                use mod1::mod2::{mod3, mod4};
 7697
 7698                fn fn_1(param1: bool, param2: &str) {
 7699                    let var1 = "«ˇhello world»";
 7700                }
 7701            "#},
 7702            cx,
 7703        );
 7704    });
 7705
 7706    // Test 5: Expansion beyond string
 7707    editor.update_in(cx, |editor, window, cx| {
 7708        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7709        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7710        assert_text_with_selections(
 7711            editor,
 7712            indoc! {r#"
 7713                use mod1::mod2::{mod3, mod4};
 7714
 7715                fn fn_1(param1: bool, param2: &str) {
 7716                    «ˇlet var1 = "hello world";»
 7717                }
 7718            "#},
 7719            cx,
 7720        );
 7721    });
 7722}
 7723
 7724#[gpui::test]
 7725async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7726    init_test(cx, |_| {});
 7727
 7728    let base_text = r#"
 7729        impl A {
 7730            // this is an uncommitted comment
 7731
 7732            fn b() {
 7733                c();
 7734            }
 7735
 7736            // this is another uncommitted comment
 7737
 7738            fn d() {
 7739                // e
 7740                // f
 7741            }
 7742        }
 7743
 7744        fn g() {
 7745            // h
 7746        }
 7747    "#
 7748    .unindent();
 7749
 7750    let text = r#"
 7751        ˇimpl A {
 7752
 7753            fn b() {
 7754                c();
 7755            }
 7756
 7757            fn d() {
 7758                // e
 7759                // f
 7760            }
 7761        }
 7762
 7763        fn g() {
 7764            // h
 7765        }
 7766    "#
 7767    .unindent();
 7768
 7769    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7770    cx.set_state(&text);
 7771    cx.set_head_text(&base_text);
 7772    cx.update_editor(|editor, window, cx| {
 7773        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7774    });
 7775
 7776    cx.assert_state_with_diff(
 7777        "
 7778        ˇimpl A {
 7779      -     // this is an uncommitted comment
 7780
 7781            fn b() {
 7782                c();
 7783            }
 7784
 7785      -     // this is another uncommitted comment
 7786      -
 7787            fn d() {
 7788                // e
 7789                // f
 7790            }
 7791        }
 7792
 7793        fn g() {
 7794            // h
 7795        }
 7796    "
 7797        .unindent(),
 7798    );
 7799
 7800    let expected_display_text = "
 7801        impl A {
 7802            // this is an uncommitted comment
 7803
 7804            fn b() {
 7805 7806            }
 7807
 7808            // this is another uncommitted comment
 7809
 7810            fn d() {
 7811 7812            }
 7813        }
 7814
 7815        fn g() {
 7816 7817        }
 7818        "
 7819    .unindent();
 7820
 7821    cx.update_editor(|editor, window, cx| {
 7822        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7823        assert_eq!(editor.display_text(cx), expected_display_text);
 7824    });
 7825}
 7826
 7827#[gpui::test]
 7828async fn test_autoindent(cx: &mut TestAppContext) {
 7829    init_test(cx, |_| {});
 7830
 7831    let language = Arc::new(
 7832        Language::new(
 7833            LanguageConfig {
 7834                brackets: BracketPairConfig {
 7835                    pairs: vec![
 7836                        BracketPair {
 7837                            start: "{".to_string(),
 7838                            end: "}".to_string(),
 7839                            close: false,
 7840                            surround: false,
 7841                            newline: true,
 7842                        },
 7843                        BracketPair {
 7844                            start: "(".to_string(),
 7845                            end: ")".to_string(),
 7846                            close: false,
 7847                            surround: false,
 7848                            newline: true,
 7849                        },
 7850                    ],
 7851                    ..Default::default()
 7852                },
 7853                ..Default::default()
 7854            },
 7855            Some(tree_sitter_rust::LANGUAGE.into()),
 7856        )
 7857        .with_indents_query(
 7858            r#"
 7859                (_ "(" ")" @end) @indent
 7860                (_ "{" "}" @end) @indent
 7861            "#,
 7862        )
 7863        .unwrap(),
 7864    );
 7865
 7866    let text = "fn a() {}";
 7867
 7868    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7869    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7870    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7871    editor
 7872        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7873        .await;
 7874
 7875    editor.update_in(cx, |editor, window, cx| {
 7876        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7877        editor.newline(&Newline, window, cx);
 7878        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7879        assert_eq!(
 7880            editor.selections.ranges(cx),
 7881            &[
 7882                Point::new(1, 4)..Point::new(1, 4),
 7883                Point::new(3, 4)..Point::new(3, 4),
 7884                Point::new(5, 0)..Point::new(5, 0)
 7885            ]
 7886        );
 7887    });
 7888}
 7889
 7890#[gpui::test]
 7891async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7892    init_test(cx, |_| {});
 7893
 7894    {
 7895        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7896        cx.set_state(indoc! {"
 7897            impl A {
 7898
 7899                fn b() {}
 7900
 7901            «fn c() {
 7902
 7903            }ˇ»
 7904            }
 7905        "});
 7906
 7907        cx.update_editor(|editor, window, cx| {
 7908            editor.autoindent(&Default::default(), window, cx);
 7909        });
 7910
 7911        cx.assert_editor_state(indoc! {"
 7912            impl A {
 7913
 7914                fn b() {}
 7915
 7916                «fn c() {
 7917
 7918                }ˇ»
 7919            }
 7920        "});
 7921    }
 7922
 7923    {
 7924        let mut cx = EditorTestContext::new_multibuffer(
 7925            cx,
 7926            [indoc! { "
 7927                impl A {
 7928                «
 7929                // a
 7930                fn b(){}
 7931                »
 7932                «
 7933                    }
 7934                    fn c(){}
 7935                »
 7936            "}],
 7937        );
 7938
 7939        let buffer = cx.update_editor(|editor, _, cx| {
 7940            let buffer = editor.buffer().update(cx, |buffer, _| {
 7941                buffer.all_buffers().iter().next().unwrap().clone()
 7942            });
 7943            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7944            buffer
 7945        });
 7946
 7947        cx.run_until_parked();
 7948        cx.update_editor(|editor, window, cx| {
 7949            editor.select_all(&Default::default(), window, cx);
 7950            editor.autoindent(&Default::default(), window, cx)
 7951        });
 7952        cx.run_until_parked();
 7953
 7954        cx.update(|_, cx| {
 7955            assert_eq!(
 7956                buffer.read(cx).text(),
 7957                indoc! { "
 7958                    impl A {
 7959
 7960                        // a
 7961                        fn b(){}
 7962
 7963
 7964                    }
 7965                    fn c(){}
 7966
 7967                " }
 7968            )
 7969        });
 7970    }
 7971}
 7972
 7973#[gpui::test]
 7974async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7975    init_test(cx, |_| {});
 7976
 7977    let mut cx = EditorTestContext::new(cx).await;
 7978
 7979    let language = Arc::new(Language::new(
 7980        LanguageConfig {
 7981            brackets: BracketPairConfig {
 7982                pairs: vec![
 7983                    BracketPair {
 7984                        start: "{".to_string(),
 7985                        end: "}".to_string(),
 7986                        close: true,
 7987                        surround: true,
 7988                        newline: true,
 7989                    },
 7990                    BracketPair {
 7991                        start: "(".to_string(),
 7992                        end: ")".to_string(),
 7993                        close: true,
 7994                        surround: true,
 7995                        newline: true,
 7996                    },
 7997                    BracketPair {
 7998                        start: "/*".to_string(),
 7999                        end: " */".to_string(),
 8000                        close: true,
 8001                        surround: true,
 8002                        newline: true,
 8003                    },
 8004                    BracketPair {
 8005                        start: "[".to_string(),
 8006                        end: "]".to_string(),
 8007                        close: false,
 8008                        surround: false,
 8009                        newline: true,
 8010                    },
 8011                    BracketPair {
 8012                        start: "\"".to_string(),
 8013                        end: "\"".to_string(),
 8014                        close: true,
 8015                        surround: true,
 8016                        newline: false,
 8017                    },
 8018                    BracketPair {
 8019                        start: "<".to_string(),
 8020                        end: ">".to_string(),
 8021                        close: false,
 8022                        surround: true,
 8023                        newline: true,
 8024                    },
 8025                ],
 8026                ..Default::default()
 8027            },
 8028            autoclose_before: "})]".to_string(),
 8029            ..Default::default()
 8030        },
 8031        Some(tree_sitter_rust::LANGUAGE.into()),
 8032    ));
 8033
 8034    cx.language_registry().add(language.clone());
 8035    cx.update_buffer(|buffer, cx| {
 8036        buffer.set_language(Some(language), cx);
 8037    });
 8038
 8039    cx.set_state(
 8040        &r#"
 8041            🏀ˇ
 8042            εˇ
 8043            ❤️ˇ
 8044        "#
 8045        .unindent(),
 8046    );
 8047
 8048    // autoclose multiple nested brackets at multiple cursors
 8049    cx.update_editor(|editor, window, cx| {
 8050        editor.handle_input("{", window, cx);
 8051        editor.handle_input("{", window, cx);
 8052        editor.handle_input("{", window, cx);
 8053    });
 8054    cx.assert_editor_state(
 8055        &"
 8056            🏀{{{ˇ}}}
 8057            ε{{{ˇ}}}
 8058            ❤️{{{ˇ}}}
 8059        "
 8060        .unindent(),
 8061    );
 8062
 8063    // insert a different closing bracket
 8064    cx.update_editor(|editor, window, cx| {
 8065        editor.handle_input(")", window, cx);
 8066    });
 8067    cx.assert_editor_state(
 8068        &"
 8069            🏀{{{)ˇ}}}
 8070            ε{{{)ˇ}}}
 8071            ❤️{{{)ˇ}}}
 8072        "
 8073        .unindent(),
 8074    );
 8075
 8076    // skip over the auto-closed brackets when typing a closing bracket
 8077    cx.update_editor(|editor, window, cx| {
 8078        editor.move_right(&MoveRight, window, cx);
 8079        editor.handle_input("}", window, cx);
 8080        editor.handle_input("}", window, cx);
 8081        editor.handle_input("}", window, cx);
 8082    });
 8083    cx.assert_editor_state(
 8084        &"
 8085            🏀{{{)}}}}ˇ
 8086            ε{{{)}}}}ˇ
 8087            ❤️{{{)}}}}ˇ
 8088        "
 8089        .unindent(),
 8090    );
 8091
 8092    // autoclose multi-character pairs
 8093    cx.set_state(
 8094        &"
 8095            ˇ
 8096            ˇ
 8097        "
 8098        .unindent(),
 8099    );
 8100    cx.update_editor(|editor, window, cx| {
 8101        editor.handle_input("/", window, cx);
 8102        editor.handle_input("*", window, cx);
 8103    });
 8104    cx.assert_editor_state(
 8105        &"
 8106            /*ˇ */
 8107            /*ˇ */
 8108        "
 8109        .unindent(),
 8110    );
 8111
 8112    // one cursor autocloses a multi-character pair, one cursor
 8113    // does not autoclose.
 8114    cx.set_state(
 8115        &"
 8116 8117            ˇ
 8118        "
 8119        .unindent(),
 8120    );
 8121    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8122    cx.assert_editor_state(
 8123        &"
 8124            /*ˇ */
 8125 8126        "
 8127        .unindent(),
 8128    );
 8129
 8130    // Don't autoclose if the next character isn't whitespace and isn't
 8131    // listed in the language's "autoclose_before" section.
 8132    cx.set_state("ˇa b");
 8133    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8134    cx.assert_editor_state("{ˇa b");
 8135
 8136    // Don't autoclose if `close` is false for the bracket pair
 8137    cx.set_state("ˇ");
 8138    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8139    cx.assert_editor_state("");
 8140
 8141    // Surround with brackets if text is selected
 8142    cx.set_state("«aˇ» b");
 8143    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8144    cx.assert_editor_state("{«aˇ»} b");
 8145
 8146    // Autoclose when not immediately after a word character
 8147    cx.set_state("a ˇ");
 8148    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8149    cx.assert_editor_state("a \"ˇ\"");
 8150
 8151    // Autoclose pair where the start and end characters are the same
 8152    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8153    cx.assert_editor_state("a \"\"ˇ");
 8154
 8155    // Don't autoclose when immediately after a word character
 8156    cx.set_state("");
 8157    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8158    cx.assert_editor_state("a\"ˇ");
 8159
 8160    // Do autoclose when after a non-word character
 8161    cx.set_state("");
 8162    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8163    cx.assert_editor_state("{\"ˇ\"");
 8164
 8165    // Non identical pairs autoclose regardless of preceding character
 8166    cx.set_state("");
 8167    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8168    cx.assert_editor_state("a{ˇ}");
 8169
 8170    // Don't autoclose pair if autoclose is disabled
 8171    cx.set_state("ˇ");
 8172    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8173    cx.assert_editor_state("");
 8174
 8175    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8176    cx.set_state("«aˇ» b");
 8177    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8178    cx.assert_editor_state("<«aˇ»> b");
 8179}
 8180
 8181#[gpui::test]
 8182async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8183    init_test(cx, |settings| {
 8184        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8185    });
 8186
 8187    let mut cx = EditorTestContext::new(cx).await;
 8188
 8189    let language = Arc::new(Language::new(
 8190        LanguageConfig {
 8191            brackets: BracketPairConfig {
 8192                pairs: vec![
 8193                    BracketPair {
 8194                        start: "{".to_string(),
 8195                        end: "}".to_string(),
 8196                        close: true,
 8197                        surround: true,
 8198                        newline: true,
 8199                    },
 8200                    BracketPair {
 8201                        start: "(".to_string(),
 8202                        end: ")".to_string(),
 8203                        close: true,
 8204                        surround: true,
 8205                        newline: true,
 8206                    },
 8207                    BracketPair {
 8208                        start: "[".to_string(),
 8209                        end: "]".to_string(),
 8210                        close: false,
 8211                        surround: false,
 8212                        newline: true,
 8213                    },
 8214                ],
 8215                ..Default::default()
 8216            },
 8217            autoclose_before: "})]".to_string(),
 8218            ..Default::default()
 8219        },
 8220        Some(tree_sitter_rust::LANGUAGE.into()),
 8221    ));
 8222
 8223    cx.language_registry().add(language.clone());
 8224    cx.update_buffer(|buffer, cx| {
 8225        buffer.set_language(Some(language), cx);
 8226    });
 8227
 8228    cx.set_state(
 8229        &"
 8230            ˇ
 8231            ˇ
 8232            ˇ
 8233        "
 8234        .unindent(),
 8235    );
 8236
 8237    // ensure only matching closing brackets are skipped over
 8238    cx.update_editor(|editor, window, cx| {
 8239        editor.handle_input("}", window, cx);
 8240        editor.move_left(&MoveLeft, window, cx);
 8241        editor.handle_input(")", window, cx);
 8242        editor.move_left(&MoveLeft, window, cx);
 8243    });
 8244    cx.assert_editor_state(
 8245        &"
 8246            ˇ)}
 8247            ˇ)}
 8248            ˇ)}
 8249        "
 8250        .unindent(),
 8251    );
 8252
 8253    // skip-over closing brackets at multiple cursors
 8254    cx.update_editor(|editor, window, cx| {
 8255        editor.handle_input(")", window, cx);
 8256        editor.handle_input("}", window, cx);
 8257    });
 8258    cx.assert_editor_state(
 8259        &"
 8260            )}ˇ
 8261            )}ˇ
 8262            )}ˇ
 8263        "
 8264        .unindent(),
 8265    );
 8266
 8267    // ignore non-close brackets
 8268    cx.update_editor(|editor, window, cx| {
 8269        editor.handle_input("]", window, cx);
 8270        editor.move_left(&MoveLeft, window, cx);
 8271        editor.handle_input("]", window, cx);
 8272    });
 8273    cx.assert_editor_state(
 8274        &"
 8275            )}]ˇ]
 8276            )}]ˇ]
 8277            )}]ˇ]
 8278        "
 8279        .unindent(),
 8280    );
 8281}
 8282
 8283#[gpui::test]
 8284async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8285    init_test(cx, |_| {});
 8286
 8287    let mut cx = EditorTestContext::new(cx).await;
 8288
 8289    let html_language = Arc::new(
 8290        Language::new(
 8291            LanguageConfig {
 8292                name: "HTML".into(),
 8293                brackets: BracketPairConfig {
 8294                    pairs: vec![
 8295                        BracketPair {
 8296                            start: "<".into(),
 8297                            end: ">".into(),
 8298                            close: true,
 8299                            ..Default::default()
 8300                        },
 8301                        BracketPair {
 8302                            start: "{".into(),
 8303                            end: "}".into(),
 8304                            close: true,
 8305                            ..Default::default()
 8306                        },
 8307                        BracketPair {
 8308                            start: "(".into(),
 8309                            end: ")".into(),
 8310                            close: true,
 8311                            ..Default::default()
 8312                        },
 8313                    ],
 8314                    ..Default::default()
 8315                },
 8316                autoclose_before: "})]>".into(),
 8317                ..Default::default()
 8318            },
 8319            Some(tree_sitter_html::LANGUAGE.into()),
 8320        )
 8321        .with_injection_query(
 8322            r#"
 8323            (script_element
 8324                (raw_text) @injection.content
 8325                (#set! injection.language "javascript"))
 8326            "#,
 8327        )
 8328        .unwrap(),
 8329    );
 8330
 8331    let javascript_language = Arc::new(Language::new(
 8332        LanguageConfig {
 8333            name: "JavaScript".into(),
 8334            brackets: BracketPairConfig {
 8335                pairs: vec![
 8336                    BracketPair {
 8337                        start: "/*".into(),
 8338                        end: " */".into(),
 8339                        close: true,
 8340                        ..Default::default()
 8341                    },
 8342                    BracketPair {
 8343                        start: "{".into(),
 8344                        end: "}".into(),
 8345                        close: true,
 8346                        ..Default::default()
 8347                    },
 8348                    BracketPair {
 8349                        start: "(".into(),
 8350                        end: ")".into(),
 8351                        close: true,
 8352                        ..Default::default()
 8353                    },
 8354                ],
 8355                ..Default::default()
 8356            },
 8357            autoclose_before: "})]>".into(),
 8358            ..Default::default()
 8359        },
 8360        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8361    ));
 8362
 8363    cx.language_registry().add(html_language.clone());
 8364    cx.language_registry().add(javascript_language.clone());
 8365
 8366    cx.update_buffer(|buffer, cx| {
 8367        buffer.set_language(Some(html_language), cx);
 8368    });
 8369
 8370    cx.set_state(
 8371        &r#"
 8372            <body>ˇ
 8373                <script>
 8374                    var x = 1;ˇ
 8375                </script>
 8376            </body>ˇ
 8377        "#
 8378        .unindent(),
 8379    );
 8380
 8381    // Precondition: different languages are active at different locations.
 8382    cx.update_editor(|editor, window, cx| {
 8383        let snapshot = editor.snapshot(window, cx);
 8384        let cursors = editor.selections.ranges::<usize>(cx);
 8385        let languages = cursors
 8386            .iter()
 8387            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8388            .collect::<Vec<_>>();
 8389        assert_eq!(
 8390            languages,
 8391            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8392        );
 8393    });
 8394
 8395    // Angle brackets autoclose in HTML, but not JavaScript.
 8396    cx.update_editor(|editor, window, cx| {
 8397        editor.handle_input("<", window, cx);
 8398        editor.handle_input("a", window, cx);
 8399    });
 8400    cx.assert_editor_state(
 8401        &r#"
 8402            <body><aˇ>
 8403                <script>
 8404                    var x = 1;<aˇ
 8405                </script>
 8406            </body><aˇ>
 8407        "#
 8408        .unindent(),
 8409    );
 8410
 8411    // Curly braces and parens autoclose in both HTML and JavaScript.
 8412    cx.update_editor(|editor, window, cx| {
 8413        editor.handle_input(" b=", window, cx);
 8414        editor.handle_input("{", window, cx);
 8415        editor.handle_input("c", window, cx);
 8416        editor.handle_input("(", window, cx);
 8417    });
 8418    cx.assert_editor_state(
 8419        &r#"
 8420            <body><a b={c(ˇ)}>
 8421                <script>
 8422                    var x = 1;<a b={c(ˇ)}
 8423                </script>
 8424            </body><a b={c(ˇ)}>
 8425        "#
 8426        .unindent(),
 8427    );
 8428
 8429    // Brackets that were already autoclosed are skipped.
 8430    cx.update_editor(|editor, window, cx| {
 8431        editor.handle_input(")", window, cx);
 8432        editor.handle_input("d", window, cx);
 8433        editor.handle_input("}", window, cx);
 8434    });
 8435    cx.assert_editor_state(
 8436        &r#"
 8437            <body><a b={c()d}ˇ>
 8438                <script>
 8439                    var x = 1;<a b={c()d}ˇ
 8440                </script>
 8441            </body><a b={c()d}ˇ>
 8442        "#
 8443        .unindent(),
 8444    );
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.handle_input(">", window, cx);
 8447    });
 8448    cx.assert_editor_state(
 8449        &r#"
 8450            <body><a b={c()d}>ˇ
 8451                <script>
 8452                    var x = 1;<a b={c()d}>ˇ
 8453                </script>
 8454            </body><a b={c()d}>ˇ
 8455        "#
 8456        .unindent(),
 8457    );
 8458
 8459    // Reset
 8460    cx.set_state(
 8461        &r#"
 8462            <body>ˇ
 8463                <script>
 8464                    var x = 1;ˇ
 8465                </script>
 8466            </body>ˇ
 8467        "#
 8468        .unindent(),
 8469    );
 8470
 8471    cx.update_editor(|editor, window, cx| {
 8472        editor.handle_input("<", window, cx);
 8473    });
 8474    cx.assert_editor_state(
 8475        &r#"
 8476            <body><ˇ>
 8477                <script>
 8478                    var x = 1;<ˇ
 8479                </script>
 8480            </body><ˇ>
 8481        "#
 8482        .unindent(),
 8483    );
 8484
 8485    // When backspacing, the closing angle brackets are removed.
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.backspace(&Backspace, window, cx);
 8488    });
 8489    cx.assert_editor_state(
 8490        &r#"
 8491            <body>ˇ
 8492                <script>
 8493                    var x = 1;ˇ
 8494                </script>
 8495            </body>ˇ
 8496        "#
 8497        .unindent(),
 8498    );
 8499
 8500    // Block comments autoclose in JavaScript, but not HTML.
 8501    cx.update_editor(|editor, window, cx| {
 8502        editor.handle_input("/", window, cx);
 8503        editor.handle_input("*", window, cx);
 8504    });
 8505    cx.assert_editor_state(
 8506        &r#"
 8507            <body>/*ˇ
 8508                <script>
 8509                    var x = 1;/*ˇ */
 8510                </script>
 8511            </body>/*ˇ
 8512        "#
 8513        .unindent(),
 8514    );
 8515}
 8516
 8517#[gpui::test]
 8518async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8519    init_test(cx, |_| {});
 8520
 8521    let mut cx = EditorTestContext::new(cx).await;
 8522
 8523    let rust_language = Arc::new(
 8524        Language::new(
 8525            LanguageConfig {
 8526                name: "Rust".into(),
 8527                brackets: serde_json::from_value(json!([
 8528                    { "start": "{", "end": "}", "close": true, "newline": true },
 8529                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8530                ]))
 8531                .unwrap(),
 8532                autoclose_before: "})]>".into(),
 8533                ..Default::default()
 8534            },
 8535            Some(tree_sitter_rust::LANGUAGE.into()),
 8536        )
 8537        .with_override_query("(string_literal) @string")
 8538        .unwrap(),
 8539    );
 8540
 8541    cx.language_registry().add(rust_language.clone());
 8542    cx.update_buffer(|buffer, cx| {
 8543        buffer.set_language(Some(rust_language), cx);
 8544    });
 8545
 8546    cx.set_state(
 8547        &r#"
 8548            let x = ˇ
 8549        "#
 8550        .unindent(),
 8551    );
 8552
 8553    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8554    cx.update_editor(|editor, window, cx| {
 8555        editor.handle_input("\"", window, cx);
 8556    });
 8557    cx.assert_editor_state(
 8558        &r#"
 8559            let x = "ˇ"
 8560        "#
 8561        .unindent(),
 8562    );
 8563
 8564    // Inserting another quotation mark. The cursor moves across the existing
 8565    // automatically-inserted quotation mark.
 8566    cx.update_editor(|editor, window, cx| {
 8567        editor.handle_input("\"", window, cx);
 8568    });
 8569    cx.assert_editor_state(
 8570        &r#"
 8571            let x = ""ˇ
 8572        "#
 8573        .unindent(),
 8574    );
 8575
 8576    // Reset
 8577    cx.set_state(
 8578        &r#"
 8579            let x = ˇ
 8580        "#
 8581        .unindent(),
 8582    );
 8583
 8584    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8585    cx.update_editor(|editor, window, cx| {
 8586        editor.handle_input("\"", window, cx);
 8587        editor.handle_input(" ", window, cx);
 8588        editor.move_left(&Default::default(), window, cx);
 8589        editor.handle_input("\\", window, cx);
 8590        editor.handle_input("\"", window, cx);
 8591    });
 8592    cx.assert_editor_state(
 8593        &r#"
 8594            let x = "\"ˇ "
 8595        "#
 8596        .unindent(),
 8597    );
 8598
 8599    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8600    // mark. Nothing is inserted.
 8601    cx.update_editor(|editor, window, cx| {
 8602        editor.move_right(&Default::default(), window, cx);
 8603        editor.handle_input("\"", window, cx);
 8604    });
 8605    cx.assert_editor_state(
 8606        &r#"
 8607            let x = "\" "ˇ
 8608        "#
 8609        .unindent(),
 8610    );
 8611}
 8612
 8613#[gpui::test]
 8614async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8615    init_test(cx, |_| {});
 8616
 8617    let language = Arc::new(Language::new(
 8618        LanguageConfig {
 8619            brackets: BracketPairConfig {
 8620                pairs: vec![
 8621                    BracketPair {
 8622                        start: "{".to_string(),
 8623                        end: "}".to_string(),
 8624                        close: true,
 8625                        surround: true,
 8626                        newline: true,
 8627                    },
 8628                    BracketPair {
 8629                        start: "/* ".to_string(),
 8630                        end: "*/".to_string(),
 8631                        close: true,
 8632                        surround: true,
 8633                        ..Default::default()
 8634                    },
 8635                ],
 8636                ..Default::default()
 8637            },
 8638            ..Default::default()
 8639        },
 8640        Some(tree_sitter_rust::LANGUAGE.into()),
 8641    ));
 8642
 8643    let text = r#"
 8644        a
 8645        b
 8646        c
 8647    "#
 8648    .unindent();
 8649
 8650    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8651    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8652    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8653    editor
 8654        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8655        .await;
 8656
 8657    editor.update_in(cx, |editor, window, cx| {
 8658        editor.change_selections(None, window, cx, |s| {
 8659            s.select_display_ranges([
 8660                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8661                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8662                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8663            ])
 8664        });
 8665
 8666        editor.handle_input("{", window, cx);
 8667        editor.handle_input("{", window, cx);
 8668        editor.handle_input("{", window, cx);
 8669        assert_eq!(
 8670            editor.text(cx),
 8671            "
 8672                {{{a}}}
 8673                {{{b}}}
 8674                {{{c}}}
 8675            "
 8676            .unindent()
 8677        );
 8678        assert_eq!(
 8679            editor.selections.display_ranges(cx),
 8680            [
 8681                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8682                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8683                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8684            ]
 8685        );
 8686
 8687        editor.undo(&Undo, window, cx);
 8688        editor.undo(&Undo, window, cx);
 8689        editor.undo(&Undo, window, cx);
 8690        assert_eq!(
 8691            editor.text(cx),
 8692            "
 8693                a
 8694                b
 8695                c
 8696            "
 8697            .unindent()
 8698        );
 8699        assert_eq!(
 8700            editor.selections.display_ranges(cx),
 8701            [
 8702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8704                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8705            ]
 8706        );
 8707
 8708        // Ensure inserting the first character of a multi-byte bracket pair
 8709        // doesn't surround the selections with the bracket.
 8710        editor.handle_input("/", window, cx);
 8711        assert_eq!(
 8712            editor.text(cx),
 8713            "
 8714                /
 8715                /
 8716                /
 8717            "
 8718            .unindent()
 8719        );
 8720        assert_eq!(
 8721            editor.selections.display_ranges(cx),
 8722            [
 8723                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8724                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8725                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8726            ]
 8727        );
 8728
 8729        editor.undo(&Undo, window, cx);
 8730        assert_eq!(
 8731            editor.text(cx),
 8732            "
 8733                a
 8734                b
 8735                c
 8736            "
 8737            .unindent()
 8738        );
 8739        assert_eq!(
 8740            editor.selections.display_ranges(cx),
 8741            [
 8742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8743                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8744                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8745            ]
 8746        );
 8747
 8748        // Ensure inserting the last character of a multi-byte bracket pair
 8749        // doesn't surround the selections with the bracket.
 8750        editor.handle_input("*", window, cx);
 8751        assert_eq!(
 8752            editor.text(cx),
 8753            "
 8754                *
 8755                *
 8756                *
 8757            "
 8758            .unindent()
 8759        );
 8760        assert_eq!(
 8761            editor.selections.display_ranges(cx),
 8762            [
 8763                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8764                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8765                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8766            ]
 8767        );
 8768    });
 8769}
 8770
 8771#[gpui::test]
 8772async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8773    init_test(cx, |_| {});
 8774
 8775    let language = Arc::new(Language::new(
 8776        LanguageConfig {
 8777            brackets: BracketPairConfig {
 8778                pairs: vec![BracketPair {
 8779                    start: "{".to_string(),
 8780                    end: "}".to_string(),
 8781                    close: true,
 8782                    surround: true,
 8783                    newline: true,
 8784                }],
 8785                ..Default::default()
 8786            },
 8787            autoclose_before: "}".to_string(),
 8788            ..Default::default()
 8789        },
 8790        Some(tree_sitter_rust::LANGUAGE.into()),
 8791    ));
 8792
 8793    let text = r#"
 8794        a
 8795        b
 8796        c
 8797    "#
 8798    .unindent();
 8799
 8800    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8801    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8802    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8803    editor
 8804        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8805        .await;
 8806
 8807    editor.update_in(cx, |editor, window, cx| {
 8808        editor.change_selections(None, window, cx, |s| {
 8809            s.select_ranges([
 8810                Point::new(0, 1)..Point::new(0, 1),
 8811                Point::new(1, 1)..Point::new(1, 1),
 8812                Point::new(2, 1)..Point::new(2, 1),
 8813            ])
 8814        });
 8815
 8816        editor.handle_input("{", window, cx);
 8817        editor.handle_input("{", window, cx);
 8818        editor.handle_input("_", window, cx);
 8819        assert_eq!(
 8820            editor.text(cx),
 8821            "
 8822                a{{_}}
 8823                b{{_}}
 8824                c{{_}}
 8825            "
 8826            .unindent()
 8827        );
 8828        assert_eq!(
 8829            editor.selections.ranges::<Point>(cx),
 8830            [
 8831                Point::new(0, 4)..Point::new(0, 4),
 8832                Point::new(1, 4)..Point::new(1, 4),
 8833                Point::new(2, 4)..Point::new(2, 4)
 8834            ]
 8835        );
 8836
 8837        editor.backspace(&Default::default(), window, cx);
 8838        editor.backspace(&Default::default(), window, cx);
 8839        assert_eq!(
 8840            editor.text(cx),
 8841            "
 8842                a{}
 8843                b{}
 8844                c{}
 8845            "
 8846            .unindent()
 8847        );
 8848        assert_eq!(
 8849            editor.selections.ranges::<Point>(cx),
 8850            [
 8851                Point::new(0, 2)..Point::new(0, 2),
 8852                Point::new(1, 2)..Point::new(1, 2),
 8853                Point::new(2, 2)..Point::new(2, 2)
 8854            ]
 8855        );
 8856
 8857        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8858        assert_eq!(
 8859            editor.text(cx),
 8860            "
 8861                a
 8862                b
 8863                c
 8864            "
 8865            .unindent()
 8866        );
 8867        assert_eq!(
 8868            editor.selections.ranges::<Point>(cx),
 8869            [
 8870                Point::new(0, 1)..Point::new(0, 1),
 8871                Point::new(1, 1)..Point::new(1, 1),
 8872                Point::new(2, 1)..Point::new(2, 1)
 8873            ]
 8874        );
 8875    });
 8876}
 8877
 8878#[gpui::test]
 8879async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8880    init_test(cx, |settings| {
 8881        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8882    });
 8883
 8884    let mut cx = EditorTestContext::new(cx).await;
 8885
 8886    let language = Arc::new(Language::new(
 8887        LanguageConfig {
 8888            brackets: BracketPairConfig {
 8889                pairs: vec![
 8890                    BracketPair {
 8891                        start: "{".to_string(),
 8892                        end: "}".to_string(),
 8893                        close: true,
 8894                        surround: true,
 8895                        newline: true,
 8896                    },
 8897                    BracketPair {
 8898                        start: "(".to_string(),
 8899                        end: ")".to_string(),
 8900                        close: true,
 8901                        surround: true,
 8902                        newline: true,
 8903                    },
 8904                    BracketPair {
 8905                        start: "[".to_string(),
 8906                        end: "]".to_string(),
 8907                        close: false,
 8908                        surround: true,
 8909                        newline: true,
 8910                    },
 8911                ],
 8912                ..Default::default()
 8913            },
 8914            autoclose_before: "})]".to_string(),
 8915            ..Default::default()
 8916        },
 8917        Some(tree_sitter_rust::LANGUAGE.into()),
 8918    ));
 8919
 8920    cx.language_registry().add(language.clone());
 8921    cx.update_buffer(|buffer, cx| {
 8922        buffer.set_language(Some(language), cx);
 8923    });
 8924
 8925    cx.set_state(
 8926        &"
 8927            {(ˇ)}
 8928            [[ˇ]]
 8929            {(ˇ)}
 8930        "
 8931        .unindent(),
 8932    );
 8933
 8934    cx.update_editor(|editor, window, cx| {
 8935        editor.backspace(&Default::default(), window, cx);
 8936        editor.backspace(&Default::default(), window, cx);
 8937    });
 8938
 8939    cx.assert_editor_state(
 8940        &"
 8941            ˇ
 8942            ˇ]]
 8943            ˇ
 8944        "
 8945        .unindent(),
 8946    );
 8947
 8948    cx.update_editor(|editor, window, cx| {
 8949        editor.handle_input("{", window, cx);
 8950        editor.handle_input("{", window, cx);
 8951        editor.move_right(&MoveRight, window, cx);
 8952        editor.move_right(&MoveRight, window, cx);
 8953        editor.move_left(&MoveLeft, window, cx);
 8954        editor.move_left(&MoveLeft, window, cx);
 8955        editor.backspace(&Default::default(), window, cx);
 8956    });
 8957
 8958    cx.assert_editor_state(
 8959        &"
 8960            {ˇ}
 8961            {ˇ}]]
 8962            {ˇ}
 8963        "
 8964        .unindent(),
 8965    );
 8966
 8967    cx.update_editor(|editor, window, cx| {
 8968        editor.backspace(&Default::default(), window, cx);
 8969    });
 8970
 8971    cx.assert_editor_state(
 8972        &"
 8973            ˇ
 8974            ˇ]]
 8975            ˇ
 8976        "
 8977        .unindent(),
 8978    );
 8979}
 8980
 8981#[gpui::test]
 8982async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8983    init_test(cx, |_| {});
 8984
 8985    let language = Arc::new(Language::new(
 8986        LanguageConfig::default(),
 8987        Some(tree_sitter_rust::LANGUAGE.into()),
 8988    ));
 8989
 8990    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8991    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8992    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8993    editor
 8994        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8995        .await;
 8996
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        editor.set_auto_replace_emoji_shortcode(true);
 8999
 9000        editor.handle_input("Hello ", window, cx);
 9001        editor.handle_input(":wave", window, cx);
 9002        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9003
 9004        editor.handle_input(":", window, cx);
 9005        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9006
 9007        editor.handle_input(" :smile", window, cx);
 9008        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9009
 9010        editor.handle_input(":", window, cx);
 9011        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9012
 9013        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9014        editor.handle_input(":wave", window, cx);
 9015        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9016
 9017        editor.handle_input(":", window, cx);
 9018        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9019
 9020        editor.handle_input(":1", window, cx);
 9021        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9022
 9023        editor.handle_input(":", window, cx);
 9024        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9025
 9026        // Ensure shortcode does not get replaced when it is part of a word
 9027        editor.handle_input(" Test:wave", window, cx);
 9028        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9029
 9030        editor.handle_input(":", window, cx);
 9031        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9032
 9033        editor.set_auto_replace_emoji_shortcode(false);
 9034
 9035        // Ensure shortcode does not get replaced when auto replace is off
 9036        editor.handle_input(" :wave", window, cx);
 9037        assert_eq!(
 9038            editor.text(cx),
 9039            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9040        );
 9041
 9042        editor.handle_input(":", window, cx);
 9043        assert_eq!(
 9044            editor.text(cx),
 9045            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9046        );
 9047    });
 9048}
 9049
 9050#[gpui::test]
 9051async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9052    init_test(cx, |_| {});
 9053
 9054    let (text, insertion_ranges) = marked_text_ranges(
 9055        indoc! {"
 9056            ˇ
 9057        "},
 9058        false,
 9059    );
 9060
 9061    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9062    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9063
 9064    _ = editor.update_in(cx, |editor, window, cx| {
 9065        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9066
 9067        editor
 9068            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9069            .unwrap();
 9070
 9071        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9072            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9073            assert_eq!(editor.text(cx), expected_text);
 9074            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9075        }
 9076
 9077        assert(
 9078            editor,
 9079            cx,
 9080            indoc! {"
 9081            type «» =•
 9082            "},
 9083        );
 9084
 9085        assert!(editor.context_menu_visible(), "There should be a matches");
 9086    });
 9087}
 9088
 9089#[gpui::test]
 9090async fn test_snippets(cx: &mut TestAppContext) {
 9091    init_test(cx, |_| {});
 9092
 9093    let mut cx = EditorTestContext::new(cx).await;
 9094
 9095    cx.set_state(indoc! {"
 9096        a.ˇ b
 9097        a.ˇ b
 9098        a.ˇ b
 9099    "});
 9100
 9101    cx.update_editor(|editor, window, cx| {
 9102        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9103        let insertion_ranges = editor
 9104            .selections
 9105            .all(cx)
 9106            .iter()
 9107            .map(|s| s.range().clone())
 9108            .collect::<Vec<_>>();
 9109        editor
 9110            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9111            .unwrap();
 9112    });
 9113
 9114    cx.assert_editor_state(indoc! {"
 9115        a.f(«oneˇ», two, «threeˇ») b
 9116        a.f(«oneˇ», two, «threeˇ») b
 9117        a.f(«oneˇ», two, «threeˇ») b
 9118    "});
 9119
 9120    // Can't move earlier than the first tab stop
 9121    cx.update_editor(|editor, window, cx| {
 9122        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9123    });
 9124    cx.assert_editor_state(indoc! {"
 9125        a.f(«oneˇ», two, «threeˇ») b
 9126        a.f(«oneˇ», two, «threeˇ») b
 9127        a.f(«oneˇ», two, «threeˇ») b
 9128    "});
 9129
 9130    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9131    cx.assert_editor_state(indoc! {"
 9132        a.f(one, «twoˇ», three) b
 9133        a.f(one, «twoˇ», three) b
 9134        a.f(one, «twoˇ», three) b
 9135    "});
 9136
 9137    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9138    cx.assert_editor_state(indoc! {"
 9139        a.f(«oneˇ», two, «threeˇ») b
 9140        a.f(«oneˇ», two, «threeˇ») b
 9141        a.f(«oneˇ», two, «threeˇ») b
 9142    "});
 9143
 9144    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9145    cx.assert_editor_state(indoc! {"
 9146        a.f(one, «twoˇ», three) b
 9147        a.f(one, «twoˇ», three) b
 9148        a.f(one, «twoˇ», three) b
 9149    "});
 9150    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9151    cx.assert_editor_state(indoc! {"
 9152        a.f(one, two, three)ˇ b
 9153        a.f(one, two, three)ˇ b
 9154        a.f(one, two, three)ˇ b
 9155    "});
 9156
 9157    // As soon as the last tab stop is reached, snippet state is gone
 9158    cx.update_editor(|editor, window, cx| {
 9159        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9160    });
 9161    cx.assert_editor_state(indoc! {"
 9162        a.f(one, two, three)ˇ b
 9163        a.f(one, two, three)ˇ b
 9164        a.f(one, two, three)ˇ b
 9165    "});
 9166}
 9167
 9168#[gpui::test]
 9169async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9170    init_test(cx, |_| {});
 9171
 9172    let mut cx = EditorTestContext::new(cx).await;
 9173
 9174    cx.update_editor(|editor, window, cx| {
 9175        let snippet = Snippet::parse(indoc! {"
 9176            /*
 9177             * Multiline comment with leading indentation
 9178             *
 9179             * $1
 9180             */
 9181            $0"})
 9182        .unwrap();
 9183        let insertion_ranges = editor
 9184            .selections
 9185            .all(cx)
 9186            .iter()
 9187            .map(|s| s.range().clone())
 9188            .collect::<Vec<_>>();
 9189        editor
 9190            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9191            .unwrap();
 9192    });
 9193
 9194    cx.assert_editor_state(indoc! {"
 9195        /*
 9196         * Multiline comment with leading indentation
 9197         *
 9198         * ˇ
 9199         */
 9200    "});
 9201
 9202    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9203    cx.assert_editor_state(indoc! {"
 9204        /*
 9205         * Multiline comment with leading indentation
 9206         *
 9207         *•
 9208         */
 9209        ˇ"});
 9210}
 9211
 9212#[gpui::test]
 9213async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9214    init_test(cx, |_| {});
 9215
 9216    let fs = FakeFs::new(cx.executor());
 9217    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9218
 9219    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9220
 9221    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9222    language_registry.add(rust_lang());
 9223    let mut fake_servers = language_registry.register_fake_lsp(
 9224        "Rust",
 9225        FakeLspAdapter {
 9226            capabilities: lsp::ServerCapabilities {
 9227                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9228                ..Default::default()
 9229            },
 9230            ..Default::default()
 9231        },
 9232    );
 9233
 9234    let buffer = project
 9235        .update(cx, |project, cx| {
 9236            project.open_local_buffer(path!("/file.rs"), cx)
 9237        })
 9238        .await
 9239        .unwrap();
 9240
 9241    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9242    let (editor, cx) = cx.add_window_view(|window, cx| {
 9243        build_editor_with_project(project.clone(), buffer, window, cx)
 9244    });
 9245    editor.update_in(cx, |editor, window, cx| {
 9246        editor.set_text("one\ntwo\nthree\n", window, cx)
 9247    });
 9248    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9249
 9250    cx.executor().start_waiting();
 9251    let fake_server = fake_servers.next().await.unwrap();
 9252
 9253    {
 9254        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9255            move |params, _| async move {
 9256                assert_eq!(
 9257                    params.text_document.uri,
 9258                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9259                );
 9260                assert_eq!(params.options.tab_size, 4);
 9261                Ok(Some(vec![lsp::TextEdit::new(
 9262                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9263                    ", ".to_string(),
 9264                )]))
 9265            },
 9266        );
 9267        let save = editor
 9268            .update_in(cx, |editor, window, cx| {
 9269                editor.save(
 9270                    SaveOptions {
 9271                        format: true,
 9272                        autosave: false,
 9273                    },
 9274                    project.clone(),
 9275                    window,
 9276                    cx,
 9277                )
 9278            })
 9279            .unwrap();
 9280        cx.executor().start_waiting();
 9281        save.await;
 9282
 9283        assert_eq!(
 9284            editor.update(cx, |editor, cx| editor.text(cx)),
 9285            "one, two\nthree\n"
 9286        );
 9287        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9288    }
 9289
 9290    {
 9291        editor.update_in(cx, |editor, window, cx| {
 9292            editor.set_text("one\ntwo\nthree\n", window, cx)
 9293        });
 9294        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9295
 9296        // Ensure we can still save even if formatting hangs.
 9297        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9298            move |params, _| async move {
 9299                assert_eq!(
 9300                    params.text_document.uri,
 9301                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9302                );
 9303                futures::future::pending::<()>().await;
 9304                unreachable!()
 9305            },
 9306        );
 9307        let save = editor
 9308            .update_in(cx, |editor, window, cx| {
 9309                editor.save(
 9310                    SaveOptions {
 9311                        format: true,
 9312                        autosave: false,
 9313                    },
 9314                    project.clone(),
 9315                    window,
 9316                    cx,
 9317                )
 9318            })
 9319            .unwrap();
 9320        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9321        cx.executor().start_waiting();
 9322        save.await;
 9323        assert_eq!(
 9324            editor.update(cx, |editor, cx| editor.text(cx)),
 9325            "one\ntwo\nthree\n"
 9326        );
 9327    }
 9328
 9329    // Set rust language override and assert overridden tabsize is sent to language server
 9330    update_test_language_settings(cx, |settings| {
 9331        settings.languages.insert(
 9332            "Rust".into(),
 9333            LanguageSettingsContent {
 9334                tab_size: NonZeroU32::new(8),
 9335                ..Default::default()
 9336            },
 9337        );
 9338    });
 9339
 9340    {
 9341        editor.update_in(cx, |editor, window, cx| {
 9342            editor.set_text("somehting_new\n", window, cx)
 9343        });
 9344        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9345        let _formatting_request_signal = fake_server
 9346            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9347                assert_eq!(
 9348                    params.text_document.uri,
 9349                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9350                );
 9351                assert_eq!(params.options.tab_size, 8);
 9352                Ok(Some(vec![]))
 9353            });
 9354        let save = editor
 9355            .update_in(cx, |editor, window, cx| {
 9356                editor.save(
 9357                    SaveOptions {
 9358                        format: true,
 9359                        autosave: false,
 9360                    },
 9361                    project.clone(),
 9362                    window,
 9363                    cx,
 9364                )
 9365            })
 9366            .unwrap();
 9367        cx.executor().start_waiting();
 9368        save.await;
 9369    }
 9370}
 9371
 9372#[gpui::test]
 9373async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9374    init_test(cx, |_| {});
 9375
 9376    let cols = 4;
 9377    let rows = 10;
 9378    let sample_text_1 = sample_text(rows, cols, 'a');
 9379    assert_eq!(
 9380        sample_text_1,
 9381        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9382    );
 9383    let sample_text_2 = sample_text(rows, cols, 'l');
 9384    assert_eq!(
 9385        sample_text_2,
 9386        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9387    );
 9388    let sample_text_3 = sample_text(rows, cols, 'v');
 9389    assert_eq!(
 9390        sample_text_3,
 9391        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9392    );
 9393
 9394    let fs = FakeFs::new(cx.executor());
 9395    fs.insert_tree(
 9396        path!("/a"),
 9397        json!({
 9398            "main.rs": sample_text_1,
 9399            "other.rs": sample_text_2,
 9400            "lib.rs": sample_text_3,
 9401        }),
 9402    )
 9403    .await;
 9404
 9405    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9406    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9407    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9408
 9409    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9410    language_registry.add(rust_lang());
 9411    let mut fake_servers = language_registry.register_fake_lsp(
 9412        "Rust",
 9413        FakeLspAdapter {
 9414            capabilities: lsp::ServerCapabilities {
 9415                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9416                ..Default::default()
 9417            },
 9418            ..Default::default()
 9419        },
 9420    );
 9421
 9422    let worktree = project.update(cx, |project, cx| {
 9423        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9424        assert_eq!(worktrees.len(), 1);
 9425        worktrees.pop().unwrap()
 9426    });
 9427    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9428
 9429    let buffer_1 = project
 9430        .update(cx, |project, cx| {
 9431            project.open_buffer((worktree_id, "main.rs"), cx)
 9432        })
 9433        .await
 9434        .unwrap();
 9435    let buffer_2 = project
 9436        .update(cx, |project, cx| {
 9437            project.open_buffer((worktree_id, "other.rs"), cx)
 9438        })
 9439        .await
 9440        .unwrap();
 9441    let buffer_3 = project
 9442        .update(cx, |project, cx| {
 9443            project.open_buffer((worktree_id, "lib.rs"), cx)
 9444        })
 9445        .await
 9446        .unwrap();
 9447
 9448    let multi_buffer = cx.new(|cx| {
 9449        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9450        multi_buffer.push_excerpts(
 9451            buffer_1.clone(),
 9452            [
 9453                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9454                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9455                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9456            ],
 9457            cx,
 9458        );
 9459        multi_buffer.push_excerpts(
 9460            buffer_2.clone(),
 9461            [
 9462                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9463                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9464                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9465            ],
 9466            cx,
 9467        );
 9468        multi_buffer.push_excerpts(
 9469            buffer_3.clone(),
 9470            [
 9471                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9472                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9473                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9474            ],
 9475            cx,
 9476        );
 9477        multi_buffer
 9478    });
 9479    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9480        Editor::new(
 9481            EditorMode::full(),
 9482            multi_buffer,
 9483            Some(project.clone()),
 9484            window,
 9485            cx,
 9486        )
 9487    });
 9488
 9489    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9490        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9491            s.select_ranges(Some(1..2))
 9492        });
 9493        editor.insert("|one|two|three|", window, cx);
 9494    });
 9495    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9496    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9497        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9498            s.select_ranges(Some(60..70))
 9499        });
 9500        editor.insert("|four|five|six|", window, cx);
 9501    });
 9502    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9503
 9504    // First two buffers should be edited, but not the third one.
 9505    assert_eq!(
 9506        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9507        "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}",
 9508    );
 9509    buffer_1.update(cx, |buffer, _| {
 9510        assert!(buffer.is_dirty());
 9511        assert_eq!(
 9512            buffer.text(),
 9513            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9514        )
 9515    });
 9516    buffer_2.update(cx, |buffer, _| {
 9517        assert!(buffer.is_dirty());
 9518        assert_eq!(
 9519            buffer.text(),
 9520            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9521        )
 9522    });
 9523    buffer_3.update(cx, |buffer, _| {
 9524        assert!(!buffer.is_dirty());
 9525        assert_eq!(buffer.text(), sample_text_3,)
 9526    });
 9527    cx.executor().run_until_parked();
 9528
 9529    cx.executor().start_waiting();
 9530    let save = multi_buffer_editor
 9531        .update_in(cx, |editor, window, cx| {
 9532            editor.save(
 9533                SaveOptions {
 9534                    format: true,
 9535                    autosave: false,
 9536                },
 9537                project.clone(),
 9538                window,
 9539                cx,
 9540            )
 9541        })
 9542        .unwrap();
 9543
 9544    let fake_server = fake_servers.next().await.unwrap();
 9545    fake_server
 9546        .server
 9547        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9548            Ok(Some(vec![lsp::TextEdit::new(
 9549                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9550                format!("[{} formatted]", params.text_document.uri),
 9551            )]))
 9552        })
 9553        .detach();
 9554    save.await;
 9555
 9556    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9557    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9558    assert_eq!(
 9559        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9560        uri!(
 9561            "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}"
 9562        ),
 9563    );
 9564    buffer_1.update(cx, |buffer, _| {
 9565        assert!(!buffer.is_dirty());
 9566        assert_eq!(
 9567            buffer.text(),
 9568            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9569        )
 9570    });
 9571    buffer_2.update(cx, |buffer, _| {
 9572        assert!(!buffer.is_dirty());
 9573        assert_eq!(
 9574            buffer.text(),
 9575            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9576        )
 9577    });
 9578    buffer_3.update(cx, |buffer, _| {
 9579        assert!(!buffer.is_dirty());
 9580        assert_eq!(buffer.text(), sample_text_3,)
 9581    });
 9582}
 9583
 9584#[gpui::test]
 9585async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9586    init_test(cx, |_| {});
 9587
 9588    let fs = FakeFs::new(cx.executor());
 9589    fs.insert_tree(
 9590        path!("/dir"),
 9591        json!({
 9592            "file1.rs": "fn main() { println!(\"hello\"); }",
 9593            "file2.rs": "fn test() { println!(\"test\"); }",
 9594            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9595        }),
 9596    )
 9597    .await;
 9598
 9599    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9600    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9601    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9602
 9603    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9604    language_registry.add(rust_lang());
 9605
 9606    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9607    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9608
 9609    // Open three buffers
 9610    let buffer_1 = project
 9611        .update(cx, |project, cx| {
 9612            project.open_buffer((worktree_id, "file1.rs"), cx)
 9613        })
 9614        .await
 9615        .unwrap();
 9616    let buffer_2 = project
 9617        .update(cx, |project, cx| {
 9618            project.open_buffer((worktree_id, "file2.rs"), cx)
 9619        })
 9620        .await
 9621        .unwrap();
 9622    let buffer_3 = project
 9623        .update(cx, |project, cx| {
 9624            project.open_buffer((worktree_id, "file3.rs"), cx)
 9625        })
 9626        .await
 9627        .unwrap();
 9628
 9629    // Create a multi-buffer with all three buffers
 9630    let multi_buffer = cx.new(|cx| {
 9631        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9632        multi_buffer.push_excerpts(
 9633            buffer_1.clone(),
 9634            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9635            cx,
 9636        );
 9637        multi_buffer.push_excerpts(
 9638            buffer_2.clone(),
 9639            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9640            cx,
 9641        );
 9642        multi_buffer.push_excerpts(
 9643            buffer_3.clone(),
 9644            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9645            cx,
 9646        );
 9647        multi_buffer
 9648    });
 9649
 9650    let editor = cx.new_window_entity(|window, cx| {
 9651        Editor::new(
 9652            EditorMode::full(),
 9653            multi_buffer,
 9654            Some(project.clone()),
 9655            window,
 9656            cx,
 9657        )
 9658    });
 9659
 9660    // Edit only the first buffer
 9661    editor.update_in(cx, |editor, window, cx| {
 9662        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9663            s.select_ranges(Some(10..10))
 9664        });
 9665        editor.insert("// edited", window, cx);
 9666    });
 9667
 9668    // Verify that only buffer 1 is dirty
 9669    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9670    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9671    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9672
 9673    // Get write counts after file creation (files were created with initial content)
 9674    // We expect each file to have been written once during creation
 9675    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9676    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9677    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9678
 9679    // Perform autosave
 9680    let save_task = editor.update_in(cx, |editor, window, cx| {
 9681        editor.save(
 9682            SaveOptions {
 9683                format: true,
 9684                autosave: true,
 9685            },
 9686            project.clone(),
 9687            window,
 9688            cx,
 9689        )
 9690    });
 9691    save_task.await.unwrap();
 9692
 9693    // Only the dirty buffer should have been saved
 9694    assert_eq!(
 9695        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9696        1,
 9697        "Buffer 1 was dirty, so it should have been written once during autosave"
 9698    );
 9699    assert_eq!(
 9700        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9701        0,
 9702        "Buffer 2 was clean, so it should not have been written during autosave"
 9703    );
 9704    assert_eq!(
 9705        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9706        0,
 9707        "Buffer 3 was clean, so it should not have been written during autosave"
 9708    );
 9709
 9710    // Verify buffer states after autosave
 9711    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9712    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9713    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9714
 9715    // Now perform a manual save (format = true)
 9716    let save_task = editor.update_in(cx, |editor, window, cx| {
 9717        editor.save(
 9718            SaveOptions {
 9719                format: true,
 9720                autosave: false,
 9721            },
 9722            project.clone(),
 9723            window,
 9724            cx,
 9725        )
 9726    });
 9727    save_task.await.unwrap();
 9728
 9729    // During manual save, clean buffers don't get written to disk
 9730    // They just get did_save called for language server notifications
 9731    assert_eq!(
 9732        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9733        1,
 9734        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9735    );
 9736    assert_eq!(
 9737        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9738        0,
 9739        "Buffer 2 should not have been written at all"
 9740    );
 9741    assert_eq!(
 9742        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9743        0,
 9744        "Buffer 3 should not have been written at all"
 9745    );
 9746}
 9747
 9748#[gpui::test]
 9749async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9750    init_test(cx, |_| {});
 9751
 9752    let fs = FakeFs::new(cx.executor());
 9753    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9754
 9755    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9756
 9757    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9758    language_registry.add(rust_lang());
 9759    let mut fake_servers = language_registry.register_fake_lsp(
 9760        "Rust",
 9761        FakeLspAdapter {
 9762            capabilities: lsp::ServerCapabilities {
 9763                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9764                ..Default::default()
 9765            },
 9766            ..Default::default()
 9767        },
 9768    );
 9769
 9770    let buffer = project
 9771        .update(cx, |project, cx| {
 9772            project.open_local_buffer(path!("/file.rs"), cx)
 9773        })
 9774        .await
 9775        .unwrap();
 9776
 9777    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9778    let (editor, cx) = cx.add_window_view(|window, cx| {
 9779        build_editor_with_project(project.clone(), buffer, window, cx)
 9780    });
 9781    editor.update_in(cx, |editor, window, cx| {
 9782        editor.set_text("one\ntwo\nthree\n", window, cx)
 9783    });
 9784    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9785
 9786    cx.executor().start_waiting();
 9787    let fake_server = fake_servers.next().await.unwrap();
 9788
 9789    let save = editor
 9790        .update_in(cx, |editor, window, cx| {
 9791            editor.save(
 9792                SaveOptions {
 9793                    format: true,
 9794                    autosave: false,
 9795                },
 9796                project.clone(),
 9797                window,
 9798                cx,
 9799            )
 9800        })
 9801        .unwrap();
 9802    fake_server
 9803        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9804            assert_eq!(
 9805                params.text_document.uri,
 9806                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9807            );
 9808            assert_eq!(params.options.tab_size, 4);
 9809            Ok(Some(vec![lsp::TextEdit::new(
 9810                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9811                ", ".to_string(),
 9812            )]))
 9813        })
 9814        .next()
 9815        .await;
 9816    cx.executor().start_waiting();
 9817    save.await;
 9818    assert_eq!(
 9819        editor.update(cx, |editor, cx| editor.text(cx)),
 9820        "one, two\nthree\n"
 9821    );
 9822    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9823
 9824    editor.update_in(cx, |editor, window, cx| {
 9825        editor.set_text("one\ntwo\nthree\n", window, cx)
 9826    });
 9827    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9828
 9829    // Ensure we can still save even if formatting hangs.
 9830    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9831        move |params, _| async move {
 9832            assert_eq!(
 9833                params.text_document.uri,
 9834                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9835            );
 9836            futures::future::pending::<()>().await;
 9837            unreachable!()
 9838        },
 9839    );
 9840    let save = editor
 9841        .update_in(cx, |editor, window, cx| {
 9842            editor.save(
 9843                SaveOptions {
 9844                    format: true,
 9845                    autosave: false,
 9846                },
 9847                project.clone(),
 9848                window,
 9849                cx,
 9850            )
 9851        })
 9852        .unwrap();
 9853    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9854    cx.executor().start_waiting();
 9855    save.await;
 9856    assert_eq!(
 9857        editor.update(cx, |editor, cx| editor.text(cx)),
 9858        "one\ntwo\nthree\n"
 9859    );
 9860    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9861
 9862    // For non-dirty buffer, no formatting request should be sent
 9863    let save = editor
 9864        .update_in(cx, |editor, window, cx| {
 9865            editor.save(
 9866                SaveOptions {
 9867                    format: false,
 9868                    autosave: false,
 9869                },
 9870                project.clone(),
 9871                window,
 9872                cx,
 9873            )
 9874        })
 9875        .unwrap();
 9876    let _pending_format_request = fake_server
 9877        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9878            panic!("Should not be invoked");
 9879        })
 9880        .next();
 9881    cx.executor().start_waiting();
 9882    save.await;
 9883
 9884    // Set Rust language override and assert overridden tabsize is sent to language server
 9885    update_test_language_settings(cx, |settings| {
 9886        settings.languages.insert(
 9887            "Rust".into(),
 9888            LanguageSettingsContent {
 9889                tab_size: NonZeroU32::new(8),
 9890                ..Default::default()
 9891            },
 9892        );
 9893    });
 9894
 9895    editor.update_in(cx, |editor, window, cx| {
 9896        editor.set_text("somehting_new\n", window, cx)
 9897    });
 9898    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9899    let save = editor
 9900        .update_in(cx, |editor, window, cx| {
 9901            editor.save(
 9902                SaveOptions {
 9903                    format: true,
 9904                    autosave: false,
 9905                },
 9906                project.clone(),
 9907                window,
 9908                cx,
 9909            )
 9910        })
 9911        .unwrap();
 9912    fake_server
 9913        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9914            assert_eq!(
 9915                params.text_document.uri,
 9916                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9917            );
 9918            assert_eq!(params.options.tab_size, 8);
 9919            Ok(Some(Vec::new()))
 9920        })
 9921        .next()
 9922        .await;
 9923    save.await;
 9924}
 9925
 9926#[gpui::test]
 9927async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9928    init_test(cx, |settings| {
 9929        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9930            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9931        ))
 9932    });
 9933
 9934    let fs = FakeFs::new(cx.executor());
 9935    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9936
 9937    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9938
 9939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9940    language_registry.add(Arc::new(Language::new(
 9941        LanguageConfig {
 9942            name: "Rust".into(),
 9943            matcher: LanguageMatcher {
 9944                path_suffixes: vec!["rs".to_string()],
 9945                ..Default::default()
 9946            },
 9947            ..LanguageConfig::default()
 9948        },
 9949        Some(tree_sitter_rust::LANGUAGE.into()),
 9950    )));
 9951    update_test_language_settings(cx, |settings| {
 9952        // Enable Prettier formatting for the same buffer, and ensure
 9953        // LSP is called instead of Prettier.
 9954        settings.defaults.prettier = Some(PrettierSettings {
 9955            allowed: true,
 9956            ..PrettierSettings::default()
 9957        });
 9958    });
 9959    let mut fake_servers = language_registry.register_fake_lsp(
 9960        "Rust",
 9961        FakeLspAdapter {
 9962            capabilities: lsp::ServerCapabilities {
 9963                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9964                ..Default::default()
 9965            },
 9966            ..Default::default()
 9967        },
 9968    );
 9969
 9970    let buffer = project
 9971        .update(cx, |project, cx| {
 9972            project.open_local_buffer(path!("/file.rs"), cx)
 9973        })
 9974        .await
 9975        .unwrap();
 9976
 9977    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9978    let (editor, cx) = cx.add_window_view(|window, cx| {
 9979        build_editor_with_project(project.clone(), buffer, window, cx)
 9980    });
 9981    editor.update_in(cx, |editor, window, cx| {
 9982        editor.set_text("one\ntwo\nthree\n", window, cx)
 9983    });
 9984
 9985    cx.executor().start_waiting();
 9986    let fake_server = fake_servers.next().await.unwrap();
 9987
 9988    let format = editor
 9989        .update_in(cx, |editor, window, cx| {
 9990            editor.perform_format(
 9991                project.clone(),
 9992                FormatTrigger::Manual,
 9993                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9994                window,
 9995                cx,
 9996            )
 9997        })
 9998        .unwrap();
 9999    fake_server
10000        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10001            assert_eq!(
10002                params.text_document.uri,
10003                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10004            );
10005            assert_eq!(params.options.tab_size, 4);
10006            Ok(Some(vec![lsp::TextEdit::new(
10007                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10008                ", ".to_string(),
10009            )]))
10010        })
10011        .next()
10012        .await;
10013    cx.executor().start_waiting();
10014    format.await;
10015    assert_eq!(
10016        editor.update(cx, |editor, cx| editor.text(cx)),
10017        "one, two\nthree\n"
10018    );
10019
10020    editor.update_in(cx, |editor, window, cx| {
10021        editor.set_text("one\ntwo\nthree\n", window, cx)
10022    });
10023    // Ensure we don't lock if formatting hangs.
10024    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10025        move |params, _| async move {
10026            assert_eq!(
10027                params.text_document.uri,
10028                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10029            );
10030            futures::future::pending::<()>().await;
10031            unreachable!()
10032        },
10033    );
10034    let format = editor
10035        .update_in(cx, |editor, window, cx| {
10036            editor.perform_format(
10037                project,
10038                FormatTrigger::Manual,
10039                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10040                window,
10041                cx,
10042            )
10043        })
10044        .unwrap();
10045    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10046    cx.executor().start_waiting();
10047    format.await;
10048    assert_eq!(
10049        editor.update(cx, |editor, cx| editor.text(cx)),
10050        "one\ntwo\nthree\n"
10051    );
10052}
10053
10054#[gpui::test]
10055async fn test_multiple_formatters(cx: &mut TestAppContext) {
10056    init_test(cx, |settings| {
10057        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10058        settings.defaults.formatter =
10059            Some(language_settings::SelectedFormatter::List(FormatterList(
10060                vec![
10061                    Formatter::LanguageServer { name: None },
10062                    Formatter::CodeActions(
10063                        [
10064                            ("code-action-1".into(), true),
10065                            ("code-action-2".into(), true),
10066                        ]
10067                        .into_iter()
10068                        .collect(),
10069                    ),
10070                ]
10071                .into(),
10072            )))
10073    });
10074
10075    let fs = FakeFs::new(cx.executor());
10076    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10077        .await;
10078
10079    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10080    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10081    language_registry.add(rust_lang());
10082
10083    let mut fake_servers = language_registry.register_fake_lsp(
10084        "Rust",
10085        FakeLspAdapter {
10086            capabilities: lsp::ServerCapabilities {
10087                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10088                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10089                    commands: vec!["the-command-for-code-action-1".into()],
10090                    ..Default::default()
10091                }),
10092                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10093                ..Default::default()
10094            },
10095            ..Default::default()
10096        },
10097    );
10098
10099    let buffer = project
10100        .update(cx, |project, cx| {
10101            project.open_local_buffer(path!("/file.rs"), cx)
10102        })
10103        .await
10104        .unwrap();
10105
10106    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10107    let (editor, cx) = cx.add_window_view(|window, cx| {
10108        build_editor_with_project(project.clone(), buffer, window, cx)
10109    });
10110
10111    cx.executor().start_waiting();
10112
10113    let fake_server = fake_servers.next().await.unwrap();
10114    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10115        move |_params, _| async move {
10116            Ok(Some(vec![lsp::TextEdit::new(
10117                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10118                "applied-formatting\n".to_string(),
10119            )]))
10120        },
10121    );
10122    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10123        move |params, _| async move {
10124            assert_eq!(
10125                params.context.only,
10126                Some(vec!["code-action-1".into(), "code-action-2".into()])
10127            );
10128            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10129            Ok(Some(vec![
10130                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10131                    kind: Some("code-action-1".into()),
10132                    edit: Some(lsp::WorkspaceEdit::new(
10133                        [(
10134                            uri.clone(),
10135                            vec![lsp::TextEdit::new(
10136                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10137                                "applied-code-action-1-edit\n".to_string(),
10138                            )],
10139                        )]
10140                        .into_iter()
10141                        .collect(),
10142                    )),
10143                    command: Some(lsp::Command {
10144                        command: "the-command-for-code-action-1".into(),
10145                        ..Default::default()
10146                    }),
10147                    ..Default::default()
10148                }),
10149                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10150                    kind: Some("code-action-2".into()),
10151                    edit: Some(lsp::WorkspaceEdit::new(
10152                        [(
10153                            uri.clone(),
10154                            vec![lsp::TextEdit::new(
10155                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10156                                "applied-code-action-2-edit\n".to_string(),
10157                            )],
10158                        )]
10159                        .into_iter()
10160                        .collect(),
10161                    )),
10162                    ..Default::default()
10163                }),
10164            ]))
10165        },
10166    );
10167
10168    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10169        move |params, _| async move { Ok(params) }
10170    });
10171
10172    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10173    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10174        let fake = fake_server.clone();
10175        let lock = command_lock.clone();
10176        move |params, _| {
10177            assert_eq!(params.command, "the-command-for-code-action-1");
10178            let fake = fake.clone();
10179            let lock = lock.clone();
10180            async move {
10181                lock.lock().await;
10182                fake.server
10183                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10184                        label: None,
10185                        edit: lsp::WorkspaceEdit {
10186                            changes: Some(
10187                                [(
10188                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10189                                    vec![lsp::TextEdit {
10190                                        range: lsp::Range::new(
10191                                            lsp::Position::new(0, 0),
10192                                            lsp::Position::new(0, 0),
10193                                        ),
10194                                        new_text: "applied-code-action-1-command\n".into(),
10195                                    }],
10196                                )]
10197                                .into_iter()
10198                                .collect(),
10199                            ),
10200                            ..Default::default()
10201                        },
10202                    })
10203                    .await
10204                    .into_response()
10205                    .unwrap();
10206                Ok(Some(json!(null)))
10207            }
10208        }
10209    });
10210
10211    cx.executor().start_waiting();
10212    editor
10213        .update_in(cx, |editor, window, cx| {
10214            editor.perform_format(
10215                project.clone(),
10216                FormatTrigger::Manual,
10217                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10218                window,
10219                cx,
10220            )
10221        })
10222        .unwrap()
10223        .await;
10224    editor.update(cx, |editor, cx| {
10225        assert_eq!(
10226            editor.text(cx),
10227            r#"
10228                applied-code-action-2-edit
10229                applied-code-action-1-command
10230                applied-code-action-1-edit
10231                applied-formatting
10232                one
10233                two
10234                three
10235            "#
10236            .unindent()
10237        );
10238    });
10239
10240    editor.update_in(cx, |editor, window, cx| {
10241        editor.undo(&Default::default(), window, cx);
10242        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10243    });
10244
10245    // Perform a manual edit while waiting for an LSP command
10246    // that's being run as part of a formatting code action.
10247    let lock_guard = command_lock.lock().await;
10248    let format = editor
10249        .update_in(cx, |editor, window, cx| {
10250            editor.perform_format(
10251                project.clone(),
10252                FormatTrigger::Manual,
10253                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10254                window,
10255                cx,
10256            )
10257        })
10258        .unwrap();
10259    cx.run_until_parked();
10260    editor.update(cx, |editor, cx| {
10261        assert_eq!(
10262            editor.text(cx),
10263            r#"
10264                applied-code-action-1-edit
10265                applied-formatting
10266                one
10267                two
10268                three
10269            "#
10270            .unindent()
10271        );
10272
10273        editor.buffer.update(cx, |buffer, cx| {
10274            let ix = buffer.len(cx);
10275            buffer.edit([(ix..ix, "edited\n")], None, cx);
10276        });
10277    });
10278
10279    // Allow the LSP command to proceed. Because the buffer was edited,
10280    // the second code action will not be run.
10281    drop(lock_guard);
10282    format.await;
10283    editor.update_in(cx, |editor, window, cx| {
10284        assert_eq!(
10285            editor.text(cx),
10286            r#"
10287                applied-code-action-1-command
10288                applied-code-action-1-edit
10289                applied-formatting
10290                one
10291                two
10292                three
10293                edited
10294            "#
10295            .unindent()
10296        );
10297
10298        // The manual edit is undone first, because it is the last thing the user did
10299        // (even though the command completed afterwards).
10300        editor.undo(&Default::default(), window, cx);
10301        assert_eq!(
10302            editor.text(cx),
10303            r#"
10304                applied-code-action-1-command
10305                applied-code-action-1-edit
10306                applied-formatting
10307                one
10308                two
10309                three
10310            "#
10311            .unindent()
10312        );
10313
10314        // All the formatting (including the command, which completed after the manual edit)
10315        // is undone together.
10316        editor.undo(&Default::default(), window, cx);
10317        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10318    });
10319}
10320
10321#[gpui::test]
10322async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10323    init_test(cx, |settings| {
10324        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10325            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10326        ))
10327    });
10328
10329    let fs = FakeFs::new(cx.executor());
10330    fs.insert_file(path!("/file.ts"), Default::default()).await;
10331
10332    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10333
10334    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10335    language_registry.add(Arc::new(Language::new(
10336        LanguageConfig {
10337            name: "TypeScript".into(),
10338            matcher: LanguageMatcher {
10339                path_suffixes: vec!["ts".to_string()],
10340                ..Default::default()
10341            },
10342            ..LanguageConfig::default()
10343        },
10344        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10345    )));
10346    update_test_language_settings(cx, |settings| {
10347        settings.defaults.prettier = Some(PrettierSettings {
10348            allowed: true,
10349            ..PrettierSettings::default()
10350        });
10351    });
10352    let mut fake_servers = language_registry.register_fake_lsp(
10353        "TypeScript",
10354        FakeLspAdapter {
10355            capabilities: lsp::ServerCapabilities {
10356                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10357                ..Default::default()
10358            },
10359            ..Default::default()
10360        },
10361    );
10362
10363    let buffer = project
10364        .update(cx, |project, cx| {
10365            project.open_local_buffer(path!("/file.ts"), cx)
10366        })
10367        .await
10368        .unwrap();
10369
10370    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10371    let (editor, cx) = cx.add_window_view(|window, cx| {
10372        build_editor_with_project(project.clone(), buffer, window, cx)
10373    });
10374    editor.update_in(cx, |editor, window, cx| {
10375        editor.set_text(
10376            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10377            window,
10378            cx,
10379        )
10380    });
10381
10382    cx.executor().start_waiting();
10383    let fake_server = fake_servers.next().await.unwrap();
10384
10385    let format = editor
10386        .update_in(cx, |editor, window, cx| {
10387            editor.perform_code_action_kind(
10388                project.clone(),
10389                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10390                window,
10391                cx,
10392            )
10393        })
10394        .unwrap();
10395    fake_server
10396        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10397            assert_eq!(
10398                params.text_document.uri,
10399                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10400            );
10401            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10402                lsp::CodeAction {
10403                    title: "Organize Imports".to_string(),
10404                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10405                    edit: Some(lsp::WorkspaceEdit {
10406                        changes: Some(
10407                            [(
10408                                params.text_document.uri.clone(),
10409                                vec![lsp::TextEdit::new(
10410                                    lsp::Range::new(
10411                                        lsp::Position::new(1, 0),
10412                                        lsp::Position::new(2, 0),
10413                                    ),
10414                                    "".to_string(),
10415                                )],
10416                            )]
10417                            .into_iter()
10418                            .collect(),
10419                        ),
10420                        ..Default::default()
10421                    }),
10422                    ..Default::default()
10423                },
10424            )]))
10425        })
10426        .next()
10427        .await;
10428    cx.executor().start_waiting();
10429    format.await;
10430    assert_eq!(
10431        editor.update(cx, |editor, cx| editor.text(cx)),
10432        "import { a } from 'module';\n\nconst x = a;\n"
10433    );
10434
10435    editor.update_in(cx, |editor, window, cx| {
10436        editor.set_text(
10437            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10438            window,
10439            cx,
10440        )
10441    });
10442    // Ensure we don't lock if code action hangs.
10443    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10444        move |params, _| async move {
10445            assert_eq!(
10446                params.text_document.uri,
10447                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10448            );
10449            futures::future::pending::<()>().await;
10450            unreachable!()
10451        },
10452    );
10453    let format = editor
10454        .update_in(cx, |editor, window, cx| {
10455            editor.perform_code_action_kind(
10456                project,
10457                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10458                window,
10459                cx,
10460            )
10461        })
10462        .unwrap();
10463    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10464    cx.executor().start_waiting();
10465    format.await;
10466    assert_eq!(
10467        editor.update(cx, |editor, cx| editor.text(cx)),
10468        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10469    );
10470}
10471
10472#[gpui::test]
10473async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10474    init_test(cx, |_| {});
10475
10476    let mut cx = EditorLspTestContext::new_rust(
10477        lsp::ServerCapabilities {
10478            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10479            ..Default::default()
10480        },
10481        cx,
10482    )
10483    .await;
10484
10485    cx.set_state(indoc! {"
10486        one.twoˇ
10487    "});
10488
10489    // The format request takes a long time. When it completes, it inserts
10490    // a newline and an indent before the `.`
10491    cx.lsp
10492        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10493            let executor = cx.background_executor().clone();
10494            async move {
10495                executor.timer(Duration::from_millis(100)).await;
10496                Ok(Some(vec![lsp::TextEdit {
10497                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10498                    new_text: "\n    ".into(),
10499                }]))
10500            }
10501        });
10502
10503    // Submit a format request.
10504    let format_1 = cx
10505        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10506        .unwrap();
10507    cx.executor().run_until_parked();
10508
10509    // Submit a second format request.
10510    let format_2 = cx
10511        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10512        .unwrap();
10513    cx.executor().run_until_parked();
10514
10515    // Wait for both format requests to complete
10516    cx.executor().advance_clock(Duration::from_millis(200));
10517    cx.executor().start_waiting();
10518    format_1.await.unwrap();
10519    cx.executor().start_waiting();
10520    format_2.await.unwrap();
10521
10522    // The formatting edits only happens once.
10523    cx.assert_editor_state(indoc! {"
10524        one
10525            .twoˇ
10526    "});
10527}
10528
10529#[gpui::test]
10530async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10531    init_test(cx, |settings| {
10532        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10533    });
10534
10535    let mut cx = EditorLspTestContext::new_rust(
10536        lsp::ServerCapabilities {
10537            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10538            ..Default::default()
10539        },
10540        cx,
10541    )
10542    .await;
10543
10544    // Set up a buffer white some trailing whitespace and no trailing newline.
10545    cx.set_state(
10546        &[
10547            "one ",   //
10548            "twoˇ",   //
10549            "three ", //
10550            "four",   //
10551        ]
10552        .join("\n"),
10553    );
10554
10555    // Submit a format request.
10556    let format = cx
10557        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10558        .unwrap();
10559
10560    // Record which buffer changes have been sent to the language server
10561    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10562    cx.lsp
10563        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10564            let buffer_changes = buffer_changes.clone();
10565            move |params, _| {
10566                buffer_changes.lock().extend(
10567                    params
10568                        .content_changes
10569                        .into_iter()
10570                        .map(|e| (e.range.unwrap(), e.text)),
10571                );
10572            }
10573        });
10574
10575    // Handle formatting requests to the language server.
10576    cx.lsp
10577        .set_request_handler::<lsp::request::Formatting, _, _>({
10578            let buffer_changes = buffer_changes.clone();
10579            move |_, _| {
10580                // When formatting is requested, trailing whitespace has already been stripped,
10581                // and the trailing newline has already been added.
10582                assert_eq!(
10583                    &buffer_changes.lock()[1..],
10584                    &[
10585                        (
10586                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10587                            "".into()
10588                        ),
10589                        (
10590                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10591                            "".into()
10592                        ),
10593                        (
10594                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10595                            "\n".into()
10596                        ),
10597                    ]
10598                );
10599
10600                // Insert blank lines between each line of the buffer.
10601                async move {
10602                    Ok(Some(vec![
10603                        lsp::TextEdit {
10604                            range: lsp::Range::new(
10605                                lsp::Position::new(1, 0),
10606                                lsp::Position::new(1, 0),
10607                            ),
10608                            new_text: "\n".into(),
10609                        },
10610                        lsp::TextEdit {
10611                            range: lsp::Range::new(
10612                                lsp::Position::new(2, 0),
10613                                lsp::Position::new(2, 0),
10614                            ),
10615                            new_text: "\n".into(),
10616                        },
10617                    ]))
10618                }
10619            }
10620        });
10621
10622    // After formatting the buffer, the trailing whitespace is stripped,
10623    // a newline is appended, and the edits provided by the language server
10624    // have been applied.
10625    format.await.unwrap();
10626    cx.assert_editor_state(
10627        &[
10628            "one",   //
10629            "",      //
10630            "twoˇ",  //
10631            "",      //
10632            "three", //
10633            "four",  //
10634            "",      //
10635        ]
10636        .join("\n"),
10637    );
10638
10639    // Undoing the formatting undoes the trailing whitespace removal, the
10640    // trailing newline, and the LSP edits.
10641    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10642    cx.assert_editor_state(
10643        &[
10644            "one ",   //
10645            "twoˇ",   //
10646            "three ", //
10647            "four",   //
10648        ]
10649        .join("\n"),
10650    );
10651}
10652
10653#[gpui::test]
10654async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10655    cx: &mut TestAppContext,
10656) {
10657    init_test(cx, |_| {});
10658
10659    cx.update(|cx| {
10660        cx.update_global::<SettingsStore, _>(|settings, cx| {
10661            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10662                settings.auto_signature_help = Some(true);
10663            });
10664        });
10665    });
10666
10667    let mut cx = EditorLspTestContext::new_rust(
10668        lsp::ServerCapabilities {
10669            signature_help_provider: Some(lsp::SignatureHelpOptions {
10670                ..Default::default()
10671            }),
10672            ..Default::default()
10673        },
10674        cx,
10675    )
10676    .await;
10677
10678    let language = Language::new(
10679        LanguageConfig {
10680            name: "Rust".into(),
10681            brackets: BracketPairConfig {
10682                pairs: vec![
10683                    BracketPair {
10684                        start: "{".to_string(),
10685                        end: "}".to_string(),
10686                        close: true,
10687                        surround: true,
10688                        newline: true,
10689                    },
10690                    BracketPair {
10691                        start: "(".to_string(),
10692                        end: ")".to_string(),
10693                        close: true,
10694                        surround: true,
10695                        newline: true,
10696                    },
10697                    BracketPair {
10698                        start: "/*".to_string(),
10699                        end: " */".to_string(),
10700                        close: true,
10701                        surround: true,
10702                        newline: true,
10703                    },
10704                    BracketPair {
10705                        start: "[".to_string(),
10706                        end: "]".to_string(),
10707                        close: false,
10708                        surround: false,
10709                        newline: true,
10710                    },
10711                    BracketPair {
10712                        start: "\"".to_string(),
10713                        end: "\"".to_string(),
10714                        close: true,
10715                        surround: true,
10716                        newline: false,
10717                    },
10718                    BracketPair {
10719                        start: "<".to_string(),
10720                        end: ">".to_string(),
10721                        close: false,
10722                        surround: true,
10723                        newline: true,
10724                    },
10725                ],
10726                ..Default::default()
10727            },
10728            autoclose_before: "})]".to_string(),
10729            ..Default::default()
10730        },
10731        Some(tree_sitter_rust::LANGUAGE.into()),
10732    );
10733    let language = Arc::new(language);
10734
10735    cx.language_registry().add(language.clone());
10736    cx.update_buffer(|buffer, cx| {
10737        buffer.set_language(Some(language), cx);
10738    });
10739
10740    cx.set_state(
10741        &r#"
10742            fn main() {
10743                sampleˇ
10744            }
10745        "#
10746        .unindent(),
10747    );
10748
10749    cx.update_editor(|editor, window, cx| {
10750        editor.handle_input("(", window, cx);
10751    });
10752    cx.assert_editor_state(
10753        &"
10754            fn main() {
10755                sample(ˇ)
10756            }
10757        "
10758        .unindent(),
10759    );
10760
10761    let mocked_response = lsp::SignatureHelp {
10762        signatures: vec![lsp::SignatureInformation {
10763            label: "fn sample(param1: u8, param2: u8)".to_string(),
10764            documentation: None,
10765            parameters: Some(vec![
10766                lsp::ParameterInformation {
10767                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10768                    documentation: None,
10769                },
10770                lsp::ParameterInformation {
10771                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10772                    documentation: None,
10773                },
10774            ]),
10775            active_parameter: None,
10776        }],
10777        active_signature: Some(0),
10778        active_parameter: Some(0),
10779    };
10780    handle_signature_help_request(&mut cx, mocked_response).await;
10781
10782    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10783        .await;
10784
10785    cx.editor(|editor, _, _| {
10786        let signature_help_state = editor.signature_help_state.popover().cloned();
10787        assert_eq!(
10788            signature_help_state.unwrap().label,
10789            "param1: u8, param2: u8"
10790        );
10791    });
10792}
10793
10794#[gpui::test]
10795async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10796    init_test(cx, |_| {});
10797
10798    cx.update(|cx| {
10799        cx.update_global::<SettingsStore, _>(|settings, cx| {
10800            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10801                settings.auto_signature_help = Some(false);
10802                settings.show_signature_help_after_edits = Some(false);
10803            });
10804        });
10805    });
10806
10807    let mut cx = EditorLspTestContext::new_rust(
10808        lsp::ServerCapabilities {
10809            signature_help_provider: Some(lsp::SignatureHelpOptions {
10810                ..Default::default()
10811            }),
10812            ..Default::default()
10813        },
10814        cx,
10815    )
10816    .await;
10817
10818    let language = Language::new(
10819        LanguageConfig {
10820            name: "Rust".into(),
10821            brackets: BracketPairConfig {
10822                pairs: vec![
10823                    BracketPair {
10824                        start: "{".to_string(),
10825                        end: "}".to_string(),
10826                        close: true,
10827                        surround: true,
10828                        newline: true,
10829                    },
10830                    BracketPair {
10831                        start: "(".to_string(),
10832                        end: ")".to_string(),
10833                        close: true,
10834                        surround: true,
10835                        newline: true,
10836                    },
10837                    BracketPair {
10838                        start: "/*".to_string(),
10839                        end: " */".to_string(),
10840                        close: true,
10841                        surround: true,
10842                        newline: true,
10843                    },
10844                    BracketPair {
10845                        start: "[".to_string(),
10846                        end: "]".to_string(),
10847                        close: false,
10848                        surround: false,
10849                        newline: true,
10850                    },
10851                    BracketPair {
10852                        start: "\"".to_string(),
10853                        end: "\"".to_string(),
10854                        close: true,
10855                        surround: true,
10856                        newline: false,
10857                    },
10858                    BracketPair {
10859                        start: "<".to_string(),
10860                        end: ">".to_string(),
10861                        close: false,
10862                        surround: true,
10863                        newline: true,
10864                    },
10865                ],
10866                ..Default::default()
10867            },
10868            autoclose_before: "})]".to_string(),
10869            ..Default::default()
10870        },
10871        Some(tree_sitter_rust::LANGUAGE.into()),
10872    );
10873    let language = Arc::new(language);
10874
10875    cx.language_registry().add(language.clone());
10876    cx.update_buffer(|buffer, cx| {
10877        buffer.set_language(Some(language), cx);
10878    });
10879
10880    // Ensure that signature_help is not called when no signature help is enabled.
10881    cx.set_state(
10882        &r#"
10883            fn main() {
10884                sampleˇ
10885            }
10886        "#
10887        .unindent(),
10888    );
10889    cx.update_editor(|editor, window, cx| {
10890        editor.handle_input("(", window, cx);
10891    });
10892    cx.assert_editor_state(
10893        &"
10894            fn main() {
10895                sample(ˇ)
10896            }
10897        "
10898        .unindent(),
10899    );
10900    cx.editor(|editor, _, _| {
10901        assert!(editor.signature_help_state.task().is_none());
10902    });
10903
10904    let mocked_response = lsp::SignatureHelp {
10905        signatures: vec![lsp::SignatureInformation {
10906            label: "fn sample(param1: u8, param2: u8)".to_string(),
10907            documentation: None,
10908            parameters: Some(vec![
10909                lsp::ParameterInformation {
10910                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10911                    documentation: None,
10912                },
10913                lsp::ParameterInformation {
10914                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10915                    documentation: None,
10916                },
10917            ]),
10918            active_parameter: None,
10919        }],
10920        active_signature: Some(0),
10921        active_parameter: Some(0),
10922    };
10923
10924    // Ensure that signature_help is called when enabled afte edits
10925    cx.update(|_, cx| {
10926        cx.update_global::<SettingsStore, _>(|settings, cx| {
10927            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10928                settings.auto_signature_help = Some(false);
10929                settings.show_signature_help_after_edits = Some(true);
10930            });
10931        });
10932    });
10933    cx.set_state(
10934        &r#"
10935            fn main() {
10936                sampleˇ
10937            }
10938        "#
10939        .unindent(),
10940    );
10941    cx.update_editor(|editor, window, cx| {
10942        editor.handle_input("(", window, cx);
10943    });
10944    cx.assert_editor_state(
10945        &"
10946            fn main() {
10947                sample(ˇ)
10948            }
10949        "
10950        .unindent(),
10951    );
10952    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10953    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10954        .await;
10955    cx.update_editor(|editor, _, _| {
10956        let signature_help_state = editor.signature_help_state.popover().cloned();
10957        assert!(signature_help_state.is_some());
10958        assert_eq!(
10959            signature_help_state.unwrap().label,
10960            "param1: u8, param2: u8"
10961        );
10962        editor.signature_help_state = SignatureHelpState::default();
10963    });
10964
10965    // Ensure that signature_help is called when auto signature help override is enabled
10966    cx.update(|_, cx| {
10967        cx.update_global::<SettingsStore, _>(|settings, cx| {
10968            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10969                settings.auto_signature_help = Some(true);
10970                settings.show_signature_help_after_edits = Some(false);
10971            });
10972        });
10973    });
10974    cx.set_state(
10975        &r#"
10976            fn main() {
10977                sampleˇ
10978            }
10979        "#
10980        .unindent(),
10981    );
10982    cx.update_editor(|editor, window, cx| {
10983        editor.handle_input("(", window, cx);
10984    });
10985    cx.assert_editor_state(
10986        &"
10987            fn main() {
10988                sample(ˇ)
10989            }
10990        "
10991        .unindent(),
10992    );
10993    handle_signature_help_request(&mut cx, mocked_response).await;
10994    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10995        .await;
10996    cx.editor(|editor, _, _| {
10997        let signature_help_state = editor.signature_help_state.popover().cloned();
10998        assert!(signature_help_state.is_some());
10999        assert_eq!(
11000            signature_help_state.unwrap().label,
11001            "param1: u8, param2: u8"
11002        );
11003    });
11004}
11005
11006#[gpui::test]
11007async fn test_signature_help(cx: &mut TestAppContext) {
11008    init_test(cx, |_| {});
11009    cx.update(|cx| {
11010        cx.update_global::<SettingsStore, _>(|settings, cx| {
11011            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11012                settings.auto_signature_help = Some(true);
11013            });
11014        });
11015    });
11016
11017    let mut cx = EditorLspTestContext::new_rust(
11018        lsp::ServerCapabilities {
11019            signature_help_provider: Some(lsp::SignatureHelpOptions {
11020                ..Default::default()
11021            }),
11022            ..Default::default()
11023        },
11024        cx,
11025    )
11026    .await;
11027
11028    // A test that directly calls `show_signature_help`
11029    cx.update_editor(|editor, window, cx| {
11030        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11031    });
11032
11033    let mocked_response = lsp::SignatureHelp {
11034        signatures: vec![lsp::SignatureInformation {
11035            label: "fn sample(param1: u8, param2: u8)".to_string(),
11036            documentation: None,
11037            parameters: Some(vec![
11038                lsp::ParameterInformation {
11039                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11040                    documentation: None,
11041                },
11042                lsp::ParameterInformation {
11043                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11044                    documentation: None,
11045                },
11046            ]),
11047            active_parameter: None,
11048        }],
11049        active_signature: Some(0),
11050        active_parameter: Some(0),
11051    };
11052    handle_signature_help_request(&mut cx, mocked_response).await;
11053
11054    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11055        .await;
11056
11057    cx.editor(|editor, _, _| {
11058        let signature_help_state = editor.signature_help_state.popover().cloned();
11059        assert!(signature_help_state.is_some());
11060        assert_eq!(
11061            signature_help_state.unwrap().label,
11062            "param1: u8, param2: u8"
11063        );
11064    });
11065
11066    // When exiting outside from inside the brackets, `signature_help` is closed.
11067    cx.set_state(indoc! {"
11068        fn main() {
11069            sample(ˇ);
11070        }
11071
11072        fn sample(param1: u8, param2: u8) {}
11073    "});
11074
11075    cx.update_editor(|editor, window, cx| {
11076        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11077    });
11078
11079    let mocked_response = lsp::SignatureHelp {
11080        signatures: Vec::new(),
11081        active_signature: None,
11082        active_parameter: None,
11083    };
11084    handle_signature_help_request(&mut cx, mocked_response).await;
11085
11086    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11087        .await;
11088
11089    cx.editor(|editor, _, _| {
11090        assert!(!editor.signature_help_state.is_shown());
11091    });
11092
11093    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11094    cx.set_state(indoc! {"
11095        fn main() {
11096            sample(ˇ);
11097        }
11098
11099        fn sample(param1: u8, param2: u8) {}
11100    "});
11101
11102    let mocked_response = lsp::SignatureHelp {
11103        signatures: vec![lsp::SignatureInformation {
11104            label: "fn sample(param1: u8, param2: u8)".to_string(),
11105            documentation: None,
11106            parameters: Some(vec![
11107                lsp::ParameterInformation {
11108                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11109                    documentation: None,
11110                },
11111                lsp::ParameterInformation {
11112                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11113                    documentation: None,
11114                },
11115            ]),
11116            active_parameter: None,
11117        }],
11118        active_signature: Some(0),
11119        active_parameter: Some(0),
11120    };
11121    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11122    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11123        .await;
11124    cx.editor(|editor, _, _| {
11125        assert!(editor.signature_help_state.is_shown());
11126    });
11127
11128    // Restore the popover with more parameter input
11129    cx.set_state(indoc! {"
11130        fn main() {
11131            sample(param1, param2ˇ);
11132        }
11133
11134        fn sample(param1: u8, param2: u8) {}
11135    "});
11136
11137    let mocked_response = lsp::SignatureHelp {
11138        signatures: vec![lsp::SignatureInformation {
11139            label: "fn sample(param1: u8, param2: u8)".to_string(),
11140            documentation: None,
11141            parameters: Some(vec![
11142                lsp::ParameterInformation {
11143                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11144                    documentation: None,
11145                },
11146                lsp::ParameterInformation {
11147                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11148                    documentation: None,
11149                },
11150            ]),
11151            active_parameter: None,
11152        }],
11153        active_signature: Some(0),
11154        active_parameter: Some(1),
11155    };
11156    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11157    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11158        .await;
11159
11160    // When selecting a range, the popover is gone.
11161    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11162    cx.update_editor(|editor, window, cx| {
11163        editor.change_selections(None, window, cx, |s| {
11164            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11165        })
11166    });
11167    cx.assert_editor_state(indoc! {"
11168        fn main() {
11169            sample(param1, «ˇparam2»);
11170        }
11171
11172        fn sample(param1: u8, param2: u8) {}
11173    "});
11174    cx.editor(|editor, _, _| {
11175        assert!(!editor.signature_help_state.is_shown());
11176    });
11177
11178    // When unselecting again, the popover is back if within the brackets.
11179    cx.update_editor(|editor, window, cx| {
11180        editor.change_selections(None, window, cx, |s| {
11181            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11182        })
11183    });
11184    cx.assert_editor_state(indoc! {"
11185        fn main() {
11186            sample(param1, ˇparam2);
11187        }
11188
11189        fn sample(param1: u8, param2: u8) {}
11190    "});
11191    handle_signature_help_request(&mut cx, mocked_response).await;
11192    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11193        .await;
11194    cx.editor(|editor, _, _| {
11195        assert!(editor.signature_help_state.is_shown());
11196    });
11197
11198    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11199    cx.update_editor(|editor, window, cx| {
11200        editor.change_selections(None, window, cx, |s| {
11201            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11202            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11203        })
11204    });
11205    cx.assert_editor_state(indoc! {"
11206        fn main() {
11207            sample(param1, ˇparam2);
11208        }
11209
11210        fn sample(param1: u8, param2: u8) {}
11211    "});
11212
11213    let mocked_response = lsp::SignatureHelp {
11214        signatures: vec![lsp::SignatureInformation {
11215            label: "fn sample(param1: u8, param2: u8)".to_string(),
11216            documentation: None,
11217            parameters: Some(vec![
11218                lsp::ParameterInformation {
11219                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11220                    documentation: None,
11221                },
11222                lsp::ParameterInformation {
11223                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11224                    documentation: None,
11225                },
11226            ]),
11227            active_parameter: None,
11228        }],
11229        active_signature: Some(0),
11230        active_parameter: Some(1),
11231    };
11232    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11233    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11234        .await;
11235    cx.update_editor(|editor, _, cx| {
11236        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11237    });
11238    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11239        .await;
11240    cx.update_editor(|editor, window, cx| {
11241        editor.change_selections(None, window, cx, |s| {
11242            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11243        })
11244    });
11245    cx.assert_editor_state(indoc! {"
11246        fn main() {
11247            sample(param1, «ˇparam2»);
11248        }
11249
11250        fn sample(param1: u8, param2: u8) {}
11251    "});
11252    cx.update_editor(|editor, window, cx| {
11253        editor.change_selections(None, window, cx, |s| {
11254            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11255        })
11256    });
11257    cx.assert_editor_state(indoc! {"
11258        fn main() {
11259            sample(param1, ˇparam2);
11260        }
11261
11262        fn sample(param1: u8, param2: u8) {}
11263    "});
11264    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11265        .await;
11266}
11267
11268#[gpui::test]
11269async fn test_completion_mode(cx: &mut TestAppContext) {
11270    init_test(cx, |_| {});
11271    let mut cx = EditorLspTestContext::new_rust(
11272        lsp::ServerCapabilities {
11273            completion_provider: Some(lsp::CompletionOptions {
11274                resolve_provider: Some(true),
11275                ..Default::default()
11276            }),
11277            ..Default::default()
11278        },
11279        cx,
11280    )
11281    .await;
11282
11283    struct Run {
11284        run_description: &'static str,
11285        initial_state: String,
11286        buffer_marked_text: String,
11287        completion_label: &'static str,
11288        completion_text: &'static str,
11289        expected_with_insert_mode: String,
11290        expected_with_replace_mode: String,
11291        expected_with_replace_subsequence_mode: String,
11292        expected_with_replace_suffix_mode: String,
11293    }
11294
11295    let runs = [
11296        Run {
11297            run_description: "Start of word matches completion text",
11298            initial_state: "before ediˇ after".into(),
11299            buffer_marked_text: "before <edi|> after".into(),
11300            completion_label: "editor",
11301            completion_text: "editor",
11302            expected_with_insert_mode: "before editorˇ after".into(),
11303            expected_with_replace_mode: "before editorˇ after".into(),
11304            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11305            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11306        },
11307        Run {
11308            run_description: "Accept same text at the middle of the word",
11309            initial_state: "before ediˇtor after".into(),
11310            buffer_marked_text: "before <edi|tor> after".into(),
11311            completion_label: "editor",
11312            completion_text: "editor",
11313            expected_with_insert_mode: "before editorˇtor after".into(),
11314            expected_with_replace_mode: "before editorˇ after".into(),
11315            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11316            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11317        },
11318        Run {
11319            run_description: "End of word matches completion text -- cursor at end",
11320            initial_state: "before torˇ after".into(),
11321            buffer_marked_text: "before <tor|> after".into(),
11322            completion_label: "editor",
11323            completion_text: "editor",
11324            expected_with_insert_mode: "before editorˇ after".into(),
11325            expected_with_replace_mode: "before editorˇ after".into(),
11326            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11327            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11328        },
11329        Run {
11330            run_description: "End of word matches completion text -- cursor at start",
11331            initial_state: "before ˇtor after".into(),
11332            buffer_marked_text: "before <|tor> after".into(),
11333            completion_label: "editor",
11334            completion_text: "editor",
11335            expected_with_insert_mode: "before editorˇtor after".into(),
11336            expected_with_replace_mode: "before editorˇ after".into(),
11337            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11338            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11339        },
11340        Run {
11341            run_description: "Prepend text containing whitespace",
11342            initial_state: "pˇfield: bool".into(),
11343            buffer_marked_text: "<p|field>: bool".into(),
11344            completion_label: "pub ",
11345            completion_text: "pub ",
11346            expected_with_insert_mode: "pub ˇfield: bool".into(),
11347            expected_with_replace_mode: "pub ˇ: bool".into(),
11348            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11349            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11350        },
11351        Run {
11352            run_description: "Add element to start of list",
11353            initial_state: "[element_ˇelement_2]".into(),
11354            buffer_marked_text: "[<element_|element_2>]".into(),
11355            completion_label: "element_1",
11356            completion_text: "element_1",
11357            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11358            expected_with_replace_mode: "[element_1ˇ]".into(),
11359            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11360            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11361        },
11362        Run {
11363            run_description: "Add element to start of list -- first and second elements are equal",
11364            initial_state: "[elˇelement]".into(),
11365            buffer_marked_text: "[<el|element>]".into(),
11366            completion_label: "element",
11367            completion_text: "element",
11368            expected_with_insert_mode: "[elementˇelement]".into(),
11369            expected_with_replace_mode: "[elementˇ]".into(),
11370            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11371            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11372        },
11373        Run {
11374            run_description: "Ends with matching suffix",
11375            initial_state: "SubˇError".into(),
11376            buffer_marked_text: "<Sub|Error>".into(),
11377            completion_label: "SubscriptionError",
11378            completion_text: "SubscriptionError",
11379            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11380            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11381            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11382            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11383        },
11384        Run {
11385            run_description: "Suffix is a subsequence -- contiguous",
11386            initial_state: "SubˇErr".into(),
11387            buffer_marked_text: "<Sub|Err>".into(),
11388            completion_label: "SubscriptionError",
11389            completion_text: "SubscriptionError",
11390            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11391            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11392            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11393            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11394        },
11395        Run {
11396            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11397            initial_state: "Suˇscrirr".into(),
11398            buffer_marked_text: "<Su|scrirr>".into(),
11399            completion_label: "SubscriptionError",
11400            completion_text: "SubscriptionError",
11401            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11402            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11403            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11404            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11405        },
11406        Run {
11407            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11408            initial_state: "foo(indˇix)".into(),
11409            buffer_marked_text: "foo(<ind|ix>)".into(),
11410            completion_label: "node_index",
11411            completion_text: "node_index",
11412            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11413            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11414            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11415            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11416        },
11417        Run {
11418            run_description: "Replace range ends before cursor - should extend to cursor",
11419            initial_state: "before editˇo after".into(),
11420            buffer_marked_text: "before <{ed}>it|o after".into(),
11421            completion_label: "editor",
11422            completion_text: "editor",
11423            expected_with_insert_mode: "before editorˇo after".into(),
11424            expected_with_replace_mode: "before editorˇo after".into(),
11425            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11426            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11427        },
11428        Run {
11429            run_description: "Uses label for suffix matching",
11430            initial_state: "before ediˇtor after".into(),
11431            buffer_marked_text: "before <edi|tor> after".into(),
11432            completion_label: "editor",
11433            completion_text: "editor()",
11434            expected_with_insert_mode: "before editor()ˇtor after".into(),
11435            expected_with_replace_mode: "before editor()ˇ after".into(),
11436            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11437            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11438        },
11439        Run {
11440            run_description: "Case insensitive subsequence and suffix matching",
11441            initial_state: "before EDiˇtoR after".into(),
11442            buffer_marked_text: "before <EDi|toR> after".into(),
11443            completion_label: "editor",
11444            completion_text: "editor",
11445            expected_with_insert_mode: "before editorˇtoR after".into(),
11446            expected_with_replace_mode: "before editorˇ after".into(),
11447            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11448            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11449        },
11450    ];
11451
11452    for run in runs {
11453        let run_variations = [
11454            (LspInsertMode::Insert, run.expected_with_insert_mode),
11455            (LspInsertMode::Replace, run.expected_with_replace_mode),
11456            (
11457                LspInsertMode::ReplaceSubsequence,
11458                run.expected_with_replace_subsequence_mode,
11459            ),
11460            (
11461                LspInsertMode::ReplaceSuffix,
11462                run.expected_with_replace_suffix_mode,
11463            ),
11464        ];
11465
11466        for (lsp_insert_mode, expected_text) in run_variations {
11467            eprintln!(
11468                "run = {:?}, mode = {lsp_insert_mode:.?}",
11469                run.run_description,
11470            );
11471
11472            update_test_language_settings(&mut cx, |settings| {
11473                settings.defaults.completions = Some(CompletionSettings {
11474                    lsp_insert_mode,
11475                    words: WordsCompletionMode::Disabled,
11476                    lsp: true,
11477                    lsp_fetch_timeout_ms: 0,
11478                });
11479            });
11480
11481            cx.set_state(&run.initial_state);
11482            cx.update_editor(|editor, window, cx| {
11483                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11484            });
11485
11486            let counter = Arc::new(AtomicUsize::new(0));
11487            handle_completion_request_with_insert_and_replace(
11488                &mut cx,
11489                &run.buffer_marked_text,
11490                vec![(run.completion_label, run.completion_text)],
11491                counter.clone(),
11492            )
11493            .await;
11494            cx.condition(|editor, _| editor.context_menu_visible())
11495                .await;
11496            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11497
11498            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11499                editor
11500                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11501                    .unwrap()
11502            });
11503            cx.assert_editor_state(&expected_text);
11504            handle_resolve_completion_request(&mut cx, None).await;
11505            apply_additional_edits.await.unwrap();
11506        }
11507    }
11508}
11509
11510#[gpui::test]
11511async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11512    init_test(cx, |_| {});
11513    let mut cx = EditorLspTestContext::new_rust(
11514        lsp::ServerCapabilities {
11515            completion_provider: Some(lsp::CompletionOptions {
11516                resolve_provider: Some(true),
11517                ..Default::default()
11518            }),
11519            ..Default::default()
11520        },
11521        cx,
11522    )
11523    .await;
11524
11525    let initial_state = "SubˇError";
11526    let buffer_marked_text = "<Sub|Error>";
11527    let completion_text = "SubscriptionError";
11528    let expected_with_insert_mode = "SubscriptionErrorˇError";
11529    let expected_with_replace_mode = "SubscriptionErrorˇ";
11530
11531    update_test_language_settings(&mut cx, |settings| {
11532        settings.defaults.completions = Some(CompletionSettings {
11533            words: WordsCompletionMode::Disabled,
11534            // set the opposite here to ensure that the action is overriding the default behavior
11535            lsp_insert_mode: LspInsertMode::Insert,
11536            lsp: true,
11537            lsp_fetch_timeout_ms: 0,
11538        });
11539    });
11540
11541    cx.set_state(initial_state);
11542    cx.update_editor(|editor, window, cx| {
11543        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11544    });
11545
11546    let counter = Arc::new(AtomicUsize::new(0));
11547    handle_completion_request_with_insert_and_replace(
11548        &mut cx,
11549        &buffer_marked_text,
11550        vec![(completion_text, completion_text)],
11551        counter.clone(),
11552    )
11553    .await;
11554    cx.condition(|editor, _| editor.context_menu_visible())
11555        .await;
11556    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11557
11558    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11559        editor
11560            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11561            .unwrap()
11562    });
11563    cx.assert_editor_state(&expected_with_replace_mode);
11564    handle_resolve_completion_request(&mut cx, None).await;
11565    apply_additional_edits.await.unwrap();
11566
11567    update_test_language_settings(&mut cx, |settings| {
11568        settings.defaults.completions = Some(CompletionSettings {
11569            words: WordsCompletionMode::Disabled,
11570            // set the opposite here to ensure that the action is overriding the default behavior
11571            lsp_insert_mode: LspInsertMode::Replace,
11572            lsp: true,
11573            lsp_fetch_timeout_ms: 0,
11574        });
11575    });
11576
11577    cx.set_state(initial_state);
11578    cx.update_editor(|editor, window, cx| {
11579        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11580    });
11581    handle_completion_request_with_insert_and_replace(
11582        &mut cx,
11583        &buffer_marked_text,
11584        vec![(completion_text, completion_text)],
11585        counter.clone(),
11586    )
11587    .await;
11588    cx.condition(|editor, _| editor.context_menu_visible())
11589        .await;
11590    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11591
11592    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11593        editor
11594            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11595            .unwrap()
11596    });
11597    cx.assert_editor_state(&expected_with_insert_mode);
11598    handle_resolve_completion_request(&mut cx, None).await;
11599    apply_additional_edits.await.unwrap();
11600}
11601
11602#[gpui::test]
11603async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11604    init_test(cx, |_| {});
11605    let mut cx = EditorLspTestContext::new_rust(
11606        lsp::ServerCapabilities {
11607            completion_provider: Some(lsp::CompletionOptions {
11608                resolve_provider: Some(true),
11609                ..Default::default()
11610            }),
11611            ..Default::default()
11612        },
11613        cx,
11614    )
11615    .await;
11616
11617    // scenario: surrounding text matches completion text
11618    let completion_text = "to_offset";
11619    let initial_state = indoc! {"
11620        1. buf.to_offˇsuffix
11621        2. buf.to_offˇsuf
11622        3. buf.to_offˇfix
11623        4. buf.to_offˇ
11624        5. into_offˇensive
11625        6. ˇsuffix
11626        7. let ˇ //
11627        8. aaˇzz
11628        9. buf.to_off«zzzzzˇ»suffix
11629        10. buf.«ˇzzzzz»suffix
11630        11. to_off«ˇzzzzz»
11631
11632        buf.to_offˇsuffix  // newest cursor
11633    "};
11634    let completion_marked_buffer = indoc! {"
11635        1. buf.to_offsuffix
11636        2. buf.to_offsuf
11637        3. buf.to_offfix
11638        4. buf.to_off
11639        5. into_offensive
11640        6. suffix
11641        7. let  //
11642        8. aazz
11643        9. buf.to_offzzzzzsuffix
11644        10. buf.zzzzzsuffix
11645        11. to_offzzzzz
11646
11647        buf.<to_off|suffix>  // newest cursor
11648    "};
11649    let expected = indoc! {"
11650        1. buf.to_offsetˇ
11651        2. buf.to_offsetˇsuf
11652        3. buf.to_offsetˇfix
11653        4. buf.to_offsetˇ
11654        5. into_offsetˇensive
11655        6. to_offsetˇsuffix
11656        7. let to_offsetˇ //
11657        8. aato_offsetˇzz
11658        9. buf.to_offsetˇ
11659        10. buf.to_offsetˇsuffix
11660        11. to_offsetˇ
11661
11662        buf.to_offsetˇ  // newest cursor
11663    "};
11664    cx.set_state(initial_state);
11665    cx.update_editor(|editor, window, cx| {
11666        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11667    });
11668    handle_completion_request_with_insert_and_replace(
11669        &mut cx,
11670        completion_marked_buffer,
11671        vec![(completion_text, completion_text)],
11672        Arc::new(AtomicUsize::new(0)),
11673    )
11674    .await;
11675    cx.condition(|editor, _| editor.context_menu_visible())
11676        .await;
11677    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11678        editor
11679            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11680            .unwrap()
11681    });
11682    cx.assert_editor_state(expected);
11683    handle_resolve_completion_request(&mut cx, None).await;
11684    apply_additional_edits.await.unwrap();
11685
11686    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11687    let completion_text = "foo_and_bar";
11688    let initial_state = indoc! {"
11689        1. ooanbˇ
11690        2. zooanbˇ
11691        3. ooanbˇz
11692        4. zooanbˇz
11693        5. ooanˇ
11694        6. oanbˇ
11695
11696        ooanbˇ
11697    "};
11698    let completion_marked_buffer = indoc! {"
11699        1. ooanb
11700        2. zooanb
11701        3. ooanbz
11702        4. zooanbz
11703        5. ooan
11704        6. oanb
11705
11706        <ooanb|>
11707    "};
11708    let expected = indoc! {"
11709        1. foo_and_barˇ
11710        2. zfoo_and_barˇ
11711        3. foo_and_barˇz
11712        4. zfoo_and_barˇz
11713        5. ooanfoo_and_barˇ
11714        6. oanbfoo_and_barˇ
11715
11716        foo_and_barˇ
11717    "};
11718    cx.set_state(initial_state);
11719    cx.update_editor(|editor, window, cx| {
11720        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11721    });
11722    handle_completion_request_with_insert_and_replace(
11723        &mut cx,
11724        completion_marked_buffer,
11725        vec![(completion_text, completion_text)],
11726        Arc::new(AtomicUsize::new(0)),
11727    )
11728    .await;
11729    cx.condition(|editor, _| editor.context_menu_visible())
11730        .await;
11731    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11732        editor
11733            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11734            .unwrap()
11735    });
11736    cx.assert_editor_state(expected);
11737    handle_resolve_completion_request(&mut cx, None).await;
11738    apply_additional_edits.await.unwrap();
11739
11740    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11741    // (expects the same as if it was inserted at the end)
11742    let completion_text = "foo_and_bar";
11743    let initial_state = indoc! {"
11744        1. ooˇanb
11745        2. zooˇanb
11746        3. ooˇanbz
11747        4. zooˇanbz
11748
11749        ooˇanb
11750    "};
11751    let completion_marked_buffer = indoc! {"
11752        1. ooanb
11753        2. zooanb
11754        3. ooanbz
11755        4. zooanbz
11756
11757        <oo|anb>
11758    "};
11759    let expected = indoc! {"
11760        1. foo_and_barˇ
11761        2. zfoo_and_barˇ
11762        3. foo_and_barˇz
11763        4. zfoo_and_barˇz
11764
11765        foo_and_barˇ
11766    "};
11767    cx.set_state(initial_state);
11768    cx.update_editor(|editor, window, cx| {
11769        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11770    });
11771    handle_completion_request_with_insert_and_replace(
11772        &mut cx,
11773        completion_marked_buffer,
11774        vec![(completion_text, completion_text)],
11775        Arc::new(AtomicUsize::new(0)),
11776    )
11777    .await;
11778    cx.condition(|editor, _| editor.context_menu_visible())
11779        .await;
11780    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11781        editor
11782            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11783            .unwrap()
11784    });
11785    cx.assert_editor_state(expected);
11786    handle_resolve_completion_request(&mut cx, None).await;
11787    apply_additional_edits.await.unwrap();
11788}
11789
11790// This used to crash
11791#[gpui::test]
11792async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11793    init_test(cx, |_| {});
11794
11795    let buffer_text = indoc! {"
11796        fn main() {
11797            10.satu;
11798
11799            //
11800            // separate cursors so they open in different excerpts (manually reproducible)
11801            //
11802
11803            10.satu20;
11804        }
11805    "};
11806    let multibuffer_text_with_selections = indoc! {"
11807        fn main() {
11808            10.satuˇ;
11809
11810            //
11811
11812            //
11813
11814            10.satuˇ20;
11815        }
11816    "};
11817    let expected_multibuffer = indoc! {"
11818        fn main() {
11819            10.saturating_sub()ˇ;
11820
11821            //
11822
11823            //
11824
11825            10.saturating_sub()ˇ;
11826        }
11827    "};
11828
11829    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11830    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11831
11832    let fs = FakeFs::new(cx.executor());
11833    fs.insert_tree(
11834        path!("/a"),
11835        json!({
11836            "main.rs": buffer_text,
11837        }),
11838    )
11839    .await;
11840
11841    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11842    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11843    language_registry.add(rust_lang());
11844    let mut fake_servers = language_registry.register_fake_lsp(
11845        "Rust",
11846        FakeLspAdapter {
11847            capabilities: lsp::ServerCapabilities {
11848                completion_provider: Some(lsp::CompletionOptions {
11849                    resolve_provider: None,
11850                    ..lsp::CompletionOptions::default()
11851                }),
11852                ..lsp::ServerCapabilities::default()
11853            },
11854            ..FakeLspAdapter::default()
11855        },
11856    );
11857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11858    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11859    let buffer = project
11860        .update(cx, |project, cx| {
11861            project.open_local_buffer(path!("/a/main.rs"), cx)
11862        })
11863        .await
11864        .unwrap();
11865
11866    let multi_buffer = cx.new(|cx| {
11867        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11868        multi_buffer.push_excerpts(
11869            buffer.clone(),
11870            [ExcerptRange::new(0..first_excerpt_end)],
11871            cx,
11872        );
11873        multi_buffer.push_excerpts(
11874            buffer.clone(),
11875            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11876            cx,
11877        );
11878        multi_buffer
11879    });
11880
11881    let editor = workspace
11882        .update(cx, |_, window, cx| {
11883            cx.new(|cx| {
11884                Editor::new(
11885                    EditorMode::Full {
11886                        scale_ui_elements_with_buffer_font_size: false,
11887                        show_active_line_background: false,
11888                        sized_by_content: false,
11889                    },
11890                    multi_buffer.clone(),
11891                    Some(project.clone()),
11892                    window,
11893                    cx,
11894                )
11895            })
11896        })
11897        .unwrap();
11898
11899    let pane = workspace
11900        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11901        .unwrap();
11902    pane.update_in(cx, |pane, window, cx| {
11903        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11904    });
11905
11906    let fake_server = fake_servers.next().await.unwrap();
11907
11908    editor.update_in(cx, |editor, window, cx| {
11909        editor.change_selections(None, window, cx, |s| {
11910            s.select_ranges([
11911                Point::new(1, 11)..Point::new(1, 11),
11912                Point::new(7, 11)..Point::new(7, 11),
11913            ])
11914        });
11915
11916        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11917    });
11918
11919    editor.update_in(cx, |editor, window, cx| {
11920        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11921    });
11922
11923    fake_server
11924        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11925            let completion_item = lsp::CompletionItem {
11926                label: "saturating_sub()".into(),
11927                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11928                    lsp::InsertReplaceEdit {
11929                        new_text: "saturating_sub()".to_owned(),
11930                        insert: lsp::Range::new(
11931                            lsp::Position::new(7, 7),
11932                            lsp::Position::new(7, 11),
11933                        ),
11934                        replace: lsp::Range::new(
11935                            lsp::Position::new(7, 7),
11936                            lsp::Position::new(7, 13),
11937                        ),
11938                    },
11939                )),
11940                ..lsp::CompletionItem::default()
11941            };
11942
11943            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11944        })
11945        .next()
11946        .await
11947        .unwrap();
11948
11949    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11950        .await;
11951
11952    editor
11953        .update_in(cx, |editor, window, cx| {
11954            editor
11955                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11956                .unwrap()
11957        })
11958        .await
11959        .unwrap();
11960
11961    editor.update(cx, |editor, cx| {
11962        assert_text_with_selections(editor, expected_multibuffer, cx);
11963    })
11964}
11965
11966#[gpui::test]
11967async fn test_completion(cx: &mut TestAppContext) {
11968    init_test(cx, |_| {});
11969
11970    let mut cx = EditorLspTestContext::new_rust(
11971        lsp::ServerCapabilities {
11972            completion_provider: Some(lsp::CompletionOptions {
11973                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11974                resolve_provider: Some(true),
11975                ..Default::default()
11976            }),
11977            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11978            ..Default::default()
11979        },
11980        cx,
11981    )
11982    .await;
11983    let counter = Arc::new(AtomicUsize::new(0));
11984
11985    cx.set_state(indoc! {"
11986        oneˇ
11987        two
11988        three
11989    "});
11990    cx.simulate_keystroke(".");
11991    handle_completion_request(
11992        indoc! {"
11993            one.|<>
11994            two
11995            three
11996        "},
11997        vec!["first_completion", "second_completion"],
11998        true,
11999        counter.clone(),
12000        &mut cx,
12001    )
12002    .await;
12003    cx.condition(|editor, _| editor.context_menu_visible())
12004        .await;
12005    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12006
12007    let _handler = handle_signature_help_request(
12008        &mut cx,
12009        lsp::SignatureHelp {
12010            signatures: vec![lsp::SignatureInformation {
12011                label: "test signature".to_string(),
12012                documentation: None,
12013                parameters: Some(vec![lsp::ParameterInformation {
12014                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12015                    documentation: None,
12016                }]),
12017                active_parameter: None,
12018            }],
12019            active_signature: None,
12020            active_parameter: None,
12021        },
12022    );
12023    cx.update_editor(|editor, window, cx| {
12024        assert!(
12025            !editor.signature_help_state.is_shown(),
12026            "No signature help was called for"
12027        );
12028        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12029    });
12030    cx.run_until_parked();
12031    cx.update_editor(|editor, _, _| {
12032        assert!(
12033            !editor.signature_help_state.is_shown(),
12034            "No signature help should be shown when completions menu is open"
12035        );
12036    });
12037
12038    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12039        editor.context_menu_next(&Default::default(), window, cx);
12040        editor
12041            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12042            .unwrap()
12043    });
12044    cx.assert_editor_state(indoc! {"
12045        one.second_completionˇ
12046        two
12047        three
12048    "});
12049
12050    handle_resolve_completion_request(
12051        &mut cx,
12052        Some(vec![
12053            (
12054                //This overlaps with the primary completion edit which is
12055                //misbehavior from the LSP spec, test that we filter it out
12056                indoc! {"
12057                    one.second_ˇcompletion
12058                    two
12059                    threeˇ
12060                "},
12061                "overlapping additional edit",
12062            ),
12063            (
12064                indoc! {"
12065                    one.second_completion
12066                    two
12067                    threeˇ
12068                "},
12069                "\nadditional edit",
12070            ),
12071        ]),
12072    )
12073    .await;
12074    apply_additional_edits.await.unwrap();
12075    cx.assert_editor_state(indoc! {"
12076        one.second_completionˇ
12077        two
12078        three
12079        additional edit
12080    "});
12081
12082    cx.set_state(indoc! {"
12083        one.second_completion
12084        twoˇ
12085        threeˇ
12086        additional edit
12087    "});
12088    cx.simulate_keystroke(" ");
12089    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12090    cx.simulate_keystroke("s");
12091    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12092
12093    cx.assert_editor_state(indoc! {"
12094        one.second_completion
12095        two sˇ
12096        three sˇ
12097        additional edit
12098    "});
12099    handle_completion_request(
12100        indoc! {"
12101            one.second_completion
12102            two s
12103            three <s|>
12104            additional edit
12105        "},
12106        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12107        true,
12108        counter.clone(),
12109        &mut cx,
12110    )
12111    .await;
12112    cx.condition(|editor, _| editor.context_menu_visible())
12113        .await;
12114    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12115
12116    cx.simulate_keystroke("i");
12117
12118    handle_completion_request(
12119        indoc! {"
12120            one.second_completion
12121            two si
12122            three <si|>
12123            additional edit
12124        "},
12125        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12126        true,
12127        counter.clone(),
12128        &mut cx,
12129    )
12130    .await;
12131    cx.condition(|editor, _| editor.context_menu_visible())
12132        .await;
12133    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12134
12135    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12136        editor
12137            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12138            .unwrap()
12139    });
12140    cx.assert_editor_state(indoc! {"
12141        one.second_completion
12142        two sixth_completionˇ
12143        three sixth_completionˇ
12144        additional edit
12145    "});
12146
12147    apply_additional_edits.await.unwrap();
12148
12149    update_test_language_settings(&mut cx, |settings| {
12150        settings.defaults.show_completions_on_input = Some(false);
12151    });
12152    cx.set_state("editorˇ");
12153    cx.simulate_keystroke(".");
12154    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12155    cx.simulate_keystrokes("c l o");
12156    cx.assert_editor_state("editor.cloˇ");
12157    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12158    cx.update_editor(|editor, window, cx| {
12159        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12160    });
12161    handle_completion_request(
12162        "editor.<clo|>",
12163        vec!["close", "clobber"],
12164        true,
12165        counter.clone(),
12166        &mut cx,
12167    )
12168    .await;
12169    cx.condition(|editor, _| editor.context_menu_visible())
12170        .await;
12171    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12172
12173    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12174        editor
12175            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12176            .unwrap()
12177    });
12178    cx.assert_editor_state("editor.clobberˇ");
12179    handle_resolve_completion_request(&mut cx, None).await;
12180    apply_additional_edits.await.unwrap();
12181}
12182
12183#[gpui::test]
12184async fn test_completion_reuse(cx: &mut TestAppContext) {
12185    init_test(cx, |_| {});
12186
12187    let mut cx = EditorLspTestContext::new_rust(
12188        lsp::ServerCapabilities {
12189            completion_provider: Some(lsp::CompletionOptions {
12190                trigger_characters: Some(vec![".".to_string()]),
12191                ..Default::default()
12192            }),
12193            ..Default::default()
12194        },
12195        cx,
12196    )
12197    .await;
12198
12199    let counter = Arc::new(AtomicUsize::new(0));
12200    cx.set_state("objˇ");
12201    cx.simulate_keystroke(".");
12202
12203    // Initial completion request returns complete results
12204    let is_incomplete = false;
12205    handle_completion_request(
12206        "obj.|<>",
12207        vec!["a", "ab", "abc"],
12208        is_incomplete,
12209        counter.clone(),
12210        &mut cx,
12211    )
12212    .await;
12213    cx.run_until_parked();
12214    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12215    cx.assert_editor_state("obj.ˇ");
12216    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12217
12218    // Type "a" - filters existing completions
12219    cx.simulate_keystroke("a");
12220    cx.run_until_parked();
12221    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12222    cx.assert_editor_state("obj.aˇ");
12223    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12224
12225    // Type "b" - filters existing completions
12226    cx.simulate_keystroke("b");
12227    cx.run_until_parked();
12228    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12229    cx.assert_editor_state("obj.abˇ");
12230    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12231
12232    // Type "c" - filters existing completions
12233    cx.simulate_keystroke("c");
12234    cx.run_until_parked();
12235    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12236    cx.assert_editor_state("obj.abcˇ");
12237    check_displayed_completions(vec!["abc"], &mut cx);
12238
12239    // Backspace to delete "c" - filters existing completions
12240    cx.update_editor(|editor, window, cx| {
12241        editor.backspace(&Backspace, window, cx);
12242    });
12243    cx.run_until_parked();
12244    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12245    cx.assert_editor_state("obj.abˇ");
12246    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12247
12248    // Moving cursor to the left dismisses menu.
12249    cx.update_editor(|editor, window, cx| {
12250        editor.move_left(&MoveLeft, window, cx);
12251    });
12252    cx.run_until_parked();
12253    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12254    cx.assert_editor_state("obj.aˇb");
12255    cx.update_editor(|editor, _, _| {
12256        assert_eq!(editor.context_menu_visible(), false);
12257    });
12258
12259    // Type "b" - new request
12260    cx.simulate_keystroke("b");
12261    let is_incomplete = false;
12262    handle_completion_request(
12263        "obj.<ab|>a",
12264        vec!["ab", "abc"],
12265        is_incomplete,
12266        counter.clone(),
12267        &mut cx,
12268    )
12269    .await;
12270    cx.run_until_parked();
12271    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12272    cx.assert_editor_state("obj.abˇb");
12273    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12274
12275    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12276    cx.update_editor(|editor, window, cx| {
12277        editor.backspace(&Backspace, window, cx);
12278    });
12279    let is_incomplete = false;
12280    handle_completion_request(
12281        "obj.<a|>b",
12282        vec!["a", "ab", "abc"],
12283        is_incomplete,
12284        counter.clone(),
12285        &mut cx,
12286    )
12287    .await;
12288    cx.run_until_parked();
12289    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12290    cx.assert_editor_state("obj.aˇb");
12291    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12292
12293    // Backspace to delete "a" - dismisses menu.
12294    cx.update_editor(|editor, window, cx| {
12295        editor.backspace(&Backspace, window, cx);
12296    });
12297    cx.run_until_parked();
12298    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12299    cx.assert_editor_state("obj.ˇb");
12300    cx.update_editor(|editor, _, _| {
12301        assert_eq!(editor.context_menu_visible(), false);
12302    });
12303}
12304
12305#[gpui::test]
12306async fn test_word_completion(cx: &mut TestAppContext) {
12307    let lsp_fetch_timeout_ms = 10;
12308    init_test(cx, |language_settings| {
12309        language_settings.defaults.completions = Some(CompletionSettings {
12310            words: WordsCompletionMode::Fallback,
12311            lsp: true,
12312            lsp_fetch_timeout_ms: 10,
12313            lsp_insert_mode: LspInsertMode::Insert,
12314        });
12315    });
12316
12317    let mut cx = EditorLspTestContext::new_rust(
12318        lsp::ServerCapabilities {
12319            completion_provider: Some(lsp::CompletionOptions {
12320                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12321                ..lsp::CompletionOptions::default()
12322            }),
12323            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12324            ..lsp::ServerCapabilities::default()
12325        },
12326        cx,
12327    )
12328    .await;
12329
12330    let throttle_completions = Arc::new(AtomicBool::new(false));
12331
12332    let lsp_throttle_completions = throttle_completions.clone();
12333    let _completion_requests_handler =
12334        cx.lsp
12335            .server
12336            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12337                let lsp_throttle_completions = lsp_throttle_completions.clone();
12338                let cx = cx.clone();
12339                async move {
12340                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12341                        cx.background_executor()
12342                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12343                            .await;
12344                    }
12345                    Ok(Some(lsp::CompletionResponse::Array(vec![
12346                        lsp::CompletionItem {
12347                            label: "first".into(),
12348                            ..lsp::CompletionItem::default()
12349                        },
12350                        lsp::CompletionItem {
12351                            label: "last".into(),
12352                            ..lsp::CompletionItem::default()
12353                        },
12354                    ])))
12355                }
12356            });
12357
12358    cx.set_state(indoc! {"
12359        oneˇ
12360        two
12361        three
12362    "});
12363    cx.simulate_keystroke(".");
12364    cx.executor().run_until_parked();
12365    cx.condition(|editor, _| editor.context_menu_visible())
12366        .await;
12367    cx.update_editor(|editor, window, cx| {
12368        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12369        {
12370            assert_eq!(
12371                completion_menu_entries(&menu),
12372                &["first", "last"],
12373                "When LSP server is fast to reply, no fallback word completions are used"
12374            );
12375        } else {
12376            panic!("expected completion menu to be open");
12377        }
12378        editor.cancel(&Cancel, window, cx);
12379    });
12380    cx.executor().run_until_parked();
12381    cx.condition(|editor, _| !editor.context_menu_visible())
12382        .await;
12383
12384    throttle_completions.store(true, atomic::Ordering::Release);
12385    cx.simulate_keystroke(".");
12386    cx.executor()
12387        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12388    cx.executor().run_until_parked();
12389    cx.condition(|editor, _| editor.context_menu_visible())
12390        .await;
12391    cx.update_editor(|editor, _, _| {
12392        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12393        {
12394            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12395                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12396        } else {
12397            panic!("expected completion menu to be open");
12398        }
12399    });
12400}
12401
12402#[gpui::test]
12403async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12404    init_test(cx, |language_settings| {
12405        language_settings.defaults.completions = Some(CompletionSettings {
12406            words: WordsCompletionMode::Enabled,
12407            lsp: true,
12408            lsp_fetch_timeout_ms: 0,
12409            lsp_insert_mode: LspInsertMode::Insert,
12410        });
12411    });
12412
12413    let mut cx = EditorLspTestContext::new_rust(
12414        lsp::ServerCapabilities {
12415            completion_provider: Some(lsp::CompletionOptions {
12416                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12417                ..lsp::CompletionOptions::default()
12418            }),
12419            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12420            ..lsp::ServerCapabilities::default()
12421        },
12422        cx,
12423    )
12424    .await;
12425
12426    let _completion_requests_handler =
12427        cx.lsp
12428            .server
12429            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12430                Ok(Some(lsp::CompletionResponse::Array(vec![
12431                    lsp::CompletionItem {
12432                        label: "first".into(),
12433                        ..lsp::CompletionItem::default()
12434                    },
12435                    lsp::CompletionItem {
12436                        label: "last".into(),
12437                        ..lsp::CompletionItem::default()
12438                    },
12439                ])))
12440            });
12441
12442    cx.set_state(indoc! {"ˇ
12443        first
12444        last
12445        second
12446    "});
12447    cx.simulate_keystroke(".");
12448    cx.executor().run_until_parked();
12449    cx.condition(|editor, _| editor.context_menu_visible())
12450        .await;
12451    cx.update_editor(|editor, _, _| {
12452        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12453        {
12454            assert_eq!(
12455                completion_menu_entries(&menu),
12456                &["first", "last", "second"],
12457                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12458            );
12459        } else {
12460            panic!("expected completion menu to be open");
12461        }
12462    });
12463}
12464
12465#[gpui::test]
12466async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12467    init_test(cx, |language_settings| {
12468        language_settings.defaults.completions = Some(CompletionSettings {
12469            words: WordsCompletionMode::Disabled,
12470            lsp: true,
12471            lsp_fetch_timeout_ms: 0,
12472            lsp_insert_mode: LspInsertMode::Insert,
12473        });
12474    });
12475
12476    let mut cx = EditorLspTestContext::new_rust(
12477        lsp::ServerCapabilities {
12478            completion_provider: Some(lsp::CompletionOptions {
12479                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12480                ..lsp::CompletionOptions::default()
12481            }),
12482            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12483            ..lsp::ServerCapabilities::default()
12484        },
12485        cx,
12486    )
12487    .await;
12488
12489    let _completion_requests_handler =
12490        cx.lsp
12491            .server
12492            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12493                panic!("LSP completions should not be queried when dealing with word completions")
12494            });
12495
12496    cx.set_state(indoc! {"ˇ
12497        first
12498        last
12499        second
12500    "});
12501    cx.update_editor(|editor, window, cx| {
12502        editor.show_word_completions(&ShowWordCompletions, window, cx);
12503    });
12504    cx.executor().run_until_parked();
12505    cx.condition(|editor, _| editor.context_menu_visible())
12506        .await;
12507    cx.update_editor(|editor, _, _| {
12508        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12509        {
12510            assert_eq!(
12511                completion_menu_entries(&menu),
12512                &["first", "last", "second"],
12513                "`ShowWordCompletions` action should show word completions"
12514            );
12515        } else {
12516            panic!("expected completion menu to be open");
12517        }
12518    });
12519
12520    cx.simulate_keystroke("l");
12521    cx.executor().run_until_parked();
12522    cx.condition(|editor, _| editor.context_menu_visible())
12523        .await;
12524    cx.update_editor(|editor, _, _| {
12525        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12526        {
12527            assert_eq!(
12528                completion_menu_entries(&menu),
12529                &["last"],
12530                "After showing word completions, further editing should filter them and not query the LSP"
12531            );
12532        } else {
12533            panic!("expected completion menu to be open");
12534        }
12535    });
12536}
12537
12538#[gpui::test]
12539async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12540    init_test(cx, |language_settings| {
12541        language_settings.defaults.completions = Some(CompletionSettings {
12542            words: WordsCompletionMode::Fallback,
12543            lsp: false,
12544            lsp_fetch_timeout_ms: 0,
12545            lsp_insert_mode: LspInsertMode::Insert,
12546        });
12547    });
12548
12549    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12550
12551    cx.set_state(indoc! {"ˇ
12552        0_usize
12553        let
12554        33
12555        4.5f32
12556    "});
12557    cx.update_editor(|editor, window, cx| {
12558        editor.show_completions(&ShowCompletions::default(), window, cx);
12559    });
12560    cx.executor().run_until_parked();
12561    cx.condition(|editor, _| editor.context_menu_visible())
12562        .await;
12563    cx.update_editor(|editor, window, cx| {
12564        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12565        {
12566            assert_eq!(
12567                completion_menu_entries(&menu),
12568                &["let"],
12569                "With no digits in the completion query, no digits should be in the word completions"
12570            );
12571        } else {
12572            panic!("expected completion menu to be open");
12573        }
12574        editor.cancel(&Cancel, window, cx);
12575    });
12576
12577    cx.set_state(indoc! {"12578        0_usize
12579        let
12580        3
12581        33.35f32
12582    "});
12583    cx.update_editor(|editor, window, cx| {
12584        editor.show_completions(&ShowCompletions::default(), window, cx);
12585    });
12586    cx.executor().run_until_parked();
12587    cx.condition(|editor, _| editor.context_menu_visible())
12588        .await;
12589    cx.update_editor(|editor, _, _| {
12590        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12591        {
12592            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12593                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12594        } else {
12595            panic!("expected completion menu to be open");
12596        }
12597    });
12598}
12599
12600fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12601    let position = || lsp::Position {
12602        line: params.text_document_position.position.line,
12603        character: params.text_document_position.position.character,
12604    };
12605    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12606        range: lsp::Range {
12607            start: position(),
12608            end: position(),
12609        },
12610        new_text: text.to_string(),
12611    }))
12612}
12613
12614#[gpui::test]
12615async fn test_multiline_completion(cx: &mut TestAppContext) {
12616    init_test(cx, |_| {});
12617
12618    let fs = FakeFs::new(cx.executor());
12619    fs.insert_tree(
12620        path!("/a"),
12621        json!({
12622            "main.ts": "a",
12623        }),
12624    )
12625    .await;
12626
12627    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12628    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12629    let typescript_language = Arc::new(Language::new(
12630        LanguageConfig {
12631            name: "TypeScript".into(),
12632            matcher: LanguageMatcher {
12633                path_suffixes: vec!["ts".to_string()],
12634                ..LanguageMatcher::default()
12635            },
12636            line_comments: vec!["// ".into()],
12637            ..LanguageConfig::default()
12638        },
12639        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12640    ));
12641    language_registry.add(typescript_language.clone());
12642    let mut fake_servers = language_registry.register_fake_lsp(
12643        "TypeScript",
12644        FakeLspAdapter {
12645            capabilities: lsp::ServerCapabilities {
12646                completion_provider: Some(lsp::CompletionOptions {
12647                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12648                    ..lsp::CompletionOptions::default()
12649                }),
12650                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12651                ..lsp::ServerCapabilities::default()
12652            },
12653            // Emulate vtsls label generation
12654            label_for_completion: Some(Box::new(|item, _| {
12655                let text = if let Some(description) = item
12656                    .label_details
12657                    .as_ref()
12658                    .and_then(|label_details| label_details.description.as_ref())
12659                {
12660                    format!("{} {}", item.label, description)
12661                } else if let Some(detail) = &item.detail {
12662                    format!("{} {}", item.label, detail)
12663                } else {
12664                    item.label.clone()
12665                };
12666                let len = text.len();
12667                Some(language::CodeLabel {
12668                    text,
12669                    runs: Vec::new(),
12670                    filter_range: 0..len,
12671                })
12672            })),
12673            ..FakeLspAdapter::default()
12674        },
12675    );
12676    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12677    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12678    let worktree_id = workspace
12679        .update(cx, |workspace, _window, cx| {
12680            workspace.project().update(cx, |project, cx| {
12681                project.worktrees(cx).next().unwrap().read(cx).id()
12682            })
12683        })
12684        .unwrap();
12685    let _buffer = project
12686        .update(cx, |project, cx| {
12687            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12688        })
12689        .await
12690        .unwrap();
12691    let editor = workspace
12692        .update(cx, |workspace, window, cx| {
12693            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12694        })
12695        .unwrap()
12696        .await
12697        .unwrap()
12698        .downcast::<Editor>()
12699        .unwrap();
12700    let fake_server = fake_servers.next().await.unwrap();
12701
12702    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12703    let multiline_label_2 = "a\nb\nc\n";
12704    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12705    let multiline_description = "d\ne\nf\n";
12706    let multiline_detail_2 = "g\nh\ni\n";
12707
12708    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12709        move |params, _| async move {
12710            Ok(Some(lsp::CompletionResponse::Array(vec![
12711                lsp::CompletionItem {
12712                    label: multiline_label.to_string(),
12713                    text_edit: gen_text_edit(&params, "new_text_1"),
12714                    ..lsp::CompletionItem::default()
12715                },
12716                lsp::CompletionItem {
12717                    label: "single line label 1".to_string(),
12718                    detail: Some(multiline_detail.to_string()),
12719                    text_edit: gen_text_edit(&params, "new_text_2"),
12720                    ..lsp::CompletionItem::default()
12721                },
12722                lsp::CompletionItem {
12723                    label: "single line label 2".to_string(),
12724                    label_details: Some(lsp::CompletionItemLabelDetails {
12725                        description: Some(multiline_description.to_string()),
12726                        detail: None,
12727                    }),
12728                    text_edit: gen_text_edit(&params, "new_text_2"),
12729                    ..lsp::CompletionItem::default()
12730                },
12731                lsp::CompletionItem {
12732                    label: multiline_label_2.to_string(),
12733                    detail: Some(multiline_detail_2.to_string()),
12734                    text_edit: gen_text_edit(&params, "new_text_3"),
12735                    ..lsp::CompletionItem::default()
12736                },
12737                lsp::CompletionItem {
12738                    label: "Label with many     spaces and \t but without newlines".to_string(),
12739                    detail: Some(
12740                        "Details with many     spaces and \t but without newlines".to_string(),
12741                    ),
12742                    text_edit: gen_text_edit(&params, "new_text_4"),
12743                    ..lsp::CompletionItem::default()
12744                },
12745            ])))
12746        },
12747    );
12748
12749    editor.update_in(cx, |editor, window, cx| {
12750        cx.focus_self(window);
12751        editor.move_to_end(&MoveToEnd, window, cx);
12752        editor.handle_input(".", window, cx);
12753    });
12754    cx.run_until_parked();
12755    completion_handle.next().await.unwrap();
12756
12757    editor.update(cx, |editor, _| {
12758        assert!(editor.context_menu_visible());
12759        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12760        {
12761            let completion_labels = menu
12762                .completions
12763                .borrow()
12764                .iter()
12765                .map(|c| c.label.text.clone())
12766                .collect::<Vec<_>>();
12767            assert_eq!(
12768                completion_labels,
12769                &[
12770                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12771                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12772                    "single line label 2 d e f ",
12773                    "a b c g h i ",
12774                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12775                ],
12776                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12777            );
12778
12779            for completion in menu
12780                .completions
12781                .borrow()
12782                .iter() {
12783                    assert_eq!(
12784                        completion.label.filter_range,
12785                        0..completion.label.text.len(),
12786                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12787                    );
12788                }
12789        } else {
12790            panic!("expected completion menu to be open");
12791        }
12792    });
12793}
12794
12795#[gpui::test]
12796async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12797    init_test(cx, |_| {});
12798    let mut cx = EditorLspTestContext::new_rust(
12799        lsp::ServerCapabilities {
12800            completion_provider: Some(lsp::CompletionOptions {
12801                trigger_characters: Some(vec![".".to_string()]),
12802                ..Default::default()
12803            }),
12804            ..Default::default()
12805        },
12806        cx,
12807    )
12808    .await;
12809    cx.lsp
12810        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12811            Ok(Some(lsp::CompletionResponse::Array(vec![
12812                lsp::CompletionItem {
12813                    label: "first".into(),
12814                    ..Default::default()
12815                },
12816                lsp::CompletionItem {
12817                    label: "last".into(),
12818                    ..Default::default()
12819                },
12820            ])))
12821        });
12822    cx.set_state("variableˇ");
12823    cx.simulate_keystroke(".");
12824    cx.executor().run_until_parked();
12825
12826    cx.update_editor(|editor, _, _| {
12827        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12828        {
12829            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12830        } else {
12831            panic!("expected completion menu to be open");
12832        }
12833    });
12834
12835    cx.update_editor(|editor, window, cx| {
12836        editor.move_page_down(&MovePageDown::default(), window, cx);
12837        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12838        {
12839            assert!(
12840                menu.selected_item == 1,
12841                "expected PageDown to select the last item from the context menu"
12842            );
12843        } else {
12844            panic!("expected completion menu to stay open after PageDown");
12845        }
12846    });
12847
12848    cx.update_editor(|editor, window, cx| {
12849        editor.move_page_up(&MovePageUp::default(), window, cx);
12850        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12851        {
12852            assert!(
12853                menu.selected_item == 0,
12854                "expected PageUp to select the first item from the context menu"
12855            );
12856        } else {
12857            panic!("expected completion menu to stay open after PageUp");
12858        }
12859    });
12860}
12861
12862#[gpui::test]
12863async fn test_as_is_completions(cx: &mut TestAppContext) {
12864    init_test(cx, |_| {});
12865    let mut cx = EditorLspTestContext::new_rust(
12866        lsp::ServerCapabilities {
12867            completion_provider: Some(lsp::CompletionOptions {
12868                ..Default::default()
12869            }),
12870            ..Default::default()
12871        },
12872        cx,
12873    )
12874    .await;
12875    cx.lsp
12876        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12877            Ok(Some(lsp::CompletionResponse::Array(vec![
12878                lsp::CompletionItem {
12879                    label: "unsafe".into(),
12880                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12881                        range: lsp::Range {
12882                            start: lsp::Position {
12883                                line: 1,
12884                                character: 2,
12885                            },
12886                            end: lsp::Position {
12887                                line: 1,
12888                                character: 3,
12889                            },
12890                        },
12891                        new_text: "unsafe".to_string(),
12892                    })),
12893                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12894                    ..Default::default()
12895                },
12896            ])))
12897        });
12898    cx.set_state("fn a() {}\n");
12899    cx.executor().run_until_parked();
12900    cx.update_editor(|editor, window, cx| {
12901        editor.show_completions(
12902            &ShowCompletions {
12903                trigger: Some("\n".into()),
12904            },
12905            window,
12906            cx,
12907        );
12908    });
12909    cx.executor().run_until_parked();
12910
12911    cx.update_editor(|editor, window, cx| {
12912        editor.confirm_completion(&Default::default(), window, cx)
12913    });
12914    cx.executor().run_until_parked();
12915    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12916}
12917
12918#[gpui::test]
12919async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12920    init_test(cx, |_| {});
12921
12922    let mut cx = EditorLspTestContext::new_rust(
12923        lsp::ServerCapabilities {
12924            completion_provider: Some(lsp::CompletionOptions {
12925                trigger_characters: Some(vec![".".to_string()]),
12926                resolve_provider: Some(true),
12927                ..Default::default()
12928            }),
12929            ..Default::default()
12930        },
12931        cx,
12932    )
12933    .await;
12934
12935    cx.set_state("fn main() { let a = 2ˇ; }");
12936    cx.simulate_keystroke(".");
12937    let completion_item = lsp::CompletionItem {
12938        label: "Some".into(),
12939        kind: Some(lsp::CompletionItemKind::SNIPPET),
12940        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12941        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12942            kind: lsp::MarkupKind::Markdown,
12943            value: "```rust\nSome(2)\n```".to_string(),
12944        })),
12945        deprecated: Some(false),
12946        sort_text: Some("Some".to_string()),
12947        filter_text: Some("Some".to_string()),
12948        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12949        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12950            range: lsp::Range {
12951                start: lsp::Position {
12952                    line: 0,
12953                    character: 22,
12954                },
12955                end: lsp::Position {
12956                    line: 0,
12957                    character: 22,
12958                },
12959            },
12960            new_text: "Some(2)".to_string(),
12961        })),
12962        additional_text_edits: Some(vec![lsp::TextEdit {
12963            range: lsp::Range {
12964                start: lsp::Position {
12965                    line: 0,
12966                    character: 20,
12967                },
12968                end: lsp::Position {
12969                    line: 0,
12970                    character: 22,
12971                },
12972            },
12973            new_text: "".to_string(),
12974        }]),
12975        ..Default::default()
12976    };
12977
12978    let closure_completion_item = completion_item.clone();
12979    let counter = Arc::new(AtomicUsize::new(0));
12980    let counter_clone = counter.clone();
12981    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12982        let task_completion_item = closure_completion_item.clone();
12983        counter_clone.fetch_add(1, atomic::Ordering::Release);
12984        async move {
12985            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12986                is_incomplete: true,
12987                item_defaults: None,
12988                items: vec![task_completion_item],
12989            })))
12990        }
12991    });
12992
12993    cx.condition(|editor, _| editor.context_menu_visible())
12994        .await;
12995    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12996    assert!(request.next().await.is_some());
12997    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12998
12999    cx.simulate_keystrokes("S o m");
13000    cx.condition(|editor, _| editor.context_menu_visible())
13001        .await;
13002    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13003    assert!(request.next().await.is_some());
13004    assert!(request.next().await.is_some());
13005    assert!(request.next().await.is_some());
13006    request.close();
13007    assert!(request.next().await.is_none());
13008    assert_eq!(
13009        counter.load(atomic::Ordering::Acquire),
13010        4,
13011        "With the completions menu open, only one LSP request should happen per input"
13012    );
13013}
13014
13015#[gpui::test]
13016async fn test_toggle_comment(cx: &mut TestAppContext) {
13017    init_test(cx, |_| {});
13018    let mut cx = EditorTestContext::new(cx).await;
13019    let language = Arc::new(Language::new(
13020        LanguageConfig {
13021            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13022            ..Default::default()
13023        },
13024        Some(tree_sitter_rust::LANGUAGE.into()),
13025    ));
13026    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13027
13028    // If multiple selections intersect a line, the line is only toggled once.
13029    cx.set_state(indoc! {"
13030        fn a() {
13031            «//b();
13032            ˇ»// «c();
13033            //ˇ»  d();
13034        }
13035    "});
13036
13037    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13038
13039    cx.assert_editor_state(indoc! {"
13040        fn a() {
13041            «b();
13042            c();
13043            ˇ» d();
13044        }
13045    "});
13046
13047    // The comment prefix is inserted at the same column for every line in a
13048    // selection.
13049    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13050
13051    cx.assert_editor_state(indoc! {"
13052        fn a() {
13053            // «b();
13054            // c();
13055            ˇ»//  d();
13056        }
13057    "});
13058
13059    // If a selection ends at the beginning of a line, that line is not toggled.
13060    cx.set_selections_state(indoc! {"
13061        fn a() {
13062            // b();
13063            «// c();
13064        ˇ»    //  d();
13065        }
13066    "});
13067
13068    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13069
13070    cx.assert_editor_state(indoc! {"
13071        fn a() {
13072            // b();
13073            «c();
13074        ˇ»    //  d();
13075        }
13076    "});
13077
13078    // If a selection span a single line and is empty, the line is toggled.
13079    cx.set_state(indoc! {"
13080        fn a() {
13081            a();
13082            b();
13083        ˇ
13084        }
13085    "});
13086
13087    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13088
13089    cx.assert_editor_state(indoc! {"
13090        fn a() {
13091            a();
13092            b();
13093        //•ˇ
13094        }
13095    "});
13096
13097    // If a selection span multiple lines, empty lines are not toggled.
13098    cx.set_state(indoc! {"
13099        fn a() {
13100            «a();
13101
13102            c();ˇ»
13103        }
13104    "});
13105
13106    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13107
13108    cx.assert_editor_state(indoc! {"
13109        fn a() {
13110            // «a();
13111
13112            // c();ˇ»
13113        }
13114    "});
13115
13116    // If a selection includes multiple comment prefixes, all lines are uncommented.
13117    cx.set_state(indoc! {"
13118        fn a() {
13119            «// a();
13120            /// b();
13121            //! c();ˇ»
13122        }
13123    "});
13124
13125    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13126
13127    cx.assert_editor_state(indoc! {"
13128        fn a() {
13129            «a();
13130            b();
13131            c();ˇ»
13132        }
13133    "});
13134}
13135
13136#[gpui::test]
13137async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13138    init_test(cx, |_| {});
13139    let mut cx = EditorTestContext::new(cx).await;
13140    let language = Arc::new(Language::new(
13141        LanguageConfig {
13142            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13143            ..Default::default()
13144        },
13145        Some(tree_sitter_rust::LANGUAGE.into()),
13146    ));
13147    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13148
13149    let toggle_comments = &ToggleComments {
13150        advance_downwards: false,
13151        ignore_indent: true,
13152    };
13153
13154    // If multiple selections intersect a line, the line is only toggled once.
13155    cx.set_state(indoc! {"
13156        fn a() {
13157        //    «b();
13158        //    c();
13159        //    ˇ» d();
13160        }
13161    "});
13162
13163    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13164
13165    cx.assert_editor_state(indoc! {"
13166        fn a() {
13167            «b();
13168            c();
13169            ˇ» d();
13170        }
13171    "});
13172
13173    // The comment prefix is inserted at the beginning of each line
13174    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13175
13176    cx.assert_editor_state(indoc! {"
13177        fn a() {
13178        //    «b();
13179        //    c();
13180        //    ˇ» d();
13181        }
13182    "});
13183
13184    // If a selection ends at the beginning of a line, that line is not toggled.
13185    cx.set_selections_state(indoc! {"
13186        fn a() {
13187        //    b();
13188        //    «c();
13189        ˇ»//     d();
13190        }
13191    "});
13192
13193    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13194
13195    cx.assert_editor_state(indoc! {"
13196        fn a() {
13197        //    b();
13198            «c();
13199        ˇ»//     d();
13200        }
13201    "});
13202
13203    // If a selection span a single line and is empty, the line is toggled.
13204    cx.set_state(indoc! {"
13205        fn a() {
13206            a();
13207            b();
13208        ˇ
13209        }
13210    "});
13211
13212    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13213
13214    cx.assert_editor_state(indoc! {"
13215        fn a() {
13216            a();
13217            b();
13218        //ˇ
13219        }
13220    "});
13221
13222    // If a selection span multiple lines, empty lines are not toggled.
13223    cx.set_state(indoc! {"
13224        fn a() {
13225            «a();
13226
13227            c();ˇ»
13228        }
13229    "});
13230
13231    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13232
13233    cx.assert_editor_state(indoc! {"
13234        fn a() {
13235        //    «a();
13236
13237        //    c();ˇ»
13238        }
13239    "});
13240
13241    // If a selection includes multiple comment prefixes, all lines are uncommented.
13242    cx.set_state(indoc! {"
13243        fn a() {
13244        //    «a();
13245        ///    b();
13246        //!    c();ˇ»
13247        }
13248    "});
13249
13250    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13251
13252    cx.assert_editor_state(indoc! {"
13253        fn a() {
13254            «a();
13255            b();
13256            c();ˇ»
13257        }
13258    "});
13259}
13260
13261#[gpui::test]
13262async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13263    init_test(cx, |_| {});
13264
13265    let language = Arc::new(Language::new(
13266        LanguageConfig {
13267            line_comments: vec!["// ".into()],
13268            ..Default::default()
13269        },
13270        Some(tree_sitter_rust::LANGUAGE.into()),
13271    ));
13272
13273    let mut cx = EditorTestContext::new(cx).await;
13274
13275    cx.language_registry().add(language.clone());
13276    cx.update_buffer(|buffer, cx| {
13277        buffer.set_language(Some(language), cx);
13278    });
13279
13280    let toggle_comments = &ToggleComments {
13281        advance_downwards: true,
13282        ignore_indent: false,
13283    };
13284
13285    // Single cursor on one line -> advance
13286    // Cursor moves horizontally 3 characters as well on non-blank line
13287    cx.set_state(indoc!(
13288        "fn a() {
13289             ˇdog();
13290             cat();
13291        }"
13292    ));
13293    cx.update_editor(|editor, window, cx| {
13294        editor.toggle_comments(toggle_comments, window, cx);
13295    });
13296    cx.assert_editor_state(indoc!(
13297        "fn a() {
13298             // dog();
13299             catˇ();
13300        }"
13301    ));
13302
13303    // Single selection on one line -> don't advance
13304    cx.set_state(indoc!(
13305        "fn a() {
13306             «dog()ˇ»;
13307             cat();
13308        }"
13309    ));
13310    cx.update_editor(|editor, window, cx| {
13311        editor.toggle_comments(toggle_comments, window, cx);
13312    });
13313    cx.assert_editor_state(indoc!(
13314        "fn a() {
13315             // «dog()ˇ»;
13316             cat();
13317        }"
13318    ));
13319
13320    // Multiple cursors on one line -> advance
13321    cx.set_state(indoc!(
13322        "fn a() {
13323             ˇdˇog();
13324             cat();
13325        }"
13326    ));
13327    cx.update_editor(|editor, window, cx| {
13328        editor.toggle_comments(toggle_comments, window, cx);
13329    });
13330    cx.assert_editor_state(indoc!(
13331        "fn a() {
13332             // dog();
13333             catˇ(ˇ);
13334        }"
13335    ));
13336
13337    // Multiple cursors on one line, with selection -> don't advance
13338    cx.set_state(indoc!(
13339        "fn a() {
13340             ˇdˇog«()ˇ»;
13341             cat();
13342        }"
13343    ));
13344    cx.update_editor(|editor, window, cx| {
13345        editor.toggle_comments(toggle_comments, window, cx);
13346    });
13347    cx.assert_editor_state(indoc!(
13348        "fn a() {
13349             // ˇdˇog«()ˇ»;
13350             cat();
13351        }"
13352    ));
13353
13354    // Single cursor on one line -> advance
13355    // Cursor moves to column 0 on blank line
13356    cx.set_state(indoc!(
13357        "fn a() {
13358             ˇdog();
13359
13360             cat();
13361        }"
13362    ));
13363    cx.update_editor(|editor, window, cx| {
13364        editor.toggle_comments(toggle_comments, window, cx);
13365    });
13366    cx.assert_editor_state(indoc!(
13367        "fn a() {
13368             // dog();
13369        ˇ
13370             cat();
13371        }"
13372    ));
13373
13374    // Single cursor on one line -> advance
13375    // Cursor starts and ends at column 0
13376    cx.set_state(indoc!(
13377        "fn a() {
13378         ˇ    dog();
13379             cat();
13380        }"
13381    ));
13382    cx.update_editor(|editor, window, cx| {
13383        editor.toggle_comments(toggle_comments, window, cx);
13384    });
13385    cx.assert_editor_state(indoc!(
13386        "fn a() {
13387             // dog();
13388         ˇ    cat();
13389        }"
13390    ));
13391}
13392
13393#[gpui::test]
13394async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13395    init_test(cx, |_| {});
13396
13397    let mut cx = EditorTestContext::new(cx).await;
13398
13399    let html_language = Arc::new(
13400        Language::new(
13401            LanguageConfig {
13402                name: "HTML".into(),
13403                block_comment: Some(("<!-- ".into(), " -->".into())),
13404                ..Default::default()
13405            },
13406            Some(tree_sitter_html::LANGUAGE.into()),
13407        )
13408        .with_injection_query(
13409            r#"
13410            (script_element
13411                (raw_text) @injection.content
13412                (#set! injection.language "javascript"))
13413            "#,
13414        )
13415        .unwrap(),
13416    );
13417
13418    let javascript_language = Arc::new(Language::new(
13419        LanguageConfig {
13420            name: "JavaScript".into(),
13421            line_comments: vec!["// ".into()],
13422            ..Default::default()
13423        },
13424        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13425    ));
13426
13427    cx.language_registry().add(html_language.clone());
13428    cx.language_registry().add(javascript_language.clone());
13429    cx.update_buffer(|buffer, cx| {
13430        buffer.set_language(Some(html_language), cx);
13431    });
13432
13433    // Toggle comments for empty selections
13434    cx.set_state(
13435        &r#"
13436            <p>A</p>ˇ
13437            <p>B</p>ˇ
13438            <p>C</p>ˇ
13439        "#
13440        .unindent(),
13441    );
13442    cx.update_editor(|editor, window, cx| {
13443        editor.toggle_comments(&ToggleComments::default(), window, cx)
13444    });
13445    cx.assert_editor_state(
13446        &r#"
13447            <!-- <p>A</p>ˇ -->
13448            <!-- <p>B</p>ˇ -->
13449            <!-- <p>C</p>ˇ -->
13450        "#
13451        .unindent(),
13452    );
13453    cx.update_editor(|editor, window, cx| {
13454        editor.toggle_comments(&ToggleComments::default(), window, cx)
13455    });
13456    cx.assert_editor_state(
13457        &r#"
13458            <p>A</p>ˇ
13459            <p>B</p>ˇ
13460            <p>C</p>ˇ
13461        "#
13462        .unindent(),
13463    );
13464
13465    // Toggle comments for mixture of empty and non-empty selections, where
13466    // multiple selections occupy a given line.
13467    cx.set_state(
13468        &r#"
13469            <p>A«</p>
13470            <p>ˇ»B</p>ˇ
13471            <p>C«</p>
13472            <p>ˇ»D</p>ˇ
13473        "#
13474        .unindent(),
13475    );
13476
13477    cx.update_editor(|editor, window, cx| {
13478        editor.toggle_comments(&ToggleComments::default(), window, cx)
13479    });
13480    cx.assert_editor_state(
13481        &r#"
13482            <!-- <p>A«</p>
13483            <p>ˇ»B</p>ˇ -->
13484            <!-- <p>C«</p>
13485            <p>ˇ»D</p>ˇ -->
13486        "#
13487        .unindent(),
13488    );
13489    cx.update_editor(|editor, window, cx| {
13490        editor.toggle_comments(&ToggleComments::default(), window, cx)
13491    });
13492    cx.assert_editor_state(
13493        &r#"
13494            <p>A«</p>
13495            <p>ˇ»B</p>ˇ
13496            <p>C«</p>
13497            <p>ˇ»D</p>ˇ
13498        "#
13499        .unindent(),
13500    );
13501
13502    // Toggle comments when different languages are active for different
13503    // selections.
13504    cx.set_state(
13505        &r#"
13506            ˇ<script>
13507                ˇvar x = new Y();
13508            ˇ</script>
13509        "#
13510        .unindent(),
13511    );
13512    cx.executor().run_until_parked();
13513    cx.update_editor(|editor, window, cx| {
13514        editor.toggle_comments(&ToggleComments::default(), window, cx)
13515    });
13516    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13517    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13518    cx.assert_editor_state(
13519        &r#"
13520            <!-- ˇ<script> -->
13521                // ˇvar x = new Y();
13522            <!-- ˇ</script> -->
13523        "#
13524        .unindent(),
13525    );
13526}
13527
13528#[gpui::test]
13529fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13530    init_test(cx, |_| {});
13531
13532    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13533    let multibuffer = cx.new(|cx| {
13534        let mut multibuffer = MultiBuffer::new(ReadWrite);
13535        multibuffer.push_excerpts(
13536            buffer.clone(),
13537            [
13538                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13539                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13540            ],
13541            cx,
13542        );
13543        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13544        multibuffer
13545    });
13546
13547    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13548    editor.update_in(cx, |editor, window, cx| {
13549        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13550        editor.change_selections(None, window, cx, |s| {
13551            s.select_ranges([
13552                Point::new(0, 0)..Point::new(0, 0),
13553                Point::new(1, 0)..Point::new(1, 0),
13554            ])
13555        });
13556
13557        editor.handle_input("X", window, cx);
13558        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13559        assert_eq!(
13560            editor.selections.ranges(cx),
13561            [
13562                Point::new(0, 1)..Point::new(0, 1),
13563                Point::new(1, 1)..Point::new(1, 1),
13564            ]
13565        );
13566
13567        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13568        editor.change_selections(None, window, cx, |s| {
13569            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13570        });
13571        editor.backspace(&Default::default(), window, cx);
13572        assert_eq!(editor.text(cx), "Xa\nbbb");
13573        assert_eq!(
13574            editor.selections.ranges(cx),
13575            [Point::new(1, 0)..Point::new(1, 0)]
13576        );
13577
13578        editor.change_selections(None, window, cx, |s| {
13579            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13580        });
13581        editor.backspace(&Default::default(), window, cx);
13582        assert_eq!(editor.text(cx), "X\nbb");
13583        assert_eq!(
13584            editor.selections.ranges(cx),
13585            [Point::new(0, 1)..Point::new(0, 1)]
13586        );
13587    });
13588}
13589
13590#[gpui::test]
13591fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13592    init_test(cx, |_| {});
13593
13594    let markers = vec![('[', ']').into(), ('(', ')').into()];
13595    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13596        indoc! {"
13597            [aaaa
13598            (bbbb]
13599            cccc)",
13600        },
13601        markers.clone(),
13602    );
13603    let excerpt_ranges = markers.into_iter().map(|marker| {
13604        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13605        ExcerptRange::new(context.clone())
13606    });
13607    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13608    let multibuffer = cx.new(|cx| {
13609        let mut multibuffer = MultiBuffer::new(ReadWrite);
13610        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13611        multibuffer
13612    });
13613
13614    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13615    editor.update_in(cx, |editor, window, cx| {
13616        let (expected_text, selection_ranges) = marked_text_ranges(
13617            indoc! {"
13618                aaaa
13619                bˇbbb
13620                bˇbbˇb
13621                cccc"
13622            },
13623            true,
13624        );
13625        assert_eq!(editor.text(cx), expected_text);
13626        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13627
13628        editor.handle_input("X", window, cx);
13629
13630        let (expected_text, expected_selections) = marked_text_ranges(
13631            indoc! {"
13632                aaaa
13633                bXˇbbXb
13634                bXˇbbXˇb
13635                cccc"
13636            },
13637            false,
13638        );
13639        assert_eq!(editor.text(cx), expected_text);
13640        assert_eq!(editor.selections.ranges(cx), expected_selections);
13641
13642        editor.newline(&Newline, window, cx);
13643        let (expected_text, expected_selections) = marked_text_ranges(
13644            indoc! {"
13645                aaaa
13646                bX
13647                ˇbbX
13648                b
13649                bX
13650                ˇbbX
13651                ˇb
13652                cccc"
13653            },
13654            false,
13655        );
13656        assert_eq!(editor.text(cx), expected_text);
13657        assert_eq!(editor.selections.ranges(cx), expected_selections);
13658    });
13659}
13660
13661#[gpui::test]
13662fn test_refresh_selections(cx: &mut TestAppContext) {
13663    init_test(cx, |_| {});
13664
13665    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13666    let mut excerpt1_id = None;
13667    let multibuffer = cx.new(|cx| {
13668        let mut multibuffer = MultiBuffer::new(ReadWrite);
13669        excerpt1_id = multibuffer
13670            .push_excerpts(
13671                buffer.clone(),
13672                [
13673                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13674                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13675                ],
13676                cx,
13677            )
13678            .into_iter()
13679            .next();
13680        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13681        multibuffer
13682    });
13683
13684    let editor = cx.add_window(|window, cx| {
13685        let mut editor = build_editor(multibuffer.clone(), window, cx);
13686        let snapshot = editor.snapshot(window, cx);
13687        editor.change_selections(None, window, cx, |s| {
13688            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13689        });
13690        editor.begin_selection(
13691            Point::new(2, 1).to_display_point(&snapshot),
13692            true,
13693            1,
13694            window,
13695            cx,
13696        );
13697        assert_eq!(
13698            editor.selections.ranges(cx),
13699            [
13700                Point::new(1, 3)..Point::new(1, 3),
13701                Point::new(2, 1)..Point::new(2, 1),
13702            ]
13703        );
13704        editor
13705    });
13706
13707    // Refreshing selections is a no-op when excerpts haven't changed.
13708    _ = editor.update(cx, |editor, window, cx| {
13709        editor.change_selections(None, window, cx, |s| s.refresh());
13710        assert_eq!(
13711            editor.selections.ranges(cx),
13712            [
13713                Point::new(1, 3)..Point::new(1, 3),
13714                Point::new(2, 1)..Point::new(2, 1),
13715            ]
13716        );
13717    });
13718
13719    multibuffer.update(cx, |multibuffer, cx| {
13720        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13721    });
13722    _ = editor.update(cx, |editor, window, cx| {
13723        // Removing an excerpt causes the first selection to become degenerate.
13724        assert_eq!(
13725            editor.selections.ranges(cx),
13726            [
13727                Point::new(0, 0)..Point::new(0, 0),
13728                Point::new(0, 1)..Point::new(0, 1)
13729            ]
13730        );
13731
13732        // Refreshing selections will relocate the first selection to the original buffer
13733        // location.
13734        editor.change_selections(None, window, cx, |s| s.refresh());
13735        assert_eq!(
13736            editor.selections.ranges(cx),
13737            [
13738                Point::new(0, 1)..Point::new(0, 1),
13739                Point::new(0, 3)..Point::new(0, 3)
13740            ]
13741        );
13742        assert!(editor.selections.pending_anchor().is_some());
13743    });
13744}
13745
13746#[gpui::test]
13747fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13748    init_test(cx, |_| {});
13749
13750    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13751    let mut excerpt1_id = None;
13752    let multibuffer = cx.new(|cx| {
13753        let mut multibuffer = MultiBuffer::new(ReadWrite);
13754        excerpt1_id = multibuffer
13755            .push_excerpts(
13756                buffer.clone(),
13757                [
13758                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13759                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13760                ],
13761                cx,
13762            )
13763            .into_iter()
13764            .next();
13765        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13766        multibuffer
13767    });
13768
13769    let editor = cx.add_window(|window, cx| {
13770        let mut editor = build_editor(multibuffer.clone(), window, cx);
13771        let snapshot = editor.snapshot(window, cx);
13772        editor.begin_selection(
13773            Point::new(1, 3).to_display_point(&snapshot),
13774            false,
13775            1,
13776            window,
13777            cx,
13778        );
13779        assert_eq!(
13780            editor.selections.ranges(cx),
13781            [Point::new(1, 3)..Point::new(1, 3)]
13782        );
13783        editor
13784    });
13785
13786    multibuffer.update(cx, |multibuffer, cx| {
13787        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13788    });
13789    _ = editor.update(cx, |editor, window, cx| {
13790        assert_eq!(
13791            editor.selections.ranges(cx),
13792            [Point::new(0, 0)..Point::new(0, 0)]
13793        );
13794
13795        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13796        editor.change_selections(None, window, cx, |s| s.refresh());
13797        assert_eq!(
13798            editor.selections.ranges(cx),
13799            [Point::new(0, 3)..Point::new(0, 3)]
13800        );
13801        assert!(editor.selections.pending_anchor().is_some());
13802    });
13803}
13804
13805#[gpui::test]
13806async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13807    init_test(cx, |_| {});
13808
13809    let language = Arc::new(
13810        Language::new(
13811            LanguageConfig {
13812                brackets: BracketPairConfig {
13813                    pairs: vec![
13814                        BracketPair {
13815                            start: "{".to_string(),
13816                            end: "}".to_string(),
13817                            close: true,
13818                            surround: true,
13819                            newline: true,
13820                        },
13821                        BracketPair {
13822                            start: "/* ".to_string(),
13823                            end: " */".to_string(),
13824                            close: true,
13825                            surround: true,
13826                            newline: true,
13827                        },
13828                    ],
13829                    ..Default::default()
13830                },
13831                ..Default::default()
13832            },
13833            Some(tree_sitter_rust::LANGUAGE.into()),
13834        )
13835        .with_indents_query("")
13836        .unwrap(),
13837    );
13838
13839    let text = concat!(
13840        "{   }\n",     //
13841        "  x\n",       //
13842        "  /*   */\n", //
13843        "x\n",         //
13844        "{{} }\n",     //
13845    );
13846
13847    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13848    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13849    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13850    editor
13851        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13852        .await;
13853
13854    editor.update_in(cx, |editor, window, cx| {
13855        editor.change_selections(None, window, cx, |s| {
13856            s.select_display_ranges([
13857                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13858                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13859                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13860            ])
13861        });
13862        editor.newline(&Newline, window, cx);
13863
13864        assert_eq!(
13865            editor.buffer().read(cx).read(cx).text(),
13866            concat!(
13867                "{ \n",    // Suppress rustfmt
13868                "\n",      //
13869                "}\n",     //
13870                "  x\n",   //
13871                "  /* \n", //
13872                "  \n",    //
13873                "  */\n",  //
13874                "x\n",     //
13875                "{{} \n",  //
13876                "}\n",     //
13877            )
13878        );
13879    });
13880}
13881
13882#[gpui::test]
13883fn test_highlighted_ranges(cx: &mut TestAppContext) {
13884    init_test(cx, |_| {});
13885
13886    let editor = cx.add_window(|window, cx| {
13887        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13888        build_editor(buffer.clone(), window, cx)
13889    });
13890
13891    _ = editor.update(cx, |editor, window, cx| {
13892        struct Type1;
13893        struct Type2;
13894
13895        let buffer = editor.buffer.read(cx).snapshot(cx);
13896
13897        let anchor_range =
13898            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13899
13900        editor.highlight_background::<Type1>(
13901            &[
13902                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13903                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13904                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13905                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13906            ],
13907            |_| Hsla::red(),
13908            cx,
13909        );
13910        editor.highlight_background::<Type2>(
13911            &[
13912                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13913                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13914                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13915                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13916            ],
13917            |_| Hsla::green(),
13918            cx,
13919        );
13920
13921        let snapshot = editor.snapshot(window, cx);
13922        let mut highlighted_ranges = editor.background_highlights_in_range(
13923            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13924            &snapshot,
13925            cx.theme(),
13926        );
13927        // Enforce a consistent ordering based on color without relying on the ordering of the
13928        // highlight's `TypeId` which is non-executor.
13929        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13930        assert_eq!(
13931            highlighted_ranges,
13932            &[
13933                (
13934                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13935                    Hsla::red(),
13936                ),
13937                (
13938                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13939                    Hsla::red(),
13940                ),
13941                (
13942                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13943                    Hsla::green(),
13944                ),
13945                (
13946                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13947                    Hsla::green(),
13948                ),
13949            ]
13950        );
13951        assert_eq!(
13952            editor.background_highlights_in_range(
13953                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13954                &snapshot,
13955                cx.theme(),
13956            ),
13957            &[(
13958                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13959                Hsla::red(),
13960            )]
13961        );
13962    });
13963}
13964
13965#[gpui::test]
13966async fn test_following(cx: &mut TestAppContext) {
13967    init_test(cx, |_| {});
13968
13969    let fs = FakeFs::new(cx.executor());
13970    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13971
13972    let buffer = project.update(cx, |project, cx| {
13973        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13974        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13975    });
13976    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13977    let follower = cx.update(|cx| {
13978        cx.open_window(
13979            WindowOptions {
13980                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13981                    gpui::Point::new(px(0.), px(0.)),
13982                    gpui::Point::new(px(10.), px(80.)),
13983                ))),
13984                ..Default::default()
13985            },
13986            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13987        )
13988        .unwrap()
13989    });
13990
13991    let is_still_following = Rc::new(RefCell::new(true));
13992    let follower_edit_event_count = Rc::new(RefCell::new(0));
13993    let pending_update = Rc::new(RefCell::new(None));
13994    let leader_entity = leader.root(cx).unwrap();
13995    let follower_entity = follower.root(cx).unwrap();
13996    _ = follower.update(cx, {
13997        let update = pending_update.clone();
13998        let is_still_following = is_still_following.clone();
13999        let follower_edit_event_count = follower_edit_event_count.clone();
14000        |_, window, cx| {
14001            cx.subscribe_in(
14002                &leader_entity,
14003                window,
14004                move |_, leader, event, window, cx| {
14005                    leader.read(cx).add_event_to_update_proto(
14006                        event,
14007                        &mut update.borrow_mut(),
14008                        window,
14009                        cx,
14010                    );
14011                },
14012            )
14013            .detach();
14014
14015            cx.subscribe_in(
14016                &follower_entity,
14017                window,
14018                move |_, _, event: &EditorEvent, _window, _cx| {
14019                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14020                        *is_still_following.borrow_mut() = false;
14021                    }
14022
14023                    if let EditorEvent::BufferEdited = event {
14024                        *follower_edit_event_count.borrow_mut() += 1;
14025                    }
14026                },
14027            )
14028            .detach();
14029        }
14030    });
14031
14032    // Update the selections only
14033    _ = leader.update(cx, |leader, window, cx| {
14034        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
14035    });
14036    follower
14037        .update(cx, |follower, window, cx| {
14038            follower.apply_update_proto(
14039                &project,
14040                pending_update.borrow_mut().take().unwrap(),
14041                window,
14042                cx,
14043            )
14044        })
14045        .unwrap()
14046        .await
14047        .unwrap();
14048    _ = follower.update(cx, |follower, _, cx| {
14049        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14050    });
14051    assert!(*is_still_following.borrow());
14052    assert_eq!(*follower_edit_event_count.borrow(), 0);
14053
14054    // Update the scroll position only
14055    _ = leader.update(cx, |leader, window, cx| {
14056        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14057    });
14058    follower
14059        .update(cx, |follower, window, cx| {
14060            follower.apply_update_proto(
14061                &project,
14062                pending_update.borrow_mut().take().unwrap(),
14063                window,
14064                cx,
14065            )
14066        })
14067        .unwrap()
14068        .await
14069        .unwrap();
14070    assert_eq!(
14071        follower
14072            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14073            .unwrap(),
14074        gpui::Point::new(1.5, 3.5)
14075    );
14076    assert!(*is_still_following.borrow());
14077    assert_eq!(*follower_edit_event_count.borrow(), 0);
14078
14079    // Update the selections and scroll position. The follower's scroll position is updated
14080    // via autoscroll, not via the leader's exact scroll position.
14081    _ = leader.update(cx, |leader, window, cx| {
14082        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
14083        leader.request_autoscroll(Autoscroll::newest(), cx);
14084        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14085    });
14086    follower
14087        .update(cx, |follower, window, cx| {
14088            follower.apply_update_proto(
14089                &project,
14090                pending_update.borrow_mut().take().unwrap(),
14091                window,
14092                cx,
14093            )
14094        })
14095        .unwrap()
14096        .await
14097        .unwrap();
14098    _ = follower.update(cx, |follower, _, cx| {
14099        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14100        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14101    });
14102    assert!(*is_still_following.borrow());
14103
14104    // Creating a pending selection that precedes another selection
14105    _ = leader.update(cx, |leader, window, cx| {
14106        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
14107        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14108    });
14109    follower
14110        .update(cx, |follower, window, cx| {
14111            follower.apply_update_proto(
14112                &project,
14113                pending_update.borrow_mut().take().unwrap(),
14114                window,
14115                cx,
14116            )
14117        })
14118        .unwrap()
14119        .await
14120        .unwrap();
14121    _ = follower.update(cx, |follower, _, cx| {
14122        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14123    });
14124    assert!(*is_still_following.borrow());
14125
14126    // Extend the pending selection so that it surrounds another selection
14127    _ = leader.update(cx, |leader, window, cx| {
14128        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14129    });
14130    follower
14131        .update(cx, |follower, window, cx| {
14132            follower.apply_update_proto(
14133                &project,
14134                pending_update.borrow_mut().take().unwrap(),
14135                window,
14136                cx,
14137            )
14138        })
14139        .unwrap()
14140        .await
14141        .unwrap();
14142    _ = follower.update(cx, |follower, _, cx| {
14143        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14144    });
14145
14146    // Scrolling locally breaks the follow
14147    _ = follower.update(cx, |follower, window, cx| {
14148        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14149        follower.set_scroll_anchor(
14150            ScrollAnchor {
14151                anchor: top_anchor,
14152                offset: gpui::Point::new(0.0, 0.5),
14153            },
14154            window,
14155            cx,
14156        );
14157    });
14158    assert!(!(*is_still_following.borrow()));
14159}
14160
14161#[gpui::test]
14162async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14163    init_test(cx, |_| {});
14164
14165    let fs = FakeFs::new(cx.executor());
14166    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14167    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14168    let pane = workspace
14169        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14170        .unwrap();
14171
14172    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14173
14174    let leader = pane.update_in(cx, |_, window, cx| {
14175        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14176        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14177    });
14178
14179    // Start following the editor when it has no excerpts.
14180    let mut state_message =
14181        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14182    let workspace_entity = workspace.root(cx).unwrap();
14183    let follower_1 = cx
14184        .update_window(*workspace.deref(), |_, window, cx| {
14185            Editor::from_state_proto(
14186                workspace_entity,
14187                ViewId {
14188                    creator: CollaboratorId::PeerId(PeerId::default()),
14189                    id: 0,
14190                },
14191                &mut state_message,
14192                window,
14193                cx,
14194            )
14195        })
14196        .unwrap()
14197        .unwrap()
14198        .await
14199        .unwrap();
14200
14201    let update_message = Rc::new(RefCell::new(None));
14202    follower_1.update_in(cx, {
14203        let update = update_message.clone();
14204        |_, window, cx| {
14205            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14206                leader.read(cx).add_event_to_update_proto(
14207                    event,
14208                    &mut update.borrow_mut(),
14209                    window,
14210                    cx,
14211                );
14212            })
14213            .detach();
14214        }
14215    });
14216
14217    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14218        (
14219            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14220            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14221        )
14222    });
14223
14224    // Insert some excerpts.
14225    leader.update(cx, |leader, cx| {
14226        leader.buffer.update(cx, |multibuffer, cx| {
14227            multibuffer.set_excerpts_for_path(
14228                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14229                buffer_1.clone(),
14230                vec![
14231                    Point::row_range(0..3),
14232                    Point::row_range(1..6),
14233                    Point::row_range(12..15),
14234                ],
14235                0,
14236                cx,
14237            );
14238            multibuffer.set_excerpts_for_path(
14239                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14240                buffer_2.clone(),
14241                vec![Point::row_range(0..6), Point::row_range(8..12)],
14242                0,
14243                cx,
14244            );
14245        });
14246    });
14247
14248    // Apply the update of adding the excerpts.
14249    follower_1
14250        .update_in(cx, |follower, window, cx| {
14251            follower.apply_update_proto(
14252                &project,
14253                update_message.borrow().clone().unwrap(),
14254                window,
14255                cx,
14256            )
14257        })
14258        .await
14259        .unwrap();
14260    assert_eq!(
14261        follower_1.update(cx, |editor, cx| editor.text(cx)),
14262        leader.update(cx, |editor, cx| editor.text(cx))
14263    );
14264    update_message.borrow_mut().take();
14265
14266    // Start following separately after it already has excerpts.
14267    let mut state_message =
14268        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14269    let workspace_entity = workspace.root(cx).unwrap();
14270    let follower_2 = cx
14271        .update_window(*workspace.deref(), |_, window, cx| {
14272            Editor::from_state_proto(
14273                workspace_entity,
14274                ViewId {
14275                    creator: CollaboratorId::PeerId(PeerId::default()),
14276                    id: 0,
14277                },
14278                &mut state_message,
14279                window,
14280                cx,
14281            )
14282        })
14283        .unwrap()
14284        .unwrap()
14285        .await
14286        .unwrap();
14287    assert_eq!(
14288        follower_2.update(cx, |editor, cx| editor.text(cx)),
14289        leader.update(cx, |editor, cx| editor.text(cx))
14290    );
14291
14292    // Remove some excerpts.
14293    leader.update(cx, |leader, cx| {
14294        leader.buffer.update(cx, |multibuffer, cx| {
14295            let excerpt_ids = multibuffer.excerpt_ids();
14296            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14297            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14298        });
14299    });
14300
14301    // Apply the update of removing the excerpts.
14302    follower_1
14303        .update_in(cx, |follower, window, cx| {
14304            follower.apply_update_proto(
14305                &project,
14306                update_message.borrow().clone().unwrap(),
14307                window,
14308                cx,
14309            )
14310        })
14311        .await
14312        .unwrap();
14313    follower_2
14314        .update_in(cx, |follower, window, cx| {
14315            follower.apply_update_proto(
14316                &project,
14317                update_message.borrow().clone().unwrap(),
14318                window,
14319                cx,
14320            )
14321        })
14322        .await
14323        .unwrap();
14324    update_message.borrow_mut().take();
14325    assert_eq!(
14326        follower_1.update(cx, |editor, cx| editor.text(cx)),
14327        leader.update(cx, |editor, cx| editor.text(cx))
14328    );
14329}
14330
14331#[gpui::test]
14332async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14333    init_test(cx, |_| {});
14334
14335    let mut cx = EditorTestContext::new(cx).await;
14336    let lsp_store =
14337        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14338
14339    cx.set_state(indoc! {"
14340        ˇfn func(abc def: i32) -> u32 {
14341        }
14342    "});
14343
14344    cx.update(|_, cx| {
14345        lsp_store.update(cx, |lsp_store, cx| {
14346            lsp_store
14347                .update_diagnostics(
14348                    LanguageServerId(0),
14349                    lsp::PublishDiagnosticsParams {
14350                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14351                        version: None,
14352                        diagnostics: vec![
14353                            lsp::Diagnostic {
14354                                range: lsp::Range::new(
14355                                    lsp::Position::new(0, 11),
14356                                    lsp::Position::new(0, 12),
14357                                ),
14358                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14359                                ..Default::default()
14360                            },
14361                            lsp::Diagnostic {
14362                                range: lsp::Range::new(
14363                                    lsp::Position::new(0, 12),
14364                                    lsp::Position::new(0, 15),
14365                                ),
14366                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14367                                ..Default::default()
14368                            },
14369                            lsp::Diagnostic {
14370                                range: lsp::Range::new(
14371                                    lsp::Position::new(0, 25),
14372                                    lsp::Position::new(0, 28),
14373                                ),
14374                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14375                                ..Default::default()
14376                            },
14377                        ],
14378                    },
14379                    None,
14380                    DiagnosticSourceKind::Pushed,
14381                    &[],
14382                    cx,
14383                )
14384                .unwrap()
14385        });
14386    });
14387
14388    executor.run_until_parked();
14389
14390    cx.update_editor(|editor, window, cx| {
14391        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14392    });
14393
14394    cx.assert_editor_state(indoc! {"
14395        fn func(abc def: i32) -> ˇu32 {
14396        }
14397    "});
14398
14399    cx.update_editor(|editor, window, cx| {
14400        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14401    });
14402
14403    cx.assert_editor_state(indoc! {"
14404        fn func(abc ˇdef: i32) -> u32 {
14405        }
14406    "});
14407
14408    cx.update_editor(|editor, window, cx| {
14409        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14410    });
14411
14412    cx.assert_editor_state(indoc! {"
14413        fn func(abcˇ def: i32) -> u32 {
14414        }
14415    "});
14416
14417    cx.update_editor(|editor, window, cx| {
14418        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14419    });
14420
14421    cx.assert_editor_state(indoc! {"
14422        fn func(abc def: i32) -> ˇu32 {
14423        }
14424    "});
14425}
14426
14427#[gpui::test]
14428async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14429    init_test(cx, |_| {});
14430
14431    let mut cx = EditorTestContext::new(cx).await;
14432
14433    let diff_base = r#"
14434        use some::mod;
14435
14436        const A: u32 = 42;
14437
14438        fn main() {
14439            println!("hello");
14440
14441            println!("world");
14442        }
14443        "#
14444    .unindent();
14445
14446    // Edits are modified, removed, modified, added
14447    cx.set_state(
14448        &r#"
14449        use some::modified;
14450
14451        ˇ
14452        fn main() {
14453            println!("hello there");
14454
14455            println!("around the");
14456            println!("world");
14457        }
14458        "#
14459        .unindent(),
14460    );
14461
14462    cx.set_head_text(&diff_base);
14463    executor.run_until_parked();
14464
14465    cx.update_editor(|editor, window, cx| {
14466        //Wrap around the bottom of the buffer
14467        for _ in 0..3 {
14468            editor.go_to_next_hunk(&GoToHunk, window, cx);
14469        }
14470    });
14471
14472    cx.assert_editor_state(
14473        &r#"
14474        ˇuse some::modified;
14475
14476
14477        fn main() {
14478            println!("hello there");
14479
14480            println!("around the");
14481            println!("world");
14482        }
14483        "#
14484        .unindent(),
14485    );
14486
14487    cx.update_editor(|editor, window, cx| {
14488        //Wrap around the top of the buffer
14489        for _ in 0..2 {
14490            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14491        }
14492    });
14493
14494    cx.assert_editor_state(
14495        &r#"
14496        use some::modified;
14497
14498
14499        fn main() {
14500        ˇ    println!("hello there");
14501
14502            println!("around the");
14503            println!("world");
14504        }
14505        "#
14506        .unindent(),
14507    );
14508
14509    cx.update_editor(|editor, window, cx| {
14510        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14511    });
14512
14513    cx.assert_editor_state(
14514        &r#"
14515        use some::modified;
14516
14517        ˇ
14518        fn main() {
14519            println!("hello there");
14520
14521            println!("around the");
14522            println!("world");
14523        }
14524        "#
14525        .unindent(),
14526    );
14527
14528    cx.update_editor(|editor, window, cx| {
14529        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14530    });
14531
14532    cx.assert_editor_state(
14533        &r#"
14534        ˇuse some::modified;
14535
14536
14537        fn main() {
14538            println!("hello there");
14539
14540            println!("around the");
14541            println!("world");
14542        }
14543        "#
14544        .unindent(),
14545    );
14546
14547    cx.update_editor(|editor, window, cx| {
14548        for _ in 0..2 {
14549            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14550        }
14551    });
14552
14553    cx.assert_editor_state(
14554        &r#"
14555        use some::modified;
14556
14557
14558        fn main() {
14559        ˇ    println!("hello there");
14560
14561            println!("around the");
14562            println!("world");
14563        }
14564        "#
14565        .unindent(),
14566    );
14567
14568    cx.update_editor(|editor, window, cx| {
14569        editor.fold(&Fold, window, cx);
14570    });
14571
14572    cx.update_editor(|editor, window, cx| {
14573        editor.go_to_next_hunk(&GoToHunk, window, cx);
14574    });
14575
14576    cx.assert_editor_state(
14577        &r#"
14578        ˇuse some::modified;
14579
14580
14581        fn main() {
14582            println!("hello there");
14583
14584            println!("around the");
14585            println!("world");
14586        }
14587        "#
14588        .unindent(),
14589    );
14590}
14591
14592#[test]
14593fn test_split_words() {
14594    fn split(text: &str) -> Vec<&str> {
14595        split_words(text).collect()
14596    }
14597
14598    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14599    assert_eq!(split("hello_world"), &["hello_", "world"]);
14600    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14601    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14602    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14603    assert_eq!(split("helloworld"), &["helloworld"]);
14604
14605    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14606}
14607
14608#[gpui::test]
14609async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14610    init_test(cx, |_| {});
14611
14612    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14613    let mut assert = |before, after| {
14614        let _state_context = cx.set_state(before);
14615        cx.run_until_parked();
14616        cx.update_editor(|editor, window, cx| {
14617            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14618        });
14619        cx.run_until_parked();
14620        cx.assert_editor_state(after);
14621    };
14622
14623    // Outside bracket jumps to outside of matching bracket
14624    assert("console.logˇ(var);", "console.log(var)ˇ;");
14625    assert("console.log(var)ˇ;", "console.logˇ(var);");
14626
14627    // Inside bracket jumps to inside of matching bracket
14628    assert("console.log(ˇvar);", "console.log(varˇ);");
14629    assert("console.log(varˇ);", "console.log(ˇvar);");
14630
14631    // When outside a bracket and inside, favor jumping to the inside bracket
14632    assert(
14633        "console.log('foo', [1, 2, 3]ˇ);",
14634        "console.log(ˇ'foo', [1, 2, 3]);",
14635    );
14636    assert(
14637        "console.log(ˇ'foo', [1, 2, 3]);",
14638        "console.log('foo', [1, 2, 3]ˇ);",
14639    );
14640
14641    // Bias forward if two options are equally likely
14642    assert(
14643        "let result = curried_fun()ˇ();",
14644        "let result = curried_fun()()ˇ;",
14645    );
14646
14647    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14648    assert(
14649        indoc! {"
14650            function test() {
14651                console.log('test')ˇ
14652            }"},
14653        indoc! {"
14654            function test() {
14655                console.logˇ('test')
14656            }"},
14657    );
14658}
14659
14660#[gpui::test]
14661async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14662    init_test(cx, |_| {});
14663
14664    let fs = FakeFs::new(cx.executor());
14665    fs.insert_tree(
14666        path!("/a"),
14667        json!({
14668            "main.rs": "fn main() { let a = 5; }",
14669            "other.rs": "// Test file",
14670        }),
14671    )
14672    .await;
14673    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14674
14675    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14676    language_registry.add(Arc::new(Language::new(
14677        LanguageConfig {
14678            name: "Rust".into(),
14679            matcher: LanguageMatcher {
14680                path_suffixes: vec!["rs".to_string()],
14681                ..Default::default()
14682            },
14683            brackets: BracketPairConfig {
14684                pairs: vec![BracketPair {
14685                    start: "{".to_string(),
14686                    end: "}".to_string(),
14687                    close: true,
14688                    surround: true,
14689                    newline: true,
14690                }],
14691                disabled_scopes_by_bracket_ix: Vec::new(),
14692            },
14693            ..Default::default()
14694        },
14695        Some(tree_sitter_rust::LANGUAGE.into()),
14696    )));
14697    let mut fake_servers = language_registry.register_fake_lsp(
14698        "Rust",
14699        FakeLspAdapter {
14700            capabilities: lsp::ServerCapabilities {
14701                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14702                    first_trigger_character: "{".to_string(),
14703                    more_trigger_character: None,
14704                }),
14705                ..Default::default()
14706            },
14707            ..Default::default()
14708        },
14709    );
14710
14711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14712
14713    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14714
14715    let worktree_id = workspace
14716        .update(cx, |workspace, _, cx| {
14717            workspace.project().update(cx, |project, cx| {
14718                project.worktrees(cx).next().unwrap().read(cx).id()
14719            })
14720        })
14721        .unwrap();
14722
14723    let buffer = project
14724        .update(cx, |project, cx| {
14725            project.open_local_buffer(path!("/a/main.rs"), cx)
14726        })
14727        .await
14728        .unwrap();
14729    let editor_handle = workspace
14730        .update(cx, |workspace, window, cx| {
14731            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14732        })
14733        .unwrap()
14734        .await
14735        .unwrap()
14736        .downcast::<Editor>()
14737        .unwrap();
14738
14739    cx.executor().start_waiting();
14740    let fake_server = fake_servers.next().await.unwrap();
14741
14742    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14743        |params, _| async move {
14744            assert_eq!(
14745                params.text_document_position.text_document.uri,
14746                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14747            );
14748            assert_eq!(
14749                params.text_document_position.position,
14750                lsp::Position::new(0, 21),
14751            );
14752
14753            Ok(Some(vec![lsp::TextEdit {
14754                new_text: "]".to_string(),
14755                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14756            }]))
14757        },
14758    );
14759
14760    editor_handle.update_in(cx, |editor, window, cx| {
14761        window.focus(&editor.focus_handle(cx));
14762        editor.change_selections(None, window, cx, |s| {
14763            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14764        });
14765        editor.handle_input("{", window, cx);
14766    });
14767
14768    cx.executor().run_until_parked();
14769
14770    buffer.update(cx, |buffer, _| {
14771        assert_eq!(
14772            buffer.text(),
14773            "fn main() { let a = {5}; }",
14774            "No extra braces from on type formatting should appear in the buffer"
14775        )
14776    });
14777}
14778
14779#[gpui::test(iterations = 20, seeds(31))]
14780async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14781    init_test(cx, |_| {});
14782
14783    let mut cx = EditorLspTestContext::new_rust(
14784        lsp::ServerCapabilities {
14785            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14786                first_trigger_character: ".".to_string(),
14787                more_trigger_character: None,
14788            }),
14789            ..Default::default()
14790        },
14791        cx,
14792    )
14793    .await;
14794
14795    cx.update_buffer(|buffer, _| {
14796        // This causes autoindent to be async.
14797        buffer.set_sync_parse_timeout(Duration::ZERO)
14798    });
14799
14800    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14801    cx.simulate_keystroke("\n");
14802    cx.run_until_parked();
14803
14804    let buffer_cloned =
14805        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14806    let mut request =
14807        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14808            let buffer_cloned = buffer_cloned.clone();
14809            async move {
14810                buffer_cloned.update(&mut cx, |buffer, _| {
14811                    assert_eq!(
14812                        buffer.text(),
14813                        "fn c() {\n    d()\n        .\n}\n",
14814                        "OnTypeFormatting should triggered after autoindent applied"
14815                    )
14816                })?;
14817
14818                Ok(Some(vec![]))
14819            }
14820        });
14821
14822    cx.simulate_keystroke(".");
14823    cx.run_until_parked();
14824
14825    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14826    assert!(request.next().await.is_some());
14827    request.close();
14828    assert!(request.next().await.is_none());
14829}
14830
14831#[gpui::test]
14832async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14833    init_test(cx, |_| {});
14834
14835    let fs = FakeFs::new(cx.executor());
14836    fs.insert_tree(
14837        path!("/a"),
14838        json!({
14839            "main.rs": "fn main() { let a = 5; }",
14840            "other.rs": "// Test file",
14841        }),
14842    )
14843    .await;
14844
14845    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14846
14847    let server_restarts = Arc::new(AtomicUsize::new(0));
14848    let closure_restarts = Arc::clone(&server_restarts);
14849    let language_server_name = "test language server";
14850    let language_name: LanguageName = "Rust".into();
14851
14852    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14853    language_registry.add(Arc::new(Language::new(
14854        LanguageConfig {
14855            name: language_name.clone(),
14856            matcher: LanguageMatcher {
14857                path_suffixes: vec!["rs".to_string()],
14858                ..Default::default()
14859            },
14860            ..Default::default()
14861        },
14862        Some(tree_sitter_rust::LANGUAGE.into()),
14863    )));
14864    let mut fake_servers = language_registry.register_fake_lsp(
14865        "Rust",
14866        FakeLspAdapter {
14867            name: language_server_name,
14868            initialization_options: Some(json!({
14869                "testOptionValue": true
14870            })),
14871            initializer: Some(Box::new(move |fake_server| {
14872                let task_restarts = Arc::clone(&closure_restarts);
14873                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14874                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14875                    futures::future::ready(Ok(()))
14876                });
14877            })),
14878            ..Default::default()
14879        },
14880    );
14881
14882    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14883    let _buffer = project
14884        .update(cx, |project, cx| {
14885            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14886        })
14887        .await
14888        .unwrap();
14889    let _fake_server = fake_servers.next().await.unwrap();
14890    update_test_language_settings(cx, |language_settings| {
14891        language_settings.languages.insert(
14892            language_name.clone(),
14893            LanguageSettingsContent {
14894                tab_size: NonZeroU32::new(8),
14895                ..Default::default()
14896            },
14897        );
14898    });
14899    cx.executor().run_until_parked();
14900    assert_eq!(
14901        server_restarts.load(atomic::Ordering::Acquire),
14902        0,
14903        "Should not restart LSP server on an unrelated change"
14904    );
14905
14906    update_test_project_settings(cx, |project_settings| {
14907        project_settings.lsp.insert(
14908            "Some other server name".into(),
14909            LspSettings {
14910                binary: None,
14911                settings: None,
14912                initialization_options: Some(json!({
14913                    "some other init value": false
14914                })),
14915                enable_lsp_tasks: false,
14916            },
14917        );
14918    });
14919    cx.executor().run_until_parked();
14920    assert_eq!(
14921        server_restarts.load(atomic::Ordering::Acquire),
14922        0,
14923        "Should not restart LSP server on an unrelated LSP settings change"
14924    );
14925
14926    update_test_project_settings(cx, |project_settings| {
14927        project_settings.lsp.insert(
14928            language_server_name.into(),
14929            LspSettings {
14930                binary: None,
14931                settings: None,
14932                initialization_options: Some(json!({
14933                    "anotherInitValue": false
14934                })),
14935                enable_lsp_tasks: false,
14936            },
14937        );
14938    });
14939    cx.executor().run_until_parked();
14940    assert_eq!(
14941        server_restarts.load(atomic::Ordering::Acquire),
14942        1,
14943        "Should restart LSP server on a related LSP settings change"
14944    );
14945
14946    update_test_project_settings(cx, |project_settings| {
14947        project_settings.lsp.insert(
14948            language_server_name.into(),
14949            LspSettings {
14950                binary: None,
14951                settings: None,
14952                initialization_options: Some(json!({
14953                    "anotherInitValue": false
14954                })),
14955                enable_lsp_tasks: false,
14956            },
14957        );
14958    });
14959    cx.executor().run_until_parked();
14960    assert_eq!(
14961        server_restarts.load(atomic::Ordering::Acquire),
14962        1,
14963        "Should not restart LSP server on a related LSP settings change that is the same"
14964    );
14965
14966    update_test_project_settings(cx, |project_settings| {
14967        project_settings.lsp.insert(
14968            language_server_name.into(),
14969            LspSettings {
14970                binary: None,
14971                settings: None,
14972                initialization_options: None,
14973                enable_lsp_tasks: false,
14974            },
14975        );
14976    });
14977    cx.executor().run_until_parked();
14978    assert_eq!(
14979        server_restarts.load(atomic::Ordering::Acquire),
14980        2,
14981        "Should restart LSP server on another related LSP settings change"
14982    );
14983}
14984
14985#[gpui::test]
14986async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14987    init_test(cx, |_| {});
14988
14989    let mut cx = EditorLspTestContext::new_rust(
14990        lsp::ServerCapabilities {
14991            completion_provider: Some(lsp::CompletionOptions {
14992                trigger_characters: Some(vec![".".to_string()]),
14993                resolve_provider: Some(true),
14994                ..Default::default()
14995            }),
14996            ..Default::default()
14997        },
14998        cx,
14999    )
15000    .await;
15001
15002    cx.set_state("fn main() { let a = 2ˇ; }");
15003    cx.simulate_keystroke(".");
15004    let completion_item = lsp::CompletionItem {
15005        label: "some".into(),
15006        kind: Some(lsp::CompletionItemKind::SNIPPET),
15007        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15008        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15009            kind: lsp::MarkupKind::Markdown,
15010            value: "```rust\nSome(2)\n```".to_string(),
15011        })),
15012        deprecated: Some(false),
15013        sort_text: Some("fffffff2".to_string()),
15014        filter_text: Some("some".to_string()),
15015        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15016        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15017            range: lsp::Range {
15018                start: lsp::Position {
15019                    line: 0,
15020                    character: 22,
15021                },
15022                end: lsp::Position {
15023                    line: 0,
15024                    character: 22,
15025                },
15026            },
15027            new_text: "Some(2)".to_string(),
15028        })),
15029        additional_text_edits: Some(vec![lsp::TextEdit {
15030            range: lsp::Range {
15031                start: lsp::Position {
15032                    line: 0,
15033                    character: 20,
15034                },
15035                end: lsp::Position {
15036                    line: 0,
15037                    character: 22,
15038                },
15039            },
15040            new_text: "".to_string(),
15041        }]),
15042        ..Default::default()
15043    };
15044
15045    let closure_completion_item = completion_item.clone();
15046    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15047        let task_completion_item = closure_completion_item.clone();
15048        async move {
15049            Ok(Some(lsp::CompletionResponse::Array(vec![
15050                task_completion_item,
15051            ])))
15052        }
15053    });
15054
15055    request.next().await;
15056
15057    cx.condition(|editor, _| editor.context_menu_visible())
15058        .await;
15059    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15060        editor
15061            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15062            .unwrap()
15063    });
15064    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15065
15066    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15067        let task_completion_item = completion_item.clone();
15068        async move { Ok(task_completion_item) }
15069    })
15070    .next()
15071    .await
15072    .unwrap();
15073    apply_additional_edits.await.unwrap();
15074    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15075}
15076
15077#[gpui::test]
15078async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15079    init_test(cx, |_| {});
15080
15081    let mut cx = EditorLspTestContext::new_rust(
15082        lsp::ServerCapabilities {
15083            completion_provider: Some(lsp::CompletionOptions {
15084                trigger_characters: Some(vec![".".to_string()]),
15085                resolve_provider: Some(true),
15086                ..Default::default()
15087            }),
15088            ..Default::default()
15089        },
15090        cx,
15091    )
15092    .await;
15093
15094    cx.set_state("fn main() { let a = 2ˇ; }");
15095    cx.simulate_keystroke(".");
15096
15097    let item1 = lsp::CompletionItem {
15098        label: "method id()".to_string(),
15099        filter_text: Some("id".to_string()),
15100        detail: None,
15101        documentation: None,
15102        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15103            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15104            new_text: ".id".to_string(),
15105        })),
15106        ..lsp::CompletionItem::default()
15107    };
15108
15109    let item2 = lsp::CompletionItem {
15110        label: "other".to_string(),
15111        filter_text: Some("other".to_string()),
15112        detail: None,
15113        documentation: None,
15114        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15115            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15116            new_text: ".other".to_string(),
15117        })),
15118        ..lsp::CompletionItem::default()
15119    };
15120
15121    let item1 = item1.clone();
15122    cx.set_request_handler::<lsp::request::Completion, _, _>({
15123        let item1 = item1.clone();
15124        move |_, _, _| {
15125            let item1 = item1.clone();
15126            let item2 = item2.clone();
15127            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15128        }
15129    })
15130    .next()
15131    .await;
15132
15133    cx.condition(|editor, _| editor.context_menu_visible())
15134        .await;
15135    cx.update_editor(|editor, _, _| {
15136        let context_menu = editor.context_menu.borrow_mut();
15137        let context_menu = context_menu
15138            .as_ref()
15139            .expect("Should have the context menu deployed");
15140        match context_menu {
15141            CodeContextMenu::Completions(completions_menu) => {
15142                let completions = completions_menu.completions.borrow_mut();
15143                assert_eq!(
15144                    completions
15145                        .iter()
15146                        .map(|completion| &completion.label.text)
15147                        .collect::<Vec<_>>(),
15148                    vec!["method id()", "other"]
15149                )
15150            }
15151            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15152        }
15153    });
15154
15155    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15156        let item1 = item1.clone();
15157        move |_, item_to_resolve, _| {
15158            let item1 = item1.clone();
15159            async move {
15160                if item1 == item_to_resolve {
15161                    Ok(lsp::CompletionItem {
15162                        label: "method id()".to_string(),
15163                        filter_text: Some("id".to_string()),
15164                        detail: Some("Now resolved!".to_string()),
15165                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15166                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15167                            range: lsp::Range::new(
15168                                lsp::Position::new(0, 22),
15169                                lsp::Position::new(0, 22),
15170                            ),
15171                            new_text: ".id".to_string(),
15172                        })),
15173                        ..lsp::CompletionItem::default()
15174                    })
15175                } else {
15176                    Ok(item_to_resolve)
15177                }
15178            }
15179        }
15180    })
15181    .next()
15182    .await
15183    .unwrap();
15184    cx.run_until_parked();
15185
15186    cx.update_editor(|editor, window, cx| {
15187        editor.context_menu_next(&Default::default(), window, cx);
15188    });
15189
15190    cx.update_editor(|editor, _, _| {
15191        let context_menu = editor.context_menu.borrow_mut();
15192        let context_menu = context_menu
15193            .as_ref()
15194            .expect("Should have the context menu deployed");
15195        match context_menu {
15196            CodeContextMenu::Completions(completions_menu) => {
15197                let completions = completions_menu.completions.borrow_mut();
15198                assert_eq!(
15199                    completions
15200                        .iter()
15201                        .map(|completion| &completion.label.text)
15202                        .collect::<Vec<_>>(),
15203                    vec!["method id() Now resolved!", "other"],
15204                    "Should update first completion label, but not second as the filter text did not match."
15205                );
15206            }
15207            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15208        }
15209    });
15210}
15211
15212#[gpui::test]
15213async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15214    init_test(cx, |_| {});
15215    let mut cx = EditorLspTestContext::new_rust(
15216        lsp::ServerCapabilities {
15217            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15218            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15219            completion_provider: Some(lsp::CompletionOptions {
15220                resolve_provider: Some(true),
15221                ..Default::default()
15222            }),
15223            ..Default::default()
15224        },
15225        cx,
15226    )
15227    .await;
15228    cx.set_state(indoc! {"
15229        struct TestStruct {
15230            field: i32
15231        }
15232
15233        fn mainˇ() {
15234            let unused_var = 42;
15235            let test_struct = TestStruct { field: 42 };
15236        }
15237    "});
15238    let symbol_range = cx.lsp_range(indoc! {"
15239        struct TestStruct {
15240            field: i32
15241        }
15242
15243        «fn main»() {
15244            let unused_var = 42;
15245            let test_struct = TestStruct { field: 42 };
15246        }
15247    "});
15248    let mut hover_requests =
15249        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15250            Ok(Some(lsp::Hover {
15251                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15252                    kind: lsp::MarkupKind::Markdown,
15253                    value: "Function documentation".to_string(),
15254                }),
15255                range: Some(symbol_range),
15256            }))
15257        });
15258
15259    // Case 1: Test that code action menu hide hover popover
15260    cx.dispatch_action(Hover);
15261    hover_requests.next().await;
15262    cx.condition(|editor, _| editor.hover_state.visible()).await;
15263    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15264        move |_, _, _| async move {
15265            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15266                lsp::CodeAction {
15267                    title: "Remove unused variable".to_string(),
15268                    kind: Some(CodeActionKind::QUICKFIX),
15269                    edit: Some(lsp::WorkspaceEdit {
15270                        changes: Some(
15271                            [(
15272                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15273                                vec![lsp::TextEdit {
15274                                    range: lsp::Range::new(
15275                                        lsp::Position::new(5, 4),
15276                                        lsp::Position::new(5, 27),
15277                                    ),
15278                                    new_text: "".to_string(),
15279                                }],
15280                            )]
15281                            .into_iter()
15282                            .collect(),
15283                        ),
15284                        ..Default::default()
15285                    }),
15286                    ..Default::default()
15287                },
15288            )]))
15289        },
15290    );
15291    cx.update_editor(|editor, window, cx| {
15292        editor.toggle_code_actions(
15293            &ToggleCodeActions {
15294                deployed_from: None,
15295                quick_launch: false,
15296            },
15297            window,
15298            cx,
15299        );
15300    });
15301    code_action_requests.next().await;
15302    cx.run_until_parked();
15303    cx.condition(|editor, _| editor.context_menu_visible())
15304        .await;
15305    cx.update_editor(|editor, _, _| {
15306        assert!(
15307            !editor.hover_state.visible(),
15308            "Hover popover should be hidden when code action menu is shown"
15309        );
15310        // Hide code actions
15311        editor.context_menu.take();
15312    });
15313
15314    // Case 2: Test that code completions hide hover popover
15315    cx.dispatch_action(Hover);
15316    hover_requests.next().await;
15317    cx.condition(|editor, _| editor.hover_state.visible()).await;
15318    let counter = Arc::new(AtomicUsize::new(0));
15319    let mut completion_requests =
15320        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15321            let counter = counter.clone();
15322            async move {
15323                counter.fetch_add(1, atomic::Ordering::Release);
15324                Ok(Some(lsp::CompletionResponse::Array(vec![
15325                    lsp::CompletionItem {
15326                        label: "main".into(),
15327                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15328                        detail: Some("() -> ()".to_string()),
15329                        ..Default::default()
15330                    },
15331                    lsp::CompletionItem {
15332                        label: "TestStruct".into(),
15333                        kind: Some(lsp::CompletionItemKind::STRUCT),
15334                        detail: Some("struct TestStruct".to_string()),
15335                        ..Default::default()
15336                    },
15337                ])))
15338            }
15339        });
15340    cx.update_editor(|editor, window, cx| {
15341        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15342    });
15343    completion_requests.next().await;
15344    cx.condition(|editor, _| editor.context_menu_visible())
15345        .await;
15346    cx.update_editor(|editor, _, _| {
15347        assert!(
15348            !editor.hover_state.visible(),
15349            "Hover popover should be hidden when completion menu is shown"
15350        );
15351    });
15352}
15353
15354#[gpui::test]
15355async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15356    init_test(cx, |_| {});
15357
15358    let mut cx = EditorLspTestContext::new_rust(
15359        lsp::ServerCapabilities {
15360            completion_provider: Some(lsp::CompletionOptions {
15361                trigger_characters: Some(vec![".".to_string()]),
15362                resolve_provider: Some(true),
15363                ..Default::default()
15364            }),
15365            ..Default::default()
15366        },
15367        cx,
15368    )
15369    .await;
15370
15371    cx.set_state("fn main() { let a = 2ˇ; }");
15372    cx.simulate_keystroke(".");
15373
15374    let unresolved_item_1 = lsp::CompletionItem {
15375        label: "id".to_string(),
15376        filter_text: Some("id".to_string()),
15377        detail: None,
15378        documentation: None,
15379        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15380            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15381            new_text: ".id".to_string(),
15382        })),
15383        ..lsp::CompletionItem::default()
15384    };
15385    let resolved_item_1 = lsp::CompletionItem {
15386        additional_text_edits: Some(vec![lsp::TextEdit {
15387            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15388            new_text: "!!".to_string(),
15389        }]),
15390        ..unresolved_item_1.clone()
15391    };
15392    let unresolved_item_2 = lsp::CompletionItem {
15393        label: "other".to_string(),
15394        filter_text: Some("other".to_string()),
15395        detail: None,
15396        documentation: None,
15397        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15398            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15399            new_text: ".other".to_string(),
15400        })),
15401        ..lsp::CompletionItem::default()
15402    };
15403    let resolved_item_2 = lsp::CompletionItem {
15404        additional_text_edits: Some(vec![lsp::TextEdit {
15405            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15406            new_text: "??".to_string(),
15407        }]),
15408        ..unresolved_item_2.clone()
15409    };
15410
15411    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15412    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15413    cx.lsp
15414        .server
15415        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15416            let unresolved_item_1 = unresolved_item_1.clone();
15417            let resolved_item_1 = resolved_item_1.clone();
15418            let unresolved_item_2 = unresolved_item_2.clone();
15419            let resolved_item_2 = resolved_item_2.clone();
15420            let resolve_requests_1 = resolve_requests_1.clone();
15421            let resolve_requests_2 = resolve_requests_2.clone();
15422            move |unresolved_request, _| {
15423                let unresolved_item_1 = unresolved_item_1.clone();
15424                let resolved_item_1 = resolved_item_1.clone();
15425                let unresolved_item_2 = unresolved_item_2.clone();
15426                let resolved_item_2 = resolved_item_2.clone();
15427                let resolve_requests_1 = resolve_requests_1.clone();
15428                let resolve_requests_2 = resolve_requests_2.clone();
15429                async move {
15430                    if unresolved_request == unresolved_item_1 {
15431                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15432                        Ok(resolved_item_1.clone())
15433                    } else if unresolved_request == unresolved_item_2 {
15434                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15435                        Ok(resolved_item_2.clone())
15436                    } else {
15437                        panic!("Unexpected completion item {unresolved_request:?}")
15438                    }
15439                }
15440            }
15441        })
15442        .detach();
15443
15444    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15445        let unresolved_item_1 = unresolved_item_1.clone();
15446        let unresolved_item_2 = unresolved_item_2.clone();
15447        async move {
15448            Ok(Some(lsp::CompletionResponse::Array(vec![
15449                unresolved_item_1,
15450                unresolved_item_2,
15451            ])))
15452        }
15453    })
15454    .next()
15455    .await;
15456
15457    cx.condition(|editor, _| editor.context_menu_visible())
15458        .await;
15459    cx.update_editor(|editor, _, _| {
15460        let context_menu = editor.context_menu.borrow_mut();
15461        let context_menu = context_menu
15462            .as_ref()
15463            .expect("Should have the context menu deployed");
15464        match context_menu {
15465            CodeContextMenu::Completions(completions_menu) => {
15466                let completions = completions_menu.completions.borrow_mut();
15467                assert_eq!(
15468                    completions
15469                        .iter()
15470                        .map(|completion| &completion.label.text)
15471                        .collect::<Vec<_>>(),
15472                    vec!["id", "other"]
15473                )
15474            }
15475            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15476        }
15477    });
15478    cx.run_until_parked();
15479
15480    cx.update_editor(|editor, window, cx| {
15481        editor.context_menu_next(&ContextMenuNext, window, cx);
15482    });
15483    cx.run_until_parked();
15484    cx.update_editor(|editor, window, cx| {
15485        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15486    });
15487    cx.run_until_parked();
15488    cx.update_editor(|editor, window, cx| {
15489        editor.context_menu_next(&ContextMenuNext, window, cx);
15490    });
15491    cx.run_until_parked();
15492    cx.update_editor(|editor, window, cx| {
15493        editor
15494            .compose_completion(&ComposeCompletion::default(), window, cx)
15495            .expect("No task returned")
15496    })
15497    .await
15498    .expect("Completion failed");
15499    cx.run_until_parked();
15500
15501    cx.update_editor(|editor, _, cx| {
15502        assert_eq!(
15503            resolve_requests_1.load(atomic::Ordering::Acquire),
15504            1,
15505            "Should always resolve once despite multiple selections"
15506        );
15507        assert_eq!(
15508            resolve_requests_2.load(atomic::Ordering::Acquire),
15509            1,
15510            "Should always resolve once after multiple selections and applying the completion"
15511        );
15512        assert_eq!(
15513            editor.text(cx),
15514            "fn main() { let a = ??.other; }",
15515            "Should use resolved data when applying the completion"
15516        );
15517    });
15518}
15519
15520#[gpui::test]
15521async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15522    init_test(cx, |_| {});
15523
15524    let item_0 = lsp::CompletionItem {
15525        label: "abs".into(),
15526        insert_text: Some("abs".into()),
15527        data: Some(json!({ "very": "special"})),
15528        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15529        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15530            lsp::InsertReplaceEdit {
15531                new_text: "abs".to_string(),
15532                insert: lsp::Range::default(),
15533                replace: lsp::Range::default(),
15534            },
15535        )),
15536        ..lsp::CompletionItem::default()
15537    };
15538    let items = iter::once(item_0.clone())
15539        .chain((11..51).map(|i| lsp::CompletionItem {
15540            label: format!("item_{}", i),
15541            insert_text: Some(format!("item_{}", i)),
15542            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15543            ..lsp::CompletionItem::default()
15544        }))
15545        .collect::<Vec<_>>();
15546
15547    let default_commit_characters = vec!["?".to_string()];
15548    let default_data = json!({ "default": "data"});
15549    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15550    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15551    let default_edit_range = lsp::Range {
15552        start: lsp::Position {
15553            line: 0,
15554            character: 5,
15555        },
15556        end: lsp::Position {
15557            line: 0,
15558            character: 5,
15559        },
15560    };
15561
15562    let mut cx = EditorLspTestContext::new_rust(
15563        lsp::ServerCapabilities {
15564            completion_provider: Some(lsp::CompletionOptions {
15565                trigger_characters: Some(vec![".".to_string()]),
15566                resolve_provider: Some(true),
15567                ..Default::default()
15568            }),
15569            ..Default::default()
15570        },
15571        cx,
15572    )
15573    .await;
15574
15575    cx.set_state("fn main() { let a = 2ˇ; }");
15576    cx.simulate_keystroke(".");
15577
15578    let completion_data = default_data.clone();
15579    let completion_characters = default_commit_characters.clone();
15580    let completion_items = items.clone();
15581    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15582        let default_data = completion_data.clone();
15583        let default_commit_characters = completion_characters.clone();
15584        let items = completion_items.clone();
15585        async move {
15586            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15587                items,
15588                item_defaults: Some(lsp::CompletionListItemDefaults {
15589                    data: Some(default_data.clone()),
15590                    commit_characters: Some(default_commit_characters.clone()),
15591                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15592                        default_edit_range,
15593                    )),
15594                    insert_text_format: Some(default_insert_text_format),
15595                    insert_text_mode: Some(default_insert_text_mode),
15596                }),
15597                ..lsp::CompletionList::default()
15598            })))
15599        }
15600    })
15601    .next()
15602    .await;
15603
15604    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15605    cx.lsp
15606        .server
15607        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15608            let closure_resolved_items = resolved_items.clone();
15609            move |item_to_resolve, _| {
15610                let closure_resolved_items = closure_resolved_items.clone();
15611                async move {
15612                    closure_resolved_items.lock().push(item_to_resolve.clone());
15613                    Ok(item_to_resolve)
15614                }
15615            }
15616        })
15617        .detach();
15618
15619    cx.condition(|editor, _| editor.context_menu_visible())
15620        .await;
15621    cx.run_until_parked();
15622    cx.update_editor(|editor, _, _| {
15623        let menu = editor.context_menu.borrow_mut();
15624        match menu.as_ref().expect("should have the completions menu") {
15625            CodeContextMenu::Completions(completions_menu) => {
15626                assert_eq!(
15627                    completions_menu
15628                        .entries
15629                        .borrow()
15630                        .iter()
15631                        .map(|mat| mat.string.clone())
15632                        .collect::<Vec<String>>(),
15633                    items
15634                        .iter()
15635                        .map(|completion| completion.label.clone())
15636                        .collect::<Vec<String>>()
15637                );
15638            }
15639            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15640        }
15641    });
15642    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15643    // with 4 from the end.
15644    assert_eq!(
15645        *resolved_items.lock(),
15646        [&items[0..16], &items[items.len() - 4..items.len()]]
15647            .concat()
15648            .iter()
15649            .cloned()
15650            .map(|mut item| {
15651                if item.data.is_none() {
15652                    item.data = Some(default_data.clone());
15653                }
15654                item
15655            })
15656            .collect::<Vec<lsp::CompletionItem>>(),
15657        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15658    );
15659    resolved_items.lock().clear();
15660
15661    cx.update_editor(|editor, window, cx| {
15662        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15663    });
15664    cx.run_until_parked();
15665    // Completions that have already been resolved are skipped.
15666    assert_eq!(
15667        *resolved_items.lock(),
15668        items[items.len() - 17..items.len() - 4]
15669            .iter()
15670            .cloned()
15671            .map(|mut item| {
15672                if item.data.is_none() {
15673                    item.data = Some(default_data.clone());
15674                }
15675                item
15676            })
15677            .collect::<Vec<lsp::CompletionItem>>()
15678    );
15679    resolved_items.lock().clear();
15680}
15681
15682#[gpui::test]
15683async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15684    init_test(cx, |_| {});
15685
15686    let mut cx = EditorLspTestContext::new(
15687        Language::new(
15688            LanguageConfig {
15689                matcher: LanguageMatcher {
15690                    path_suffixes: vec!["jsx".into()],
15691                    ..Default::default()
15692                },
15693                overrides: [(
15694                    "element".into(),
15695                    LanguageConfigOverride {
15696                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15697                        ..Default::default()
15698                    },
15699                )]
15700                .into_iter()
15701                .collect(),
15702                ..Default::default()
15703            },
15704            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15705        )
15706        .with_override_query("(jsx_self_closing_element) @element")
15707        .unwrap(),
15708        lsp::ServerCapabilities {
15709            completion_provider: Some(lsp::CompletionOptions {
15710                trigger_characters: Some(vec![":".to_string()]),
15711                ..Default::default()
15712            }),
15713            ..Default::default()
15714        },
15715        cx,
15716    )
15717    .await;
15718
15719    cx.lsp
15720        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15721            Ok(Some(lsp::CompletionResponse::Array(vec![
15722                lsp::CompletionItem {
15723                    label: "bg-blue".into(),
15724                    ..Default::default()
15725                },
15726                lsp::CompletionItem {
15727                    label: "bg-red".into(),
15728                    ..Default::default()
15729                },
15730                lsp::CompletionItem {
15731                    label: "bg-yellow".into(),
15732                    ..Default::default()
15733                },
15734            ])))
15735        });
15736
15737    cx.set_state(r#"<p class="bgˇ" />"#);
15738
15739    // Trigger completion when typing a dash, because the dash is an extra
15740    // word character in the 'element' scope, which contains the cursor.
15741    cx.simulate_keystroke("-");
15742    cx.executor().run_until_parked();
15743    cx.update_editor(|editor, _, _| {
15744        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15745        {
15746            assert_eq!(
15747                completion_menu_entries(&menu),
15748                &["bg-blue", "bg-red", "bg-yellow"]
15749            );
15750        } else {
15751            panic!("expected completion menu to be open");
15752        }
15753    });
15754
15755    cx.simulate_keystroke("l");
15756    cx.executor().run_until_parked();
15757    cx.update_editor(|editor, _, _| {
15758        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15759        {
15760            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15761        } else {
15762            panic!("expected completion menu to be open");
15763        }
15764    });
15765
15766    // When filtering completions, consider the character after the '-' to
15767    // be the start of a subword.
15768    cx.set_state(r#"<p class="yelˇ" />"#);
15769    cx.simulate_keystroke("l");
15770    cx.executor().run_until_parked();
15771    cx.update_editor(|editor, _, _| {
15772        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15773        {
15774            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15775        } else {
15776            panic!("expected completion menu to be open");
15777        }
15778    });
15779}
15780
15781fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15782    let entries = menu.entries.borrow();
15783    entries.iter().map(|mat| mat.string.clone()).collect()
15784}
15785
15786#[gpui::test]
15787async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15788    init_test(cx, |settings| {
15789        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15790            FormatterList(vec![Formatter::Prettier].into()),
15791        ))
15792    });
15793
15794    let fs = FakeFs::new(cx.executor());
15795    fs.insert_file(path!("/file.ts"), Default::default()).await;
15796
15797    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15798    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15799
15800    language_registry.add(Arc::new(Language::new(
15801        LanguageConfig {
15802            name: "TypeScript".into(),
15803            matcher: LanguageMatcher {
15804                path_suffixes: vec!["ts".to_string()],
15805                ..Default::default()
15806            },
15807            ..Default::default()
15808        },
15809        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15810    )));
15811    update_test_language_settings(cx, |settings| {
15812        settings.defaults.prettier = Some(PrettierSettings {
15813            allowed: true,
15814            ..PrettierSettings::default()
15815        });
15816    });
15817
15818    let test_plugin = "test_plugin";
15819    let _ = language_registry.register_fake_lsp(
15820        "TypeScript",
15821        FakeLspAdapter {
15822            prettier_plugins: vec![test_plugin],
15823            ..Default::default()
15824        },
15825    );
15826
15827    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15828    let buffer = project
15829        .update(cx, |project, cx| {
15830            project.open_local_buffer(path!("/file.ts"), cx)
15831        })
15832        .await
15833        .unwrap();
15834
15835    let buffer_text = "one\ntwo\nthree\n";
15836    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15837    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15838    editor.update_in(cx, |editor, window, cx| {
15839        editor.set_text(buffer_text, window, cx)
15840    });
15841
15842    editor
15843        .update_in(cx, |editor, window, cx| {
15844            editor.perform_format(
15845                project.clone(),
15846                FormatTrigger::Manual,
15847                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15848                window,
15849                cx,
15850            )
15851        })
15852        .unwrap()
15853        .await;
15854    assert_eq!(
15855        editor.update(cx, |editor, cx| editor.text(cx)),
15856        buffer_text.to_string() + prettier_format_suffix,
15857        "Test prettier formatting was not applied to the original buffer text",
15858    );
15859
15860    update_test_language_settings(cx, |settings| {
15861        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15862    });
15863    let format = editor.update_in(cx, |editor, window, cx| {
15864        editor.perform_format(
15865            project.clone(),
15866            FormatTrigger::Manual,
15867            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15868            window,
15869            cx,
15870        )
15871    });
15872    format.await.unwrap();
15873    assert_eq!(
15874        editor.update(cx, |editor, cx| editor.text(cx)),
15875        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15876        "Autoformatting (via test prettier) was not applied to the original buffer text",
15877    );
15878}
15879
15880#[gpui::test]
15881async fn test_addition_reverts(cx: &mut TestAppContext) {
15882    init_test(cx, |_| {});
15883    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15884    let base_text = indoc! {r#"
15885        struct Row;
15886        struct Row1;
15887        struct Row2;
15888
15889        struct Row4;
15890        struct Row5;
15891        struct Row6;
15892
15893        struct Row8;
15894        struct Row9;
15895        struct Row10;"#};
15896
15897    // When addition hunks are not adjacent to carets, no hunk revert is performed
15898    assert_hunk_revert(
15899        indoc! {r#"struct Row;
15900                   struct Row1;
15901                   struct Row1.1;
15902                   struct Row1.2;
15903                   struct Row2;ˇ
15904
15905                   struct Row4;
15906                   struct Row5;
15907                   struct Row6;
15908
15909                   struct Row8;
15910                   ˇstruct Row9;
15911                   struct Row9.1;
15912                   struct Row9.2;
15913                   struct Row9.3;
15914                   struct Row10;"#},
15915        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15916        indoc! {r#"struct Row;
15917                   struct Row1;
15918                   struct Row1.1;
15919                   struct Row1.2;
15920                   struct Row2;ˇ
15921
15922                   struct Row4;
15923                   struct Row5;
15924                   struct Row6;
15925
15926                   struct Row8;
15927                   ˇstruct Row9;
15928                   struct Row9.1;
15929                   struct Row9.2;
15930                   struct Row9.3;
15931                   struct Row10;"#},
15932        base_text,
15933        &mut cx,
15934    );
15935    // Same for selections
15936    assert_hunk_revert(
15937        indoc! {r#"struct Row;
15938                   struct Row1;
15939                   struct Row2;
15940                   struct Row2.1;
15941                   struct Row2.2;
15942                   «ˇ
15943                   struct Row4;
15944                   struct» Row5;
15945                   «struct Row6;
15946                   ˇ»
15947                   struct Row9.1;
15948                   struct Row9.2;
15949                   struct Row9.3;
15950                   struct Row8;
15951                   struct Row9;
15952                   struct Row10;"#},
15953        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15954        indoc! {r#"struct Row;
15955                   struct Row1;
15956                   struct Row2;
15957                   struct Row2.1;
15958                   struct Row2.2;
15959                   «ˇ
15960                   struct Row4;
15961                   struct» Row5;
15962                   «struct Row6;
15963                   ˇ»
15964                   struct Row9.1;
15965                   struct Row9.2;
15966                   struct Row9.3;
15967                   struct Row8;
15968                   struct Row9;
15969                   struct Row10;"#},
15970        base_text,
15971        &mut cx,
15972    );
15973
15974    // When carets and selections intersect the addition hunks, those are reverted.
15975    // Adjacent carets got merged.
15976    assert_hunk_revert(
15977        indoc! {r#"struct Row;
15978                   ˇ// something on the top
15979                   struct Row1;
15980                   struct Row2;
15981                   struct Roˇw3.1;
15982                   struct Row2.2;
15983                   struct Row2.3;ˇ
15984
15985                   struct Row4;
15986                   struct ˇRow5.1;
15987                   struct Row5.2;
15988                   struct «Rowˇ»5.3;
15989                   struct Row5;
15990                   struct Row6;
15991                   ˇ
15992                   struct Row9.1;
15993                   struct «Rowˇ»9.2;
15994                   struct «ˇRow»9.3;
15995                   struct Row8;
15996                   struct Row9;
15997                   «ˇ// something on bottom»
15998                   struct Row10;"#},
15999        vec![
16000            DiffHunkStatusKind::Added,
16001            DiffHunkStatusKind::Added,
16002            DiffHunkStatusKind::Added,
16003            DiffHunkStatusKind::Added,
16004            DiffHunkStatusKind::Added,
16005        ],
16006        indoc! {r#"struct Row;
16007                   ˇstruct Row1;
16008                   struct Row2;
16009                   ˇ
16010                   struct Row4;
16011                   ˇstruct Row5;
16012                   struct Row6;
16013                   ˇ
16014                   ˇstruct Row8;
16015                   struct Row9;
16016                   ˇstruct Row10;"#},
16017        base_text,
16018        &mut cx,
16019    );
16020}
16021
16022#[gpui::test]
16023async fn test_modification_reverts(cx: &mut TestAppContext) {
16024    init_test(cx, |_| {});
16025    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16026    let base_text = indoc! {r#"
16027        struct Row;
16028        struct Row1;
16029        struct Row2;
16030
16031        struct Row4;
16032        struct Row5;
16033        struct Row6;
16034
16035        struct Row8;
16036        struct Row9;
16037        struct Row10;"#};
16038
16039    // Modification hunks behave the same as the addition ones.
16040    assert_hunk_revert(
16041        indoc! {r#"struct Row;
16042                   struct Row1;
16043                   struct Row33;
16044                   ˇ
16045                   struct Row4;
16046                   struct Row5;
16047                   struct Row6;
16048                   ˇ
16049                   struct Row99;
16050                   struct Row9;
16051                   struct Row10;"#},
16052        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16053        indoc! {r#"struct Row;
16054                   struct Row1;
16055                   struct Row33;
16056                   ˇ
16057                   struct Row4;
16058                   struct Row5;
16059                   struct Row6;
16060                   ˇ
16061                   struct Row99;
16062                   struct Row9;
16063                   struct Row10;"#},
16064        base_text,
16065        &mut cx,
16066    );
16067    assert_hunk_revert(
16068        indoc! {r#"struct Row;
16069                   struct Row1;
16070                   struct Row33;
16071                   «ˇ
16072                   struct Row4;
16073                   struct» Row5;
16074                   «struct Row6;
16075                   ˇ»
16076                   struct Row99;
16077                   struct Row9;
16078                   struct Row10;"#},
16079        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16080        indoc! {r#"struct Row;
16081                   struct Row1;
16082                   struct Row33;
16083                   «ˇ
16084                   struct Row4;
16085                   struct» Row5;
16086                   «struct Row6;
16087                   ˇ»
16088                   struct Row99;
16089                   struct Row9;
16090                   struct Row10;"#},
16091        base_text,
16092        &mut cx,
16093    );
16094
16095    assert_hunk_revert(
16096        indoc! {r#"ˇstruct Row1.1;
16097                   struct Row1;
16098                   «ˇstr»uct Row22;
16099
16100                   struct ˇRow44;
16101                   struct Row5;
16102                   struct «Rˇ»ow66;ˇ
16103
16104                   «struˇ»ct Row88;
16105                   struct Row9;
16106                   struct Row1011;ˇ"#},
16107        vec![
16108            DiffHunkStatusKind::Modified,
16109            DiffHunkStatusKind::Modified,
16110            DiffHunkStatusKind::Modified,
16111            DiffHunkStatusKind::Modified,
16112            DiffHunkStatusKind::Modified,
16113            DiffHunkStatusKind::Modified,
16114        ],
16115        indoc! {r#"struct Row;
16116                   ˇstruct Row1;
16117                   struct Row2;
16118                   ˇ
16119                   struct Row4;
16120                   ˇstruct Row5;
16121                   struct Row6;
16122                   ˇ
16123                   struct Row8;
16124                   ˇstruct Row9;
16125                   struct Row10;ˇ"#},
16126        base_text,
16127        &mut cx,
16128    );
16129}
16130
16131#[gpui::test]
16132async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16133    init_test(cx, |_| {});
16134    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16135    let base_text = indoc! {r#"
16136        one
16137
16138        two
16139        three
16140        "#};
16141
16142    cx.set_head_text(base_text);
16143    cx.set_state("\nˇ\n");
16144    cx.executor().run_until_parked();
16145    cx.update_editor(|editor, _window, cx| {
16146        editor.expand_selected_diff_hunks(cx);
16147    });
16148    cx.executor().run_until_parked();
16149    cx.update_editor(|editor, window, cx| {
16150        editor.backspace(&Default::default(), window, cx);
16151    });
16152    cx.run_until_parked();
16153    cx.assert_state_with_diff(
16154        indoc! {r#"
16155
16156        - two
16157        - threeˇ
16158        +
16159        "#}
16160        .to_string(),
16161    );
16162}
16163
16164#[gpui::test]
16165async fn test_deletion_reverts(cx: &mut TestAppContext) {
16166    init_test(cx, |_| {});
16167    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16168    let base_text = indoc! {r#"struct Row;
16169struct Row1;
16170struct Row2;
16171
16172struct Row4;
16173struct Row5;
16174struct Row6;
16175
16176struct Row8;
16177struct Row9;
16178struct Row10;"#};
16179
16180    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16181    assert_hunk_revert(
16182        indoc! {r#"struct Row;
16183                   struct Row2;
16184
16185                   ˇstruct Row4;
16186                   struct Row5;
16187                   struct Row6;
16188                   ˇ
16189                   struct Row8;
16190                   struct Row10;"#},
16191        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16192        indoc! {r#"struct Row;
16193                   struct Row2;
16194
16195                   ˇstruct Row4;
16196                   struct Row5;
16197                   struct Row6;
16198                   ˇ
16199                   struct Row8;
16200                   struct Row10;"#},
16201        base_text,
16202        &mut cx,
16203    );
16204    assert_hunk_revert(
16205        indoc! {r#"struct Row;
16206                   struct Row2;
16207
16208                   «ˇstruct Row4;
16209                   struct» Row5;
16210                   «struct Row6;
16211                   ˇ»
16212                   struct Row8;
16213                   struct Row10;"#},
16214        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16215        indoc! {r#"struct Row;
16216                   struct Row2;
16217
16218                   «ˇstruct Row4;
16219                   struct» Row5;
16220                   «struct Row6;
16221                   ˇ»
16222                   struct Row8;
16223                   struct Row10;"#},
16224        base_text,
16225        &mut cx,
16226    );
16227
16228    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16229    assert_hunk_revert(
16230        indoc! {r#"struct Row;
16231                   ˇstruct Row2;
16232
16233                   struct Row4;
16234                   struct Row5;
16235                   struct Row6;
16236
16237                   struct Row8;ˇ
16238                   struct Row10;"#},
16239        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16240        indoc! {r#"struct Row;
16241                   struct Row1;
16242                   ˇstruct Row2;
16243
16244                   struct Row4;
16245                   struct Row5;
16246                   struct Row6;
16247
16248                   struct Row8;ˇ
16249                   struct Row9;
16250                   struct Row10;"#},
16251        base_text,
16252        &mut cx,
16253    );
16254    assert_hunk_revert(
16255        indoc! {r#"struct Row;
16256                   struct Row2«ˇ;
16257                   struct Row4;
16258                   struct» Row5;
16259                   «struct Row6;
16260
16261                   struct Row8;ˇ»
16262                   struct Row10;"#},
16263        vec![
16264            DiffHunkStatusKind::Deleted,
16265            DiffHunkStatusKind::Deleted,
16266            DiffHunkStatusKind::Deleted,
16267        ],
16268        indoc! {r#"struct Row;
16269                   struct Row1;
16270                   struct Row2«ˇ;
16271
16272                   struct Row4;
16273                   struct» Row5;
16274                   «struct Row6;
16275
16276                   struct Row8;ˇ»
16277                   struct Row9;
16278                   struct Row10;"#},
16279        base_text,
16280        &mut cx,
16281    );
16282}
16283
16284#[gpui::test]
16285async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16286    init_test(cx, |_| {});
16287
16288    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16289    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16290    let base_text_3 =
16291        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16292
16293    let text_1 = edit_first_char_of_every_line(base_text_1);
16294    let text_2 = edit_first_char_of_every_line(base_text_2);
16295    let text_3 = edit_first_char_of_every_line(base_text_3);
16296
16297    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16298    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16299    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16300
16301    let multibuffer = cx.new(|cx| {
16302        let mut multibuffer = MultiBuffer::new(ReadWrite);
16303        multibuffer.push_excerpts(
16304            buffer_1.clone(),
16305            [
16306                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16307                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16308                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16309            ],
16310            cx,
16311        );
16312        multibuffer.push_excerpts(
16313            buffer_2.clone(),
16314            [
16315                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16316                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16317                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16318            ],
16319            cx,
16320        );
16321        multibuffer.push_excerpts(
16322            buffer_3.clone(),
16323            [
16324                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16325                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16326                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16327            ],
16328            cx,
16329        );
16330        multibuffer
16331    });
16332
16333    let fs = FakeFs::new(cx.executor());
16334    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16335    let (editor, cx) = cx
16336        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16337    editor.update_in(cx, |editor, _window, cx| {
16338        for (buffer, diff_base) in [
16339            (buffer_1.clone(), base_text_1),
16340            (buffer_2.clone(), base_text_2),
16341            (buffer_3.clone(), base_text_3),
16342        ] {
16343            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16344            editor
16345                .buffer
16346                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16347        }
16348    });
16349    cx.executor().run_until_parked();
16350
16351    editor.update_in(cx, |editor, window, cx| {
16352        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}");
16353        editor.select_all(&SelectAll, window, cx);
16354        editor.git_restore(&Default::default(), window, cx);
16355    });
16356    cx.executor().run_until_parked();
16357
16358    // When all ranges are selected, all buffer hunks are reverted.
16359    editor.update(cx, |editor, cx| {
16360        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");
16361    });
16362    buffer_1.update(cx, |buffer, _| {
16363        assert_eq!(buffer.text(), base_text_1);
16364    });
16365    buffer_2.update(cx, |buffer, _| {
16366        assert_eq!(buffer.text(), base_text_2);
16367    });
16368    buffer_3.update(cx, |buffer, _| {
16369        assert_eq!(buffer.text(), base_text_3);
16370    });
16371
16372    editor.update_in(cx, |editor, window, cx| {
16373        editor.undo(&Default::default(), window, cx);
16374    });
16375
16376    editor.update_in(cx, |editor, window, cx| {
16377        editor.change_selections(None, window, cx, |s| {
16378            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16379        });
16380        editor.git_restore(&Default::default(), window, cx);
16381    });
16382
16383    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16384    // but not affect buffer_2 and its related excerpts.
16385    editor.update(cx, |editor, cx| {
16386        assert_eq!(
16387            editor.text(cx),
16388            "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}"
16389        );
16390    });
16391    buffer_1.update(cx, |buffer, _| {
16392        assert_eq!(buffer.text(), base_text_1);
16393    });
16394    buffer_2.update(cx, |buffer, _| {
16395        assert_eq!(
16396            buffer.text(),
16397            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16398        );
16399    });
16400    buffer_3.update(cx, |buffer, _| {
16401        assert_eq!(
16402            buffer.text(),
16403            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16404        );
16405    });
16406
16407    fn edit_first_char_of_every_line(text: &str) -> String {
16408        text.split('\n')
16409            .map(|line| format!("X{}", &line[1..]))
16410            .collect::<Vec<_>>()
16411            .join("\n")
16412    }
16413}
16414
16415#[gpui::test]
16416async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16417    init_test(cx, |_| {});
16418
16419    let cols = 4;
16420    let rows = 10;
16421    let sample_text_1 = sample_text(rows, cols, 'a');
16422    assert_eq!(
16423        sample_text_1,
16424        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16425    );
16426    let sample_text_2 = sample_text(rows, cols, 'l');
16427    assert_eq!(
16428        sample_text_2,
16429        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16430    );
16431    let sample_text_3 = sample_text(rows, cols, 'v');
16432    assert_eq!(
16433        sample_text_3,
16434        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16435    );
16436
16437    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16438    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16439    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16440
16441    let multi_buffer = cx.new(|cx| {
16442        let mut multibuffer = MultiBuffer::new(ReadWrite);
16443        multibuffer.push_excerpts(
16444            buffer_1.clone(),
16445            [
16446                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16447                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16448                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16449            ],
16450            cx,
16451        );
16452        multibuffer.push_excerpts(
16453            buffer_2.clone(),
16454            [
16455                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16456                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16457                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16458            ],
16459            cx,
16460        );
16461        multibuffer.push_excerpts(
16462            buffer_3.clone(),
16463            [
16464                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16465                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16466                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16467            ],
16468            cx,
16469        );
16470        multibuffer
16471    });
16472
16473    let fs = FakeFs::new(cx.executor());
16474    fs.insert_tree(
16475        "/a",
16476        json!({
16477            "main.rs": sample_text_1,
16478            "other.rs": sample_text_2,
16479            "lib.rs": sample_text_3,
16480        }),
16481    )
16482    .await;
16483    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16484    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16485    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16486    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16487        Editor::new(
16488            EditorMode::full(),
16489            multi_buffer,
16490            Some(project.clone()),
16491            window,
16492            cx,
16493        )
16494    });
16495    let multibuffer_item_id = workspace
16496        .update(cx, |workspace, window, cx| {
16497            assert!(
16498                workspace.active_item(cx).is_none(),
16499                "active item should be None before the first item is added"
16500            );
16501            workspace.add_item_to_active_pane(
16502                Box::new(multi_buffer_editor.clone()),
16503                None,
16504                true,
16505                window,
16506                cx,
16507            );
16508            let active_item = workspace
16509                .active_item(cx)
16510                .expect("should have an active item after adding the multi buffer");
16511            assert!(
16512                !active_item.is_singleton(cx),
16513                "A multi buffer was expected to active after adding"
16514            );
16515            active_item.item_id()
16516        })
16517        .unwrap();
16518    cx.executor().run_until_parked();
16519
16520    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16521        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16522            s.select_ranges(Some(1..2))
16523        });
16524        editor.open_excerpts(&OpenExcerpts, window, cx);
16525    });
16526    cx.executor().run_until_parked();
16527    let first_item_id = workspace
16528        .update(cx, |workspace, window, cx| {
16529            let active_item = workspace
16530                .active_item(cx)
16531                .expect("should have an active item after navigating into the 1st buffer");
16532            let first_item_id = active_item.item_id();
16533            assert_ne!(
16534                first_item_id, multibuffer_item_id,
16535                "Should navigate into the 1st buffer and activate it"
16536            );
16537            assert!(
16538                active_item.is_singleton(cx),
16539                "New active item should be a singleton buffer"
16540            );
16541            assert_eq!(
16542                active_item
16543                    .act_as::<Editor>(cx)
16544                    .expect("should have navigated into an editor for the 1st buffer")
16545                    .read(cx)
16546                    .text(cx),
16547                sample_text_1
16548            );
16549
16550            workspace
16551                .go_back(workspace.active_pane().downgrade(), window, cx)
16552                .detach_and_log_err(cx);
16553
16554            first_item_id
16555        })
16556        .unwrap();
16557    cx.executor().run_until_parked();
16558    workspace
16559        .update(cx, |workspace, _, cx| {
16560            let active_item = workspace
16561                .active_item(cx)
16562                .expect("should have an active item after navigating back");
16563            assert_eq!(
16564                active_item.item_id(),
16565                multibuffer_item_id,
16566                "Should navigate back to the multi buffer"
16567            );
16568            assert!(!active_item.is_singleton(cx));
16569        })
16570        .unwrap();
16571
16572    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16573        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16574            s.select_ranges(Some(39..40))
16575        });
16576        editor.open_excerpts(&OpenExcerpts, window, cx);
16577    });
16578    cx.executor().run_until_parked();
16579    let second_item_id = workspace
16580        .update(cx, |workspace, window, cx| {
16581            let active_item = workspace
16582                .active_item(cx)
16583                .expect("should have an active item after navigating into the 2nd buffer");
16584            let second_item_id = active_item.item_id();
16585            assert_ne!(
16586                second_item_id, multibuffer_item_id,
16587                "Should navigate away from the multibuffer"
16588            );
16589            assert_ne!(
16590                second_item_id, first_item_id,
16591                "Should navigate into the 2nd buffer and activate it"
16592            );
16593            assert!(
16594                active_item.is_singleton(cx),
16595                "New active item should be a singleton buffer"
16596            );
16597            assert_eq!(
16598                active_item
16599                    .act_as::<Editor>(cx)
16600                    .expect("should have navigated into an editor")
16601                    .read(cx)
16602                    .text(cx),
16603                sample_text_2
16604            );
16605
16606            workspace
16607                .go_back(workspace.active_pane().downgrade(), window, cx)
16608                .detach_and_log_err(cx);
16609
16610            second_item_id
16611        })
16612        .unwrap();
16613    cx.executor().run_until_parked();
16614    workspace
16615        .update(cx, |workspace, _, cx| {
16616            let active_item = workspace
16617                .active_item(cx)
16618                .expect("should have an active item after navigating back from the 2nd buffer");
16619            assert_eq!(
16620                active_item.item_id(),
16621                multibuffer_item_id,
16622                "Should navigate back from the 2nd buffer to the multi buffer"
16623            );
16624            assert!(!active_item.is_singleton(cx));
16625        })
16626        .unwrap();
16627
16628    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16629        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16630            s.select_ranges(Some(70..70))
16631        });
16632        editor.open_excerpts(&OpenExcerpts, window, cx);
16633    });
16634    cx.executor().run_until_parked();
16635    workspace
16636        .update(cx, |workspace, window, cx| {
16637            let active_item = workspace
16638                .active_item(cx)
16639                .expect("should have an active item after navigating into the 3rd buffer");
16640            let third_item_id = active_item.item_id();
16641            assert_ne!(
16642                third_item_id, multibuffer_item_id,
16643                "Should navigate into the 3rd buffer and activate it"
16644            );
16645            assert_ne!(third_item_id, first_item_id);
16646            assert_ne!(third_item_id, second_item_id);
16647            assert!(
16648                active_item.is_singleton(cx),
16649                "New active item should be a singleton buffer"
16650            );
16651            assert_eq!(
16652                active_item
16653                    .act_as::<Editor>(cx)
16654                    .expect("should have navigated into an editor")
16655                    .read(cx)
16656                    .text(cx),
16657                sample_text_3
16658            );
16659
16660            workspace
16661                .go_back(workspace.active_pane().downgrade(), window, cx)
16662                .detach_and_log_err(cx);
16663        })
16664        .unwrap();
16665    cx.executor().run_until_parked();
16666    workspace
16667        .update(cx, |workspace, _, cx| {
16668            let active_item = workspace
16669                .active_item(cx)
16670                .expect("should have an active item after navigating back from the 3rd buffer");
16671            assert_eq!(
16672                active_item.item_id(),
16673                multibuffer_item_id,
16674                "Should navigate back from the 3rd buffer to the multi buffer"
16675            );
16676            assert!(!active_item.is_singleton(cx));
16677        })
16678        .unwrap();
16679}
16680
16681#[gpui::test]
16682async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16683    init_test(cx, |_| {});
16684
16685    let mut cx = EditorTestContext::new(cx).await;
16686
16687    let diff_base = r#"
16688        use some::mod;
16689
16690        const A: u32 = 42;
16691
16692        fn main() {
16693            println!("hello");
16694
16695            println!("world");
16696        }
16697        "#
16698    .unindent();
16699
16700    cx.set_state(
16701        &r#"
16702        use some::modified;
16703
16704        ˇ
16705        fn main() {
16706            println!("hello there");
16707
16708            println!("around the");
16709            println!("world");
16710        }
16711        "#
16712        .unindent(),
16713    );
16714
16715    cx.set_head_text(&diff_base);
16716    executor.run_until_parked();
16717
16718    cx.update_editor(|editor, window, cx| {
16719        editor.go_to_next_hunk(&GoToHunk, window, cx);
16720        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16721    });
16722    executor.run_until_parked();
16723    cx.assert_state_with_diff(
16724        r#"
16725          use some::modified;
16726
16727
16728          fn main() {
16729        -     println!("hello");
16730        + ˇ    println!("hello there");
16731
16732              println!("around the");
16733              println!("world");
16734          }
16735        "#
16736        .unindent(),
16737    );
16738
16739    cx.update_editor(|editor, window, cx| {
16740        for _ in 0..2 {
16741            editor.go_to_next_hunk(&GoToHunk, window, cx);
16742            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16743        }
16744    });
16745    executor.run_until_parked();
16746    cx.assert_state_with_diff(
16747        r#"
16748        - use some::mod;
16749        + ˇuse some::modified;
16750
16751
16752          fn main() {
16753        -     println!("hello");
16754        +     println!("hello there");
16755
16756        +     println!("around the");
16757              println!("world");
16758          }
16759        "#
16760        .unindent(),
16761    );
16762
16763    cx.update_editor(|editor, window, cx| {
16764        editor.go_to_next_hunk(&GoToHunk, window, cx);
16765        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16766    });
16767    executor.run_until_parked();
16768    cx.assert_state_with_diff(
16769        r#"
16770        - use some::mod;
16771        + use some::modified;
16772
16773        - const A: u32 = 42;
16774          ˇ
16775          fn main() {
16776        -     println!("hello");
16777        +     println!("hello there");
16778
16779        +     println!("around the");
16780              println!("world");
16781          }
16782        "#
16783        .unindent(),
16784    );
16785
16786    cx.update_editor(|editor, window, cx| {
16787        editor.cancel(&Cancel, window, cx);
16788    });
16789
16790    cx.assert_state_with_diff(
16791        r#"
16792          use some::modified;
16793
16794          ˇ
16795          fn main() {
16796              println!("hello there");
16797
16798              println!("around the");
16799              println!("world");
16800          }
16801        "#
16802        .unindent(),
16803    );
16804}
16805
16806#[gpui::test]
16807async fn test_diff_base_change_with_expanded_diff_hunks(
16808    executor: BackgroundExecutor,
16809    cx: &mut TestAppContext,
16810) {
16811    init_test(cx, |_| {});
16812
16813    let mut cx = EditorTestContext::new(cx).await;
16814
16815    let diff_base = r#"
16816        use some::mod1;
16817        use some::mod2;
16818
16819        const A: u32 = 42;
16820        const B: u32 = 42;
16821        const C: u32 = 42;
16822
16823        fn main() {
16824            println!("hello");
16825
16826            println!("world");
16827        }
16828        "#
16829    .unindent();
16830
16831    cx.set_state(
16832        &r#"
16833        use some::mod2;
16834
16835        const A: u32 = 42;
16836        const C: u32 = 42;
16837
16838        fn main(ˇ) {
16839            //println!("hello");
16840
16841            println!("world");
16842            //
16843            //
16844        }
16845        "#
16846        .unindent(),
16847    );
16848
16849    cx.set_head_text(&diff_base);
16850    executor.run_until_parked();
16851
16852    cx.update_editor(|editor, window, cx| {
16853        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16854    });
16855    executor.run_until_parked();
16856    cx.assert_state_with_diff(
16857        r#"
16858        - use some::mod1;
16859          use some::mod2;
16860
16861          const A: u32 = 42;
16862        - const B: u32 = 42;
16863          const C: u32 = 42;
16864
16865          fn main(ˇ) {
16866        -     println!("hello");
16867        +     //println!("hello");
16868
16869              println!("world");
16870        +     //
16871        +     //
16872          }
16873        "#
16874        .unindent(),
16875    );
16876
16877    cx.set_head_text("new diff base!");
16878    executor.run_until_parked();
16879    cx.assert_state_with_diff(
16880        r#"
16881        - new diff base!
16882        + use some::mod2;
16883        +
16884        + const A: u32 = 42;
16885        + const C: u32 = 42;
16886        +
16887        + fn main(ˇ) {
16888        +     //println!("hello");
16889        +
16890        +     println!("world");
16891        +     //
16892        +     //
16893        + }
16894        "#
16895        .unindent(),
16896    );
16897}
16898
16899#[gpui::test]
16900async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16901    init_test(cx, |_| {});
16902
16903    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16904    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16905    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16906    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16907    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16908    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16909
16910    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16911    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16912    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16913
16914    let multi_buffer = cx.new(|cx| {
16915        let mut multibuffer = MultiBuffer::new(ReadWrite);
16916        multibuffer.push_excerpts(
16917            buffer_1.clone(),
16918            [
16919                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16920                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16921                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16922            ],
16923            cx,
16924        );
16925        multibuffer.push_excerpts(
16926            buffer_2.clone(),
16927            [
16928                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16929                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16930                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16931            ],
16932            cx,
16933        );
16934        multibuffer.push_excerpts(
16935            buffer_3.clone(),
16936            [
16937                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16938                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16939                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16940            ],
16941            cx,
16942        );
16943        multibuffer
16944    });
16945
16946    let editor =
16947        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16948    editor
16949        .update(cx, |editor, _window, cx| {
16950            for (buffer, diff_base) in [
16951                (buffer_1.clone(), file_1_old),
16952                (buffer_2.clone(), file_2_old),
16953                (buffer_3.clone(), file_3_old),
16954            ] {
16955                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16956                editor
16957                    .buffer
16958                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16959            }
16960        })
16961        .unwrap();
16962
16963    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16964    cx.run_until_parked();
16965
16966    cx.assert_editor_state(
16967        &"
16968            ˇaaa
16969            ccc
16970            ddd
16971
16972            ggg
16973            hhh
16974
16975
16976            lll
16977            mmm
16978            NNN
16979
16980            qqq
16981            rrr
16982
16983            uuu
16984            111
16985            222
16986            333
16987
16988            666
16989            777
16990
16991            000
16992            !!!"
16993        .unindent(),
16994    );
16995
16996    cx.update_editor(|editor, window, cx| {
16997        editor.select_all(&SelectAll, window, cx);
16998        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16999    });
17000    cx.executor().run_until_parked();
17001
17002    cx.assert_state_with_diff(
17003        "
17004            «aaa
17005          - bbb
17006            ccc
17007            ddd
17008
17009            ggg
17010            hhh
17011
17012
17013            lll
17014            mmm
17015          - nnn
17016          + NNN
17017
17018            qqq
17019            rrr
17020
17021            uuu
17022            111
17023            222
17024            333
17025
17026          + 666
17027            777
17028
17029            000
17030            !!!ˇ»"
17031            .unindent(),
17032    );
17033}
17034
17035#[gpui::test]
17036async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17037    init_test(cx, |_| {});
17038
17039    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17040    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17041
17042    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17043    let multi_buffer = cx.new(|cx| {
17044        let mut multibuffer = MultiBuffer::new(ReadWrite);
17045        multibuffer.push_excerpts(
17046            buffer.clone(),
17047            [
17048                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17049                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17050                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17051            ],
17052            cx,
17053        );
17054        multibuffer
17055    });
17056
17057    let editor =
17058        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17059    editor
17060        .update(cx, |editor, _window, cx| {
17061            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17062            editor
17063                .buffer
17064                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17065        })
17066        .unwrap();
17067
17068    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17069    cx.run_until_parked();
17070
17071    cx.update_editor(|editor, window, cx| {
17072        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17073    });
17074    cx.executor().run_until_parked();
17075
17076    // When the start of a hunk coincides with the start of its excerpt,
17077    // the hunk is expanded. When the start of a a hunk is earlier than
17078    // the start of its excerpt, the hunk is not expanded.
17079    cx.assert_state_with_diff(
17080        "
17081            ˇaaa
17082          - bbb
17083          + BBB
17084
17085          - ddd
17086          - eee
17087          + DDD
17088          + EEE
17089            fff
17090
17091            iii
17092        "
17093        .unindent(),
17094    );
17095}
17096
17097#[gpui::test]
17098async fn test_edits_around_expanded_insertion_hunks(
17099    executor: BackgroundExecutor,
17100    cx: &mut TestAppContext,
17101) {
17102    init_test(cx, |_| {});
17103
17104    let mut cx = EditorTestContext::new(cx).await;
17105
17106    let diff_base = r#"
17107        use some::mod1;
17108        use some::mod2;
17109
17110        const A: u32 = 42;
17111
17112        fn main() {
17113            println!("hello");
17114
17115            println!("world");
17116        }
17117        "#
17118    .unindent();
17119    executor.run_until_parked();
17120    cx.set_state(
17121        &r#"
17122        use some::mod1;
17123        use some::mod2;
17124
17125        const A: u32 = 42;
17126        const B: u32 = 42;
17127        const C: u32 = 42;
17128        ˇ
17129
17130        fn main() {
17131            println!("hello");
17132
17133            println!("world");
17134        }
17135        "#
17136        .unindent(),
17137    );
17138
17139    cx.set_head_text(&diff_base);
17140    executor.run_until_parked();
17141
17142    cx.update_editor(|editor, window, cx| {
17143        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17144    });
17145    executor.run_until_parked();
17146
17147    cx.assert_state_with_diff(
17148        r#"
17149        use some::mod1;
17150        use some::mod2;
17151
17152        const A: u32 = 42;
17153      + const B: u32 = 42;
17154      + const C: u32 = 42;
17155      + ˇ
17156
17157        fn main() {
17158            println!("hello");
17159
17160            println!("world");
17161        }
17162      "#
17163        .unindent(),
17164    );
17165
17166    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17167    executor.run_until_parked();
17168
17169    cx.assert_state_with_diff(
17170        r#"
17171        use some::mod1;
17172        use some::mod2;
17173
17174        const A: u32 = 42;
17175      + const B: u32 = 42;
17176      + const C: u32 = 42;
17177      + const D: u32 = 42;
17178      + ˇ
17179
17180        fn main() {
17181            println!("hello");
17182
17183            println!("world");
17184        }
17185      "#
17186        .unindent(),
17187    );
17188
17189    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17190    executor.run_until_parked();
17191
17192    cx.assert_state_with_diff(
17193        r#"
17194        use some::mod1;
17195        use some::mod2;
17196
17197        const A: u32 = 42;
17198      + const B: u32 = 42;
17199      + const C: u32 = 42;
17200      + const D: u32 = 42;
17201      + const E: u32 = 42;
17202      + ˇ
17203
17204        fn main() {
17205            println!("hello");
17206
17207            println!("world");
17208        }
17209      "#
17210        .unindent(),
17211    );
17212
17213    cx.update_editor(|editor, window, cx| {
17214        editor.delete_line(&DeleteLine, window, cx);
17215    });
17216    executor.run_until_parked();
17217
17218    cx.assert_state_with_diff(
17219        r#"
17220        use some::mod1;
17221        use some::mod2;
17222
17223        const A: u32 = 42;
17224      + const B: u32 = 42;
17225      + const C: u32 = 42;
17226      + const D: u32 = 42;
17227      + const E: u32 = 42;
17228        ˇ
17229        fn main() {
17230            println!("hello");
17231
17232            println!("world");
17233        }
17234      "#
17235        .unindent(),
17236    );
17237
17238    cx.update_editor(|editor, window, cx| {
17239        editor.move_up(&MoveUp, window, cx);
17240        editor.delete_line(&DeleteLine, window, cx);
17241        editor.move_up(&MoveUp, window, cx);
17242        editor.delete_line(&DeleteLine, window, cx);
17243        editor.move_up(&MoveUp, window, cx);
17244        editor.delete_line(&DeleteLine, window, cx);
17245    });
17246    executor.run_until_parked();
17247    cx.assert_state_with_diff(
17248        r#"
17249        use some::mod1;
17250        use some::mod2;
17251
17252        const A: u32 = 42;
17253      + const B: u32 = 42;
17254        ˇ
17255        fn main() {
17256            println!("hello");
17257
17258            println!("world");
17259        }
17260      "#
17261        .unindent(),
17262    );
17263
17264    cx.update_editor(|editor, window, cx| {
17265        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17266        editor.delete_line(&DeleteLine, window, cx);
17267    });
17268    executor.run_until_parked();
17269    cx.assert_state_with_diff(
17270        r#"
17271        ˇ
17272        fn main() {
17273            println!("hello");
17274
17275            println!("world");
17276        }
17277      "#
17278        .unindent(),
17279    );
17280}
17281
17282#[gpui::test]
17283async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17284    init_test(cx, |_| {});
17285
17286    let mut cx = EditorTestContext::new(cx).await;
17287    cx.set_head_text(indoc! { "
17288        one
17289        two
17290        three
17291        four
17292        five
17293        "
17294    });
17295    cx.set_state(indoc! { "
17296        one
17297        ˇthree
17298        five
17299    "});
17300    cx.run_until_parked();
17301    cx.update_editor(|editor, window, cx| {
17302        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17303    });
17304    cx.assert_state_with_diff(
17305        indoc! { "
17306        one
17307      - two
17308        ˇthree
17309      - four
17310        five
17311    "}
17312        .to_string(),
17313    );
17314    cx.update_editor(|editor, window, cx| {
17315        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17316    });
17317
17318    cx.assert_state_with_diff(
17319        indoc! { "
17320        one
17321        ˇthree
17322        five
17323    "}
17324        .to_string(),
17325    );
17326
17327    cx.set_state(indoc! { "
17328        one
17329        ˇTWO
17330        three
17331        four
17332        five
17333    "});
17334    cx.run_until_parked();
17335    cx.update_editor(|editor, window, cx| {
17336        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17337    });
17338
17339    cx.assert_state_with_diff(
17340        indoc! { "
17341            one
17342          - two
17343          + ˇTWO
17344            three
17345            four
17346            five
17347        "}
17348        .to_string(),
17349    );
17350    cx.update_editor(|editor, window, cx| {
17351        editor.move_up(&Default::default(), window, cx);
17352        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17353    });
17354    cx.assert_state_with_diff(
17355        indoc! { "
17356            one
17357            ˇTWO
17358            three
17359            four
17360            five
17361        "}
17362        .to_string(),
17363    );
17364}
17365
17366#[gpui::test]
17367async fn test_edits_around_expanded_deletion_hunks(
17368    executor: BackgroundExecutor,
17369    cx: &mut TestAppContext,
17370) {
17371    init_test(cx, |_| {});
17372
17373    let mut cx = EditorTestContext::new(cx).await;
17374
17375    let diff_base = r#"
17376        use some::mod1;
17377        use some::mod2;
17378
17379        const A: u32 = 42;
17380        const B: u32 = 42;
17381        const C: u32 = 42;
17382
17383
17384        fn main() {
17385            println!("hello");
17386
17387            println!("world");
17388        }
17389    "#
17390    .unindent();
17391    executor.run_until_parked();
17392    cx.set_state(
17393        &r#"
17394        use some::mod1;
17395        use some::mod2;
17396
17397        ˇconst B: u32 = 42;
17398        const C: u32 = 42;
17399
17400
17401        fn main() {
17402            println!("hello");
17403
17404            println!("world");
17405        }
17406        "#
17407        .unindent(),
17408    );
17409
17410    cx.set_head_text(&diff_base);
17411    executor.run_until_parked();
17412
17413    cx.update_editor(|editor, window, cx| {
17414        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17415    });
17416    executor.run_until_parked();
17417
17418    cx.assert_state_with_diff(
17419        r#"
17420        use some::mod1;
17421        use some::mod2;
17422
17423      - const A: u32 = 42;
17424        ˇconst B: u32 = 42;
17425        const C: u32 = 42;
17426
17427
17428        fn main() {
17429            println!("hello");
17430
17431            println!("world");
17432        }
17433      "#
17434        .unindent(),
17435    );
17436
17437    cx.update_editor(|editor, window, cx| {
17438        editor.delete_line(&DeleteLine, window, cx);
17439    });
17440    executor.run_until_parked();
17441    cx.assert_state_with_diff(
17442        r#"
17443        use some::mod1;
17444        use some::mod2;
17445
17446      - const A: u32 = 42;
17447      - const B: u32 = 42;
17448        ˇconst C: u32 = 42;
17449
17450
17451        fn main() {
17452            println!("hello");
17453
17454            println!("world");
17455        }
17456      "#
17457        .unindent(),
17458    );
17459
17460    cx.update_editor(|editor, window, cx| {
17461        editor.delete_line(&DeleteLine, window, cx);
17462    });
17463    executor.run_until_parked();
17464    cx.assert_state_with_diff(
17465        r#"
17466        use some::mod1;
17467        use some::mod2;
17468
17469      - const A: u32 = 42;
17470      - const B: u32 = 42;
17471      - const C: u32 = 42;
17472        ˇ
17473
17474        fn main() {
17475            println!("hello");
17476
17477            println!("world");
17478        }
17479      "#
17480        .unindent(),
17481    );
17482
17483    cx.update_editor(|editor, window, cx| {
17484        editor.handle_input("replacement", window, cx);
17485    });
17486    executor.run_until_parked();
17487    cx.assert_state_with_diff(
17488        r#"
17489        use some::mod1;
17490        use some::mod2;
17491
17492      - const A: u32 = 42;
17493      - const B: u32 = 42;
17494      - const C: u32 = 42;
17495      -
17496      + replacementˇ
17497
17498        fn main() {
17499            println!("hello");
17500
17501            println!("world");
17502        }
17503      "#
17504        .unindent(),
17505    );
17506}
17507
17508#[gpui::test]
17509async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17510    init_test(cx, |_| {});
17511
17512    let mut cx = EditorTestContext::new(cx).await;
17513
17514    let base_text = r#"
17515        one
17516        two
17517        three
17518        four
17519        five
17520    "#
17521    .unindent();
17522    executor.run_until_parked();
17523    cx.set_state(
17524        &r#"
17525        one
17526        two
17527        fˇour
17528        five
17529        "#
17530        .unindent(),
17531    );
17532
17533    cx.set_head_text(&base_text);
17534    executor.run_until_parked();
17535
17536    cx.update_editor(|editor, window, cx| {
17537        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17538    });
17539    executor.run_until_parked();
17540
17541    cx.assert_state_with_diff(
17542        r#"
17543          one
17544          two
17545        - three
17546          fˇour
17547          five
17548        "#
17549        .unindent(),
17550    );
17551
17552    cx.update_editor(|editor, window, cx| {
17553        editor.backspace(&Backspace, window, cx);
17554        editor.backspace(&Backspace, window, cx);
17555    });
17556    executor.run_until_parked();
17557    cx.assert_state_with_diff(
17558        r#"
17559          one
17560          two
17561        - threeˇ
17562        - four
17563        + our
17564          five
17565        "#
17566        .unindent(),
17567    );
17568}
17569
17570#[gpui::test]
17571async fn test_edit_after_expanded_modification_hunk(
17572    executor: BackgroundExecutor,
17573    cx: &mut TestAppContext,
17574) {
17575    init_test(cx, |_| {});
17576
17577    let mut cx = EditorTestContext::new(cx).await;
17578
17579    let diff_base = r#"
17580        use some::mod1;
17581        use some::mod2;
17582
17583        const A: u32 = 42;
17584        const B: u32 = 42;
17585        const C: u32 = 42;
17586        const D: u32 = 42;
17587
17588
17589        fn main() {
17590            println!("hello");
17591
17592            println!("world");
17593        }"#
17594    .unindent();
17595
17596    cx.set_state(
17597        &r#"
17598        use some::mod1;
17599        use some::mod2;
17600
17601        const A: u32 = 42;
17602        const B: u32 = 42;
17603        const C: u32 = 43ˇ
17604        const D: u32 = 42;
17605
17606
17607        fn main() {
17608            println!("hello");
17609
17610            println!("world");
17611        }"#
17612        .unindent(),
17613    );
17614
17615    cx.set_head_text(&diff_base);
17616    executor.run_until_parked();
17617    cx.update_editor(|editor, window, cx| {
17618        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17619    });
17620    executor.run_until_parked();
17621
17622    cx.assert_state_with_diff(
17623        r#"
17624        use some::mod1;
17625        use some::mod2;
17626
17627        const A: u32 = 42;
17628        const B: u32 = 42;
17629      - const C: u32 = 42;
17630      + const C: u32 = 43ˇ
17631        const D: u32 = 42;
17632
17633
17634        fn main() {
17635            println!("hello");
17636
17637            println!("world");
17638        }"#
17639        .unindent(),
17640    );
17641
17642    cx.update_editor(|editor, window, cx| {
17643        editor.handle_input("\nnew_line\n", window, cx);
17644    });
17645    executor.run_until_parked();
17646
17647    cx.assert_state_with_diff(
17648        r#"
17649        use some::mod1;
17650        use some::mod2;
17651
17652        const A: u32 = 42;
17653        const B: u32 = 42;
17654      - const C: u32 = 42;
17655      + const C: u32 = 43
17656      + new_line
17657      + ˇ
17658        const D: u32 = 42;
17659
17660
17661        fn main() {
17662            println!("hello");
17663
17664            println!("world");
17665        }"#
17666        .unindent(),
17667    );
17668}
17669
17670#[gpui::test]
17671async fn test_stage_and_unstage_added_file_hunk(
17672    executor: BackgroundExecutor,
17673    cx: &mut TestAppContext,
17674) {
17675    init_test(cx, |_| {});
17676
17677    let mut cx = EditorTestContext::new(cx).await;
17678    cx.update_editor(|editor, _, cx| {
17679        editor.set_expand_all_diff_hunks(cx);
17680    });
17681
17682    let working_copy = r#"
17683            ˇfn main() {
17684                println!("hello, world!");
17685            }
17686        "#
17687    .unindent();
17688
17689    cx.set_state(&working_copy);
17690    executor.run_until_parked();
17691
17692    cx.assert_state_with_diff(
17693        r#"
17694            + ˇfn main() {
17695            +     println!("hello, world!");
17696            + }
17697        "#
17698        .unindent(),
17699    );
17700    cx.assert_index_text(None);
17701
17702    cx.update_editor(|editor, window, cx| {
17703        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17704    });
17705    executor.run_until_parked();
17706    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17707    cx.assert_state_with_diff(
17708        r#"
17709            + ˇfn main() {
17710            +     println!("hello, world!");
17711            + }
17712        "#
17713        .unindent(),
17714    );
17715
17716    cx.update_editor(|editor, window, cx| {
17717        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17718    });
17719    executor.run_until_parked();
17720    cx.assert_index_text(None);
17721}
17722
17723async fn setup_indent_guides_editor(
17724    text: &str,
17725    cx: &mut TestAppContext,
17726) -> (BufferId, EditorTestContext) {
17727    init_test(cx, |_| {});
17728
17729    let mut cx = EditorTestContext::new(cx).await;
17730
17731    let buffer_id = cx.update_editor(|editor, window, cx| {
17732        editor.set_text(text, window, cx);
17733        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17734
17735        buffer_ids[0]
17736    });
17737
17738    (buffer_id, cx)
17739}
17740
17741fn assert_indent_guides(
17742    range: Range<u32>,
17743    expected: Vec<IndentGuide>,
17744    active_indices: Option<Vec<usize>>,
17745    cx: &mut EditorTestContext,
17746) {
17747    let indent_guides = cx.update_editor(|editor, window, cx| {
17748        let snapshot = editor.snapshot(window, cx).display_snapshot;
17749        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17750            editor,
17751            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17752            true,
17753            &snapshot,
17754            cx,
17755        );
17756
17757        indent_guides.sort_by(|a, b| {
17758            a.depth.cmp(&b.depth).then(
17759                a.start_row
17760                    .cmp(&b.start_row)
17761                    .then(a.end_row.cmp(&b.end_row)),
17762            )
17763        });
17764        indent_guides
17765    });
17766
17767    if let Some(expected) = active_indices {
17768        let active_indices = cx.update_editor(|editor, window, cx| {
17769            let snapshot = editor.snapshot(window, cx).display_snapshot;
17770            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17771        });
17772
17773        assert_eq!(
17774            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17775            expected,
17776            "Active indent guide indices do not match"
17777        );
17778    }
17779
17780    assert_eq!(indent_guides, expected, "Indent guides do not match");
17781}
17782
17783fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17784    IndentGuide {
17785        buffer_id,
17786        start_row: MultiBufferRow(start_row),
17787        end_row: MultiBufferRow(end_row),
17788        depth,
17789        tab_size: 4,
17790        settings: IndentGuideSettings {
17791            enabled: true,
17792            line_width: 1,
17793            active_line_width: 1,
17794            ..Default::default()
17795        },
17796    }
17797}
17798
17799#[gpui::test]
17800async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17801    let (buffer_id, mut cx) = setup_indent_guides_editor(
17802        &"
17803        fn main() {
17804            let a = 1;
17805        }"
17806        .unindent(),
17807        cx,
17808    )
17809    .await;
17810
17811    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17812}
17813
17814#[gpui::test]
17815async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17816    let (buffer_id, mut cx) = setup_indent_guides_editor(
17817        &"
17818        fn main() {
17819            let a = 1;
17820            let b = 2;
17821        }"
17822        .unindent(),
17823        cx,
17824    )
17825    .await;
17826
17827    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17828}
17829
17830#[gpui::test]
17831async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17832    let (buffer_id, mut cx) = setup_indent_guides_editor(
17833        &"
17834        fn main() {
17835            let a = 1;
17836            if a == 3 {
17837                let b = 2;
17838            } else {
17839                let c = 3;
17840            }
17841        }"
17842        .unindent(),
17843        cx,
17844    )
17845    .await;
17846
17847    assert_indent_guides(
17848        0..8,
17849        vec![
17850            indent_guide(buffer_id, 1, 6, 0),
17851            indent_guide(buffer_id, 3, 3, 1),
17852            indent_guide(buffer_id, 5, 5, 1),
17853        ],
17854        None,
17855        &mut cx,
17856    );
17857}
17858
17859#[gpui::test]
17860async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17861    let (buffer_id, mut cx) = setup_indent_guides_editor(
17862        &"
17863        fn main() {
17864            let a = 1;
17865                let b = 2;
17866            let c = 3;
17867        }"
17868        .unindent(),
17869        cx,
17870    )
17871    .await;
17872
17873    assert_indent_guides(
17874        0..5,
17875        vec![
17876            indent_guide(buffer_id, 1, 3, 0),
17877            indent_guide(buffer_id, 2, 2, 1),
17878        ],
17879        None,
17880        &mut cx,
17881    );
17882}
17883
17884#[gpui::test]
17885async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17886    let (buffer_id, mut cx) = setup_indent_guides_editor(
17887        &"
17888        fn main() {
17889            let a = 1;
17890
17891            let c = 3;
17892        }"
17893        .unindent(),
17894        cx,
17895    )
17896    .await;
17897
17898    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17899}
17900
17901#[gpui::test]
17902async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17903    let (buffer_id, mut cx) = setup_indent_guides_editor(
17904        &"
17905        fn main() {
17906            let a = 1;
17907
17908            let c = 3;
17909
17910            if a == 3 {
17911                let b = 2;
17912            } else {
17913                let c = 3;
17914            }
17915        }"
17916        .unindent(),
17917        cx,
17918    )
17919    .await;
17920
17921    assert_indent_guides(
17922        0..11,
17923        vec![
17924            indent_guide(buffer_id, 1, 9, 0),
17925            indent_guide(buffer_id, 6, 6, 1),
17926            indent_guide(buffer_id, 8, 8, 1),
17927        ],
17928        None,
17929        &mut cx,
17930    );
17931}
17932
17933#[gpui::test]
17934async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17935    let (buffer_id, mut cx) = setup_indent_guides_editor(
17936        &"
17937        fn main() {
17938            let a = 1;
17939
17940            let c = 3;
17941
17942            if a == 3 {
17943                let b = 2;
17944            } else {
17945                let c = 3;
17946            }
17947        }"
17948        .unindent(),
17949        cx,
17950    )
17951    .await;
17952
17953    assert_indent_guides(
17954        1..11,
17955        vec![
17956            indent_guide(buffer_id, 1, 9, 0),
17957            indent_guide(buffer_id, 6, 6, 1),
17958            indent_guide(buffer_id, 8, 8, 1),
17959        ],
17960        None,
17961        &mut cx,
17962    );
17963}
17964
17965#[gpui::test]
17966async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17967    let (buffer_id, mut cx) = setup_indent_guides_editor(
17968        &"
17969        fn main() {
17970            let a = 1;
17971
17972            let c = 3;
17973
17974            if a == 3 {
17975                let b = 2;
17976            } else {
17977                let c = 3;
17978            }
17979        }"
17980        .unindent(),
17981        cx,
17982    )
17983    .await;
17984
17985    assert_indent_guides(
17986        1..10,
17987        vec![
17988            indent_guide(buffer_id, 1, 9, 0),
17989            indent_guide(buffer_id, 6, 6, 1),
17990            indent_guide(buffer_id, 8, 8, 1),
17991        ],
17992        None,
17993        &mut cx,
17994    );
17995}
17996
17997#[gpui::test]
17998async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17999    let (buffer_id, mut cx) = setup_indent_guides_editor(
18000        &"
18001        fn main() {
18002            if a {
18003                b(
18004                    c,
18005                    d,
18006                )
18007            } else {
18008                e(
18009                    f
18010                )
18011            }
18012        }"
18013        .unindent(),
18014        cx,
18015    )
18016    .await;
18017
18018    assert_indent_guides(
18019        0..11,
18020        vec![
18021            indent_guide(buffer_id, 1, 10, 0),
18022            indent_guide(buffer_id, 2, 5, 1),
18023            indent_guide(buffer_id, 7, 9, 1),
18024            indent_guide(buffer_id, 3, 4, 2),
18025            indent_guide(buffer_id, 8, 8, 2),
18026        ],
18027        None,
18028        &mut cx,
18029    );
18030
18031    cx.update_editor(|editor, window, cx| {
18032        editor.fold_at(MultiBufferRow(2), window, cx);
18033        assert_eq!(
18034            editor.display_text(cx),
18035            "
18036            fn main() {
18037                if a {
18038                    b(⋯
18039                    )
18040                } else {
18041                    e(
18042                        f
18043                    )
18044                }
18045            }"
18046            .unindent()
18047        );
18048    });
18049
18050    assert_indent_guides(
18051        0..11,
18052        vec![
18053            indent_guide(buffer_id, 1, 10, 0),
18054            indent_guide(buffer_id, 2, 5, 1),
18055            indent_guide(buffer_id, 7, 9, 1),
18056            indent_guide(buffer_id, 8, 8, 2),
18057        ],
18058        None,
18059        &mut cx,
18060    );
18061}
18062
18063#[gpui::test]
18064async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18065    let (buffer_id, mut cx) = setup_indent_guides_editor(
18066        &"
18067        block1
18068            block2
18069                block3
18070                    block4
18071            block2
18072        block1
18073        block1"
18074            .unindent(),
18075        cx,
18076    )
18077    .await;
18078
18079    assert_indent_guides(
18080        1..10,
18081        vec![
18082            indent_guide(buffer_id, 1, 4, 0),
18083            indent_guide(buffer_id, 2, 3, 1),
18084            indent_guide(buffer_id, 3, 3, 2),
18085        ],
18086        None,
18087        &mut cx,
18088    );
18089}
18090
18091#[gpui::test]
18092async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18093    let (buffer_id, mut cx) = setup_indent_guides_editor(
18094        &"
18095        block1
18096            block2
18097                block3
18098
18099        block1
18100        block1"
18101            .unindent(),
18102        cx,
18103    )
18104    .await;
18105
18106    assert_indent_guides(
18107        0..6,
18108        vec![
18109            indent_guide(buffer_id, 1, 2, 0),
18110            indent_guide(buffer_id, 2, 2, 1),
18111        ],
18112        None,
18113        &mut cx,
18114    );
18115}
18116
18117#[gpui::test]
18118async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18119    let (buffer_id, mut cx) = setup_indent_guides_editor(
18120        &"
18121        function component() {
18122        \treturn (
18123        \t\t\t
18124        \t\t<div>
18125        \t\t\t<abc></abc>
18126        \t\t</div>
18127        \t)
18128        }"
18129        .unindent(),
18130        cx,
18131    )
18132    .await;
18133
18134    assert_indent_guides(
18135        0..8,
18136        vec![
18137            indent_guide(buffer_id, 1, 6, 0),
18138            indent_guide(buffer_id, 2, 5, 1),
18139            indent_guide(buffer_id, 4, 4, 2),
18140        ],
18141        None,
18142        &mut cx,
18143    );
18144}
18145
18146#[gpui::test]
18147async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18148    let (buffer_id, mut cx) = setup_indent_guides_editor(
18149        &"
18150        function component() {
18151        \treturn (
18152        \t
18153        \t\t<div>
18154        \t\t\t<abc></abc>
18155        \t\t</div>
18156        \t)
18157        }"
18158        .unindent(),
18159        cx,
18160    )
18161    .await;
18162
18163    assert_indent_guides(
18164        0..8,
18165        vec![
18166            indent_guide(buffer_id, 1, 6, 0),
18167            indent_guide(buffer_id, 2, 5, 1),
18168            indent_guide(buffer_id, 4, 4, 2),
18169        ],
18170        None,
18171        &mut cx,
18172    );
18173}
18174
18175#[gpui::test]
18176async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18177    let (buffer_id, mut cx) = setup_indent_guides_editor(
18178        &"
18179        block1
18180
18181
18182
18183            block2
18184        "
18185        .unindent(),
18186        cx,
18187    )
18188    .await;
18189
18190    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18191}
18192
18193#[gpui::test]
18194async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18195    let (buffer_id, mut cx) = setup_indent_guides_editor(
18196        &"
18197        def a:
18198        \tb = 3
18199        \tif True:
18200        \t\tc = 4
18201        \t\td = 5
18202        \tprint(b)
18203        "
18204        .unindent(),
18205        cx,
18206    )
18207    .await;
18208
18209    assert_indent_guides(
18210        0..6,
18211        vec![
18212            indent_guide(buffer_id, 1, 5, 0),
18213            indent_guide(buffer_id, 3, 4, 1),
18214        ],
18215        None,
18216        &mut cx,
18217    );
18218}
18219
18220#[gpui::test]
18221async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18222    let (buffer_id, mut cx) = setup_indent_guides_editor(
18223        &"
18224    fn main() {
18225        let a = 1;
18226    }"
18227        .unindent(),
18228        cx,
18229    )
18230    .await;
18231
18232    cx.update_editor(|editor, window, cx| {
18233        editor.change_selections(None, window, cx, |s| {
18234            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18235        });
18236    });
18237
18238    assert_indent_guides(
18239        0..3,
18240        vec![indent_guide(buffer_id, 1, 1, 0)],
18241        Some(vec![0]),
18242        &mut cx,
18243    );
18244}
18245
18246#[gpui::test]
18247async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18248    let (buffer_id, mut cx) = setup_indent_guides_editor(
18249        &"
18250    fn main() {
18251        if 1 == 2 {
18252            let a = 1;
18253        }
18254    }"
18255        .unindent(),
18256        cx,
18257    )
18258    .await;
18259
18260    cx.update_editor(|editor, window, cx| {
18261        editor.change_selections(None, window, cx, |s| {
18262            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18263        });
18264    });
18265
18266    assert_indent_guides(
18267        0..4,
18268        vec![
18269            indent_guide(buffer_id, 1, 3, 0),
18270            indent_guide(buffer_id, 2, 2, 1),
18271        ],
18272        Some(vec![1]),
18273        &mut cx,
18274    );
18275
18276    cx.update_editor(|editor, window, cx| {
18277        editor.change_selections(None, window, cx, |s| {
18278            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18279        });
18280    });
18281
18282    assert_indent_guides(
18283        0..4,
18284        vec![
18285            indent_guide(buffer_id, 1, 3, 0),
18286            indent_guide(buffer_id, 2, 2, 1),
18287        ],
18288        Some(vec![1]),
18289        &mut cx,
18290    );
18291
18292    cx.update_editor(|editor, window, cx| {
18293        editor.change_selections(None, window, cx, |s| {
18294            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18295        });
18296    });
18297
18298    assert_indent_guides(
18299        0..4,
18300        vec![
18301            indent_guide(buffer_id, 1, 3, 0),
18302            indent_guide(buffer_id, 2, 2, 1),
18303        ],
18304        Some(vec![0]),
18305        &mut cx,
18306    );
18307}
18308
18309#[gpui::test]
18310async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18311    let (buffer_id, mut cx) = setup_indent_guides_editor(
18312        &"
18313    fn main() {
18314        let a = 1;
18315
18316        let b = 2;
18317    }"
18318        .unindent(),
18319        cx,
18320    )
18321    .await;
18322
18323    cx.update_editor(|editor, window, cx| {
18324        editor.change_selections(None, window, cx, |s| {
18325            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18326        });
18327    });
18328
18329    assert_indent_guides(
18330        0..5,
18331        vec![indent_guide(buffer_id, 1, 3, 0)],
18332        Some(vec![0]),
18333        &mut cx,
18334    );
18335}
18336
18337#[gpui::test]
18338async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18339    let (buffer_id, mut cx) = setup_indent_guides_editor(
18340        &"
18341    def m:
18342        a = 1
18343        pass"
18344            .unindent(),
18345        cx,
18346    )
18347    .await;
18348
18349    cx.update_editor(|editor, window, cx| {
18350        editor.change_selections(None, window, cx, |s| {
18351            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18352        });
18353    });
18354
18355    assert_indent_guides(
18356        0..3,
18357        vec![indent_guide(buffer_id, 1, 2, 0)],
18358        Some(vec![0]),
18359        &mut cx,
18360    );
18361}
18362
18363#[gpui::test]
18364async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18365    init_test(cx, |_| {});
18366    let mut cx = EditorTestContext::new(cx).await;
18367    let text = indoc! {
18368        "
18369        impl A {
18370            fn b() {
18371                0;
18372                3;
18373                5;
18374                6;
18375                7;
18376            }
18377        }
18378        "
18379    };
18380    let base_text = indoc! {
18381        "
18382        impl A {
18383            fn b() {
18384                0;
18385                1;
18386                2;
18387                3;
18388                4;
18389            }
18390            fn c() {
18391                5;
18392                6;
18393                7;
18394            }
18395        }
18396        "
18397    };
18398
18399    cx.update_editor(|editor, window, cx| {
18400        editor.set_text(text, window, cx);
18401
18402        editor.buffer().update(cx, |multibuffer, cx| {
18403            let buffer = multibuffer.as_singleton().unwrap();
18404            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18405
18406            multibuffer.set_all_diff_hunks_expanded(cx);
18407            multibuffer.add_diff(diff, cx);
18408
18409            buffer.read(cx).remote_id()
18410        })
18411    });
18412    cx.run_until_parked();
18413
18414    cx.assert_state_with_diff(
18415        indoc! { "
18416          impl A {
18417              fn b() {
18418                  0;
18419        -         1;
18420        -         2;
18421                  3;
18422        -         4;
18423        -     }
18424        -     fn c() {
18425                  5;
18426                  6;
18427                  7;
18428              }
18429          }
18430          ˇ"
18431        }
18432        .to_string(),
18433    );
18434
18435    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18436        editor
18437            .snapshot(window, cx)
18438            .buffer_snapshot
18439            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18440            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18441            .collect::<Vec<_>>()
18442    });
18443    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18444    assert_eq!(
18445        actual_guides,
18446        vec![
18447            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18448            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18449            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18450        ]
18451    );
18452}
18453
18454#[gpui::test]
18455async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457    let mut cx = EditorTestContext::new(cx).await;
18458
18459    let diff_base = r#"
18460        a
18461        b
18462        c
18463        "#
18464    .unindent();
18465
18466    cx.set_state(
18467        &r#"
18468        ˇA
18469        b
18470        C
18471        "#
18472        .unindent(),
18473    );
18474    cx.set_head_text(&diff_base);
18475    cx.update_editor(|editor, window, cx| {
18476        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18477    });
18478    executor.run_until_parked();
18479
18480    let both_hunks_expanded = r#"
18481        - a
18482        + ˇA
18483          b
18484        - c
18485        + C
18486        "#
18487    .unindent();
18488
18489    cx.assert_state_with_diff(both_hunks_expanded.clone());
18490
18491    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18492        let snapshot = editor.snapshot(window, cx);
18493        let hunks = editor
18494            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18495            .collect::<Vec<_>>();
18496        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18497        let buffer_id = hunks[0].buffer_id;
18498        hunks
18499            .into_iter()
18500            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18501            .collect::<Vec<_>>()
18502    });
18503    assert_eq!(hunk_ranges.len(), 2);
18504
18505    cx.update_editor(|editor, _, cx| {
18506        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18507    });
18508    executor.run_until_parked();
18509
18510    let second_hunk_expanded = r#"
18511          ˇA
18512          b
18513        - c
18514        + C
18515        "#
18516    .unindent();
18517
18518    cx.assert_state_with_diff(second_hunk_expanded);
18519
18520    cx.update_editor(|editor, _, cx| {
18521        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18522    });
18523    executor.run_until_parked();
18524
18525    cx.assert_state_with_diff(both_hunks_expanded.clone());
18526
18527    cx.update_editor(|editor, _, cx| {
18528        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18529    });
18530    executor.run_until_parked();
18531
18532    let first_hunk_expanded = r#"
18533        - a
18534        + ˇA
18535          b
18536          C
18537        "#
18538    .unindent();
18539
18540    cx.assert_state_with_diff(first_hunk_expanded);
18541
18542    cx.update_editor(|editor, _, cx| {
18543        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18544    });
18545    executor.run_until_parked();
18546
18547    cx.assert_state_with_diff(both_hunks_expanded);
18548
18549    cx.set_state(
18550        &r#"
18551        ˇA
18552        b
18553        "#
18554        .unindent(),
18555    );
18556    cx.run_until_parked();
18557
18558    // TODO this cursor position seems bad
18559    cx.assert_state_with_diff(
18560        r#"
18561        - ˇa
18562        + A
18563          b
18564        "#
18565        .unindent(),
18566    );
18567
18568    cx.update_editor(|editor, window, cx| {
18569        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18570    });
18571
18572    cx.assert_state_with_diff(
18573        r#"
18574            - ˇa
18575            + A
18576              b
18577            - c
18578            "#
18579        .unindent(),
18580    );
18581
18582    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18583        let snapshot = editor.snapshot(window, cx);
18584        let hunks = editor
18585            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18586            .collect::<Vec<_>>();
18587        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18588        let buffer_id = hunks[0].buffer_id;
18589        hunks
18590            .into_iter()
18591            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18592            .collect::<Vec<_>>()
18593    });
18594    assert_eq!(hunk_ranges.len(), 2);
18595
18596    cx.update_editor(|editor, _, cx| {
18597        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18598    });
18599    executor.run_until_parked();
18600
18601    cx.assert_state_with_diff(
18602        r#"
18603        - ˇa
18604        + A
18605          b
18606        "#
18607        .unindent(),
18608    );
18609}
18610
18611#[gpui::test]
18612async fn test_toggle_deletion_hunk_at_start_of_file(
18613    executor: BackgroundExecutor,
18614    cx: &mut TestAppContext,
18615) {
18616    init_test(cx, |_| {});
18617    let mut cx = EditorTestContext::new(cx).await;
18618
18619    let diff_base = r#"
18620        a
18621        b
18622        c
18623        "#
18624    .unindent();
18625
18626    cx.set_state(
18627        &r#"
18628        ˇb
18629        c
18630        "#
18631        .unindent(),
18632    );
18633    cx.set_head_text(&diff_base);
18634    cx.update_editor(|editor, window, cx| {
18635        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18636    });
18637    executor.run_until_parked();
18638
18639    let hunk_expanded = r#"
18640        - a
18641          ˇb
18642          c
18643        "#
18644    .unindent();
18645
18646    cx.assert_state_with_diff(hunk_expanded.clone());
18647
18648    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18649        let snapshot = editor.snapshot(window, cx);
18650        let hunks = editor
18651            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18652            .collect::<Vec<_>>();
18653        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18654        let buffer_id = hunks[0].buffer_id;
18655        hunks
18656            .into_iter()
18657            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18658            .collect::<Vec<_>>()
18659    });
18660    assert_eq!(hunk_ranges.len(), 1);
18661
18662    cx.update_editor(|editor, _, cx| {
18663        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18664    });
18665    executor.run_until_parked();
18666
18667    let hunk_collapsed = r#"
18668          ˇb
18669          c
18670        "#
18671    .unindent();
18672
18673    cx.assert_state_with_diff(hunk_collapsed);
18674
18675    cx.update_editor(|editor, _, cx| {
18676        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18677    });
18678    executor.run_until_parked();
18679
18680    cx.assert_state_with_diff(hunk_expanded.clone());
18681}
18682
18683#[gpui::test]
18684async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18685    init_test(cx, |_| {});
18686
18687    let fs = FakeFs::new(cx.executor());
18688    fs.insert_tree(
18689        path!("/test"),
18690        json!({
18691            ".git": {},
18692            "file-1": "ONE\n",
18693            "file-2": "TWO\n",
18694            "file-3": "THREE\n",
18695        }),
18696    )
18697    .await;
18698
18699    fs.set_head_for_repo(
18700        path!("/test/.git").as_ref(),
18701        &[
18702            ("file-1".into(), "one\n".into()),
18703            ("file-2".into(), "two\n".into()),
18704            ("file-3".into(), "three\n".into()),
18705        ],
18706        "deadbeef",
18707    );
18708
18709    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18710    let mut buffers = vec![];
18711    for i in 1..=3 {
18712        let buffer = project
18713            .update(cx, |project, cx| {
18714                let path = format!(path!("/test/file-{}"), i);
18715                project.open_local_buffer(path, cx)
18716            })
18717            .await
18718            .unwrap();
18719        buffers.push(buffer);
18720    }
18721
18722    let multibuffer = cx.new(|cx| {
18723        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18724        multibuffer.set_all_diff_hunks_expanded(cx);
18725        for buffer in &buffers {
18726            let snapshot = buffer.read(cx).snapshot();
18727            multibuffer.set_excerpts_for_path(
18728                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18729                buffer.clone(),
18730                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18731                DEFAULT_MULTIBUFFER_CONTEXT,
18732                cx,
18733            );
18734        }
18735        multibuffer
18736    });
18737
18738    let editor = cx.add_window(|window, cx| {
18739        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18740    });
18741    cx.run_until_parked();
18742
18743    let snapshot = editor
18744        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18745        .unwrap();
18746    let hunks = snapshot
18747        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18748        .map(|hunk| match hunk {
18749            DisplayDiffHunk::Unfolded {
18750                display_row_range, ..
18751            } => display_row_range,
18752            DisplayDiffHunk::Folded { .. } => unreachable!(),
18753        })
18754        .collect::<Vec<_>>();
18755    assert_eq!(
18756        hunks,
18757        [
18758            DisplayRow(2)..DisplayRow(4),
18759            DisplayRow(7)..DisplayRow(9),
18760            DisplayRow(12)..DisplayRow(14),
18761        ]
18762    );
18763}
18764
18765#[gpui::test]
18766async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18767    init_test(cx, |_| {});
18768
18769    let mut cx = EditorTestContext::new(cx).await;
18770    cx.set_head_text(indoc! { "
18771        one
18772        two
18773        three
18774        four
18775        five
18776        "
18777    });
18778    cx.set_index_text(indoc! { "
18779        one
18780        two
18781        three
18782        four
18783        five
18784        "
18785    });
18786    cx.set_state(indoc! {"
18787        one
18788        TWO
18789        ˇTHREE
18790        FOUR
18791        five
18792    "});
18793    cx.run_until_parked();
18794    cx.update_editor(|editor, window, cx| {
18795        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18796    });
18797    cx.run_until_parked();
18798    cx.assert_index_text(Some(indoc! {"
18799        one
18800        TWO
18801        THREE
18802        FOUR
18803        five
18804    "}));
18805    cx.set_state(indoc! { "
18806        one
18807        TWO
18808        ˇTHREE-HUNDRED
18809        FOUR
18810        five
18811    "});
18812    cx.run_until_parked();
18813    cx.update_editor(|editor, window, cx| {
18814        let snapshot = editor.snapshot(window, cx);
18815        let hunks = editor
18816            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18817            .collect::<Vec<_>>();
18818        assert_eq!(hunks.len(), 1);
18819        assert_eq!(
18820            hunks[0].status(),
18821            DiffHunkStatus {
18822                kind: DiffHunkStatusKind::Modified,
18823                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18824            }
18825        );
18826
18827        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18828    });
18829    cx.run_until_parked();
18830    cx.assert_index_text(Some(indoc! {"
18831        one
18832        TWO
18833        THREE-HUNDRED
18834        FOUR
18835        five
18836    "}));
18837}
18838
18839#[gpui::test]
18840fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18841    init_test(cx, |_| {});
18842
18843    let editor = cx.add_window(|window, cx| {
18844        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18845        build_editor(buffer, window, cx)
18846    });
18847
18848    let render_args = Arc::new(Mutex::new(None));
18849    let snapshot = editor
18850        .update(cx, |editor, window, cx| {
18851            let snapshot = editor.buffer().read(cx).snapshot(cx);
18852            let range =
18853                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18854
18855            struct RenderArgs {
18856                row: MultiBufferRow,
18857                folded: bool,
18858                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18859            }
18860
18861            let crease = Crease::inline(
18862                range,
18863                FoldPlaceholder::test(),
18864                {
18865                    let toggle_callback = render_args.clone();
18866                    move |row, folded, callback, _window, _cx| {
18867                        *toggle_callback.lock() = Some(RenderArgs {
18868                            row,
18869                            folded,
18870                            callback,
18871                        });
18872                        div()
18873                    }
18874                },
18875                |_row, _folded, _window, _cx| div(),
18876            );
18877
18878            editor.insert_creases(Some(crease), cx);
18879            let snapshot = editor.snapshot(window, cx);
18880            let _div = snapshot.render_crease_toggle(
18881                MultiBufferRow(1),
18882                false,
18883                cx.entity().clone(),
18884                window,
18885                cx,
18886            );
18887            snapshot
18888        })
18889        .unwrap();
18890
18891    let render_args = render_args.lock().take().unwrap();
18892    assert_eq!(render_args.row, MultiBufferRow(1));
18893    assert!(!render_args.folded);
18894    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18895
18896    cx.update_window(*editor, |_, window, cx| {
18897        (render_args.callback)(true, window, cx)
18898    })
18899    .unwrap();
18900    let snapshot = editor
18901        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18902        .unwrap();
18903    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18904
18905    cx.update_window(*editor, |_, window, cx| {
18906        (render_args.callback)(false, window, cx)
18907    })
18908    .unwrap();
18909    let snapshot = editor
18910        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18911        .unwrap();
18912    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18913}
18914
18915#[gpui::test]
18916async fn test_input_text(cx: &mut TestAppContext) {
18917    init_test(cx, |_| {});
18918    let mut cx = EditorTestContext::new(cx).await;
18919
18920    cx.set_state(
18921        &r#"ˇone
18922        two
18923
18924        three
18925        fourˇ
18926        five
18927
18928        siˇx"#
18929            .unindent(),
18930    );
18931
18932    cx.dispatch_action(HandleInput(String::new()));
18933    cx.assert_editor_state(
18934        &r#"ˇone
18935        two
18936
18937        three
18938        fourˇ
18939        five
18940
18941        siˇx"#
18942            .unindent(),
18943    );
18944
18945    cx.dispatch_action(HandleInput("AAAA".to_string()));
18946    cx.assert_editor_state(
18947        &r#"AAAAˇone
18948        two
18949
18950        three
18951        fourAAAAˇ
18952        five
18953
18954        siAAAAˇx"#
18955            .unindent(),
18956    );
18957}
18958
18959#[gpui::test]
18960async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18961    init_test(cx, |_| {});
18962
18963    let mut cx = EditorTestContext::new(cx).await;
18964    cx.set_state(
18965        r#"let foo = 1;
18966let foo = 2;
18967let foo = 3;
18968let fooˇ = 4;
18969let foo = 5;
18970let foo = 6;
18971let foo = 7;
18972let foo = 8;
18973let foo = 9;
18974let foo = 10;
18975let foo = 11;
18976let foo = 12;
18977let foo = 13;
18978let foo = 14;
18979let foo = 15;"#,
18980    );
18981
18982    cx.update_editor(|e, window, cx| {
18983        assert_eq!(
18984            e.next_scroll_position,
18985            NextScrollCursorCenterTopBottom::Center,
18986            "Default next scroll direction is center",
18987        );
18988
18989        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18990        assert_eq!(
18991            e.next_scroll_position,
18992            NextScrollCursorCenterTopBottom::Top,
18993            "After center, next scroll direction should be top",
18994        );
18995
18996        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18997        assert_eq!(
18998            e.next_scroll_position,
18999            NextScrollCursorCenterTopBottom::Bottom,
19000            "After top, next scroll direction should be bottom",
19001        );
19002
19003        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19004        assert_eq!(
19005            e.next_scroll_position,
19006            NextScrollCursorCenterTopBottom::Center,
19007            "After bottom, scrolling should start over",
19008        );
19009
19010        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19011        assert_eq!(
19012            e.next_scroll_position,
19013            NextScrollCursorCenterTopBottom::Top,
19014            "Scrolling continues if retriggered fast enough"
19015        );
19016    });
19017
19018    cx.executor()
19019        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19020    cx.executor().run_until_parked();
19021    cx.update_editor(|e, _, _| {
19022        assert_eq!(
19023            e.next_scroll_position,
19024            NextScrollCursorCenterTopBottom::Center,
19025            "If scrolling is not triggered fast enough, it should reset"
19026        );
19027    });
19028}
19029
19030#[gpui::test]
19031async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19032    init_test(cx, |_| {});
19033    let mut cx = EditorLspTestContext::new_rust(
19034        lsp::ServerCapabilities {
19035            definition_provider: Some(lsp::OneOf::Left(true)),
19036            references_provider: Some(lsp::OneOf::Left(true)),
19037            ..lsp::ServerCapabilities::default()
19038        },
19039        cx,
19040    )
19041    .await;
19042
19043    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19044        let go_to_definition = cx
19045            .lsp
19046            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19047                move |params, _| async move {
19048                    if empty_go_to_definition {
19049                        Ok(None)
19050                    } else {
19051                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19052                            uri: params.text_document_position_params.text_document.uri,
19053                            range: lsp::Range::new(
19054                                lsp::Position::new(4, 3),
19055                                lsp::Position::new(4, 6),
19056                            ),
19057                        })))
19058                    }
19059                },
19060            );
19061        let references = cx
19062            .lsp
19063            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19064                Ok(Some(vec![lsp::Location {
19065                    uri: params.text_document_position.text_document.uri,
19066                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19067                }]))
19068            });
19069        (go_to_definition, references)
19070    };
19071
19072    cx.set_state(
19073        &r#"fn one() {
19074            let mut a = ˇtwo();
19075        }
19076
19077        fn two() {}"#
19078            .unindent(),
19079    );
19080    set_up_lsp_handlers(false, &mut cx);
19081    let navigated = cx
19082        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19083        .await
19084        .expect("Failed to navigate to definition");
19085    assert_eq!(
19086        navigated,
19087        Navigated::Yes,
19088        "Should have navigated to definition from the GetDefinition response"
19089    );
19090    cx.assert_editor_state(
19091        &r#"fn one() {
19092            let mut a = two();
19093        }
19094
19095        fn «twoˇ»() {}"#
19096            .unindent(),
19097    );
19098
19099    let editors = cx.update_workspace(|workspace, _, cx| {
19100        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19101    });
19102    cx.update_editor(|_, _, test_editor_cx| {
19103        assert_eq!(
19104            editors.len(),
19105            1,
19106            "Initially, only one, test, editor should be open in the workspace"
19107        );
19108        assert_eq!(
19109            test_editor_cx.entity(),
19110            editors.last().expect("Asserted len is 1").clone()
19111        );
19112    });
19113
19114    set_up_lsp_handlers(true, &mut cx);
19115    let navigated = cx
19116        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19117        .await
19118        .expect("Failed to navigate to lookup references");
19119    assert_eq!(
19120        navigated,
19121        Navigated::Yes,
19122        "Should have navigated to references as a fallback after empty GoToDefinition response"
19123    );
19124    // We should not change the selections in the existing file,
19125    // if opening another milti buffer with the references
19126    cx.assert_editor_state(
19127        &r#"fn one() {
19128            let mut a = two();
19129        }
19130
19131        fn «twoˇ»() {}"#
19132            .unindent(),
19133    );
19134    let editors = cx.update_workspace(|workspace, _, cx| {
19135        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19136    });
19137    cx.update_editor(|_, _, test_editor_cx| {
19138        assert_eq!(
19139            editors.len(),
19140            2,
19141            "After falling back to references search, we open a new editor with the results"
19142        );
19143        let references_fallback_text = editors
19144            .into_iter()
19145            .find(|new_editor| *new_editor != test_editor_cx.entity())
19146            .expect("Should have one non-test editor now")
19147            .read(test_editor_cx)
19148            .text(test_editor_cx);
19149        assert_eq!(
19150            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19151            "Should use the range from the references response and not the GoToDefinition one"
19152        );
19153    });
19154}
19155
19156#[gpui::test]
19157async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19158    init_test(cx, |_| {});
19159    cx.update(|cx| {
19160        let mut editor_settings = EditorSettings::get_global(cx).clone();
19161        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19162        EditorSettings::override_global(editor_settings, cx);
19163    });
19164    let mut cx = EditorLspTestContext::new_rust(
19165        lsp::ServerCapabilities {
19166            definition_provider: Some(lsp::OneOf::Left(true)),
19167            references_provider: Some(lsp::OneOf::Left(true)),
19168            ..lsp::ServerCapabilities::default()
19169        },
19170        cx,
19171    )
19172    .await;
19173    let original_state = r#"fn one() {
19174        let mut a = ˇtwo();
19175    }
19176
19177    fn two() {}"#
19178        .unindent();
19179    cx.set_state(&original_state);
19180
19181    let mut go_to_definition = cx
19182        .lsp
19183        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19184            move |_, _| async move { Ok(None) },
19185        );
19186    let _references = cx
19187        .lsp
19188        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19189            panic!("Should not call for references with no go to definition fallback")
19190        });
19191
19192    let navigated = cx
19193        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19194        .await
19195        .expect("Failed to navigate to lookup references");
19196    go_to_definition
19197        .next()
19198        .await
19199        .expect("Should have called the go_to_definition handler");
19200
19201    assert_eq!(
19202        navigated,
19203        Navigated::No,
19204        "Should have navigated to references as a fallback after empty GoToDefinition response"
19205    );
19206    cx.assert_editor_state(&original_state);
19207    let editors = cx.update_workspace(|workspace, _, cx| {
19208        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19209    });
19210    cx.update_editor(|_, _, _| {
19211        assert_eq!(
19212            editors.len(),
19213            1,
19214            "After unsuccessful fallback, no other editor should have been opened"
19215        );
19216    });
19217}
19218
19219#[gpui::test]
19220async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19221    init_test(cx, |_| {});
19222
19223    let language = Arc::new(Language::new(
19224        LanguageConfig::default(),
19225        Some(tree_sitter_rust::LANGUAGE.into()),
19226    ));
19227
19228    let text = r#"
19229        #[cfg(test)]
19230        mod tests() {
19231            #[test]
19232            fn runnable_1() {
19233                let a = 1;
19234            }
19235
19236            #[test]
19237            fn runnable_2() {
19238                let a = 1;
19239                let b = 2;
19240            }
19241        }
19242    "#
19243    .unindent();
19244
19245    let fs = FakeFs::new(cx.executor());
19246    fs.insert_file("/file.rs", Default::default()).await;
19247
19248    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19249    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19250    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19251    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19252    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19253
19254    let editor = cx.new_window_entity(|window, cx| {
19255        Editor::new(
19256            EditorMode::full(),
19257            multi_buffer,
19258            Some(project.clone()),
19259            window,
19260            cx,
19261        )
19262    });
19263
19264    editor.update_in(cx, |editor, window, cx| {
19265        let snapshot = editor.buffer().read(cx).snapshot(cx);
19266        editor.tasks.insert(
19267            (buffer.read(cx).remote_id(), 3),
19268            RunnableTasks {
19269                templates: vec![],
19270                offset: snapshot.anchor_before(43),
19271                column: 0,
19272                extra_variables: HashMap::default(),
19273                context_range: BufferOffset(43)..BufferOffset(85),
19274            },
19275        );
19276        editor.tasks.insert(
19277            (buffer.read(cx).remote_id(), 8),
19278            RunnableTasks {
19279                templates: vec![],
19280                offset: snapshot.anchor_before(86),
19281                column: 0,
19282                extra_variables: HashMap::default(),
19283                context_range: BufferOffset(86)..BufferOffset(191),
19284            },
19285        );
19286
19287        // Test finding task when cursor is inside function body
19288        editor.change_selections(None, window, cx, |s| {
19289            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19290        });
19291        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19292        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19293
19294        // Test finding task when cursor is on function name
19295        editor.change_selections(None, window, cx, |s| {
19296            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19297        });
19298        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19299        assert_eq!(row, 8, "Should find task when cursor is on function name");
19300    });
19301}
19302
19303#[gpui::test]
19304async fn test_folding_buffers(cx: &mut TestAppContext) {
19305    init_test(cx, |_| {});
19306
19307    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19308    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19309    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19310
19311    let fs = FakeFs::new(cx.executor());
19312    fs.insert_tree(
19313        path!("/a"),
19314        json!({
19315            "first.rs": sample_text_1,
19316            "second.rs": sample_text_2,
19317            "third.rs": sample_text_3,
19318        }),
19319    )
19320    .await;
19321    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19322    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19323    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19324    let worktree = project.update(cx, |project, cx| {
19325        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19326        assert_eq!(worktrees.len(), 1);
19327        worktrees.pop().unwrap()
19328    });
19329    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19330
19331    let buffer_1 = project
19332        .update(cx, |project, cx| {
19333            project.open_buffer((worktree_id, "first.rs"), cx)
19334        })
19335        .await
19336        .unwrap();
19337    let buffer_2 = project
19338        .update(cx, |project, cx| {
19339            project.open_buffer((worktree_id, "second.rs"), cx)
19340        })
19341        .await
19342        .unwrap();
19343    let buffer_3 = project
19344        .update(cx, |project, cx| {
19345            project.open_buffer((worktree_id, "third.rs"), cx)
19346        })
19347        .await
19348        .unwrap();
19349
19350    let multi_buffer = cx.new(|cx| {
19351        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19352        multi_buffer.push_excerpts(
19353            buffer_1.clone(),
19354            [
19355                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19356                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19357                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19358            ],
19359            cx,
19360        );
19361        multi_buffer.push_excerpts(
19362            buffer_2.clone(),
19363            [
19364                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19365                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19366                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19367            ],
19368            cx,
19369        );
19370        multi_buffer.push_excerpts(
19371            buffer_3.clone(),
19372            [
19373                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19374                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19375                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19376            ],
19377            cx,
19378        );
19379        multi_buffer
19380    });
19381    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19382        Editor::new(
19383            EditorMode::full(),
19384            multi_buffer.clone(),
19385            Some(project.clone()),
19386            window,
19387            cx,
19388        )
19389    });
19390
19391    assert_eq!(
19392        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19393        "\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",
19394    );
19395
19396    multi_buffer_editor.update(cx, |editor, cx| {
19397        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19398    });
19399    assert_eq!(
19400        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19401        "\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",
19402        "After folding the first buffer, its text should not be displayed"
19403    );
19404
19405    multi_buffer_editor.update(cx, |editor, cx| {
19406        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19407    });
19408    assert_eq!(
19409        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19410        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19411        "After folding the second buffer, its text should not be displayed"
19412    );
19413
19414    multi_buffer_editor.update(cx, |editor, cx| {
19415        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19416    });
19417    assert_eq!(
19418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19419        "\n\n\n\n\n",
19420        "After folding the third buffer, its text should not be displayed"
19421    );
19422
19423    // Emulate selection inside the fold logic, that should work
19424    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19425        editor
19426            .snapshot(window, cx)
19427            .next_line_boundary(Point::new(0, 4));
19428    });
19429
19430    multi_buffer_editor.update(cx, |editor, cx| {
19431        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19432    });
19433    assert_eq!(
19434        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19435        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19436        "After unfolding the second buffer, its text should be displayed"
19437    );
19438
19439    // Typing inside of buffer 1 causes that buffer to be unfolded.
19440    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19441        assert_eq!(
19442            multi_buffer
19443                .read(cx)
19444                .snapshot(cx)
19445                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19446                .collect::<String>(),
19447            "bbbb"
19448        );
19449        editor.change_selections(None, window, cx, |selections| {
19450            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19451        });
19452        editor.handle_input("B", window, cx);
19453    });
19454
19455    assert_eq!(
19456        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19457        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19458        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19459    );
19460
19461    multi_buffer_editor.update(cx, |editor, cx| {
19462        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19463    });
19464    assert_eq!(
19465        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19466        "\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",
19467        "After unfolding the all buffers, all original text should be displayed"
19468    );
19469}
19470
19471#[gpui::test]
19472async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19473    init_test(cx, |_| {});
19474
19475    let sample_text_1 = "1111\n2222\n3333".to_string();
19476    let sample_text_2 = "4444\n5555\n6666".to_string();
19477    let sample_text_3 = "7777\n8888\n9999".to_string();
19478
19479    let fs = FakeFs::new(cx.executor());
19480    fs.insert_tree(
19481        path!("/a"),
19482        json!({
19483            "first.rs": sample_text_1,
19484            "second.rs": sample_text_2,
19485            "third.rs": sample_text_3,
19486        }),
19487    )
19488    .await;
19489    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19490    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19492    let worktree = project.update(cx, |project, cx| {
19493        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19494        assert_eq!(worktrees.len(), 1);
19495        worktrees.pop().unwrap()
19496    });
19497    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19498
19499    let buffer_1 = project
19500        .update(cx, |project, cx| {
19501            project.open_buffer((worktree_id, "first.rs"), cx)
19502        })
19503        .await
19504        .unwrap();
19505    let buffer_2 = project
19506        .update(cx, |project, cx| {
19507            project.open_buffer((worktree_id, "second.rs"), cx)
19508        })
19509        .await
19510        .unwrap();
19511    let buffer_3 = project
19512        .update(cx, |project, cx| {
19513            project.open_buffer((worktree_id, "third.rs"), cx)
19514        })
19515        .await
19516        .unwrap();
19517
19518    let multi_buffer = cx.new(|cx| {
19519        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19520        multi_buffer.push_excerpts(
19521            buffer_1.clone(),
19522            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19523            cx,
19524        );
19525        multi_buffer.push_excerpts(
19526            buffer_2.clone(),
19527            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19528            cx,
19529        );
19530        multi_buffer.push_excerpts(
19531            buffer_3.clone(),
19532            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19533            cx,
19534        );
19535        multi_buffer
19536    });
19537
19538    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19539        Editor::new(
19540            EditorMode::full(),
19541            multi_buffer,
19542            Some(project.clone()),
19543            window,
19544            cx,
19545        )
19546    });
19547
19548    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19549    assert_eq!(
19550        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19551        full_text,
19552    );
19553
19554    multi_buffer_editor.update(cx, |editor, cx| {
19555        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19556    });
19557    assert_eq!(
19558        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19559        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19560        "After folding the first buffer, its text should not be displayed"
19561    );
19562
19563    multi_buffer_editor.update(cx, |editor, cx| {
19564        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19565    });
19566
19567    assert_eq!(
19568        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19569        "\n\n\n\n\n\n7777\n8888\n9999",
19570        "After folding the second buffer, its text should not be displayed"
19571    );
19572
19573    multi_buffer_editor.update(cx, |editor, cx| {
19574        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19575    });
19576    assert_eq!(
19577        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19578        "\n\n\n\n\n",
19579        "After folding the third buffer, its text should not be displayed"
19580    );
19581
19582    multi_buffer_editor.update(cx, |editor, cx| {
19583        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19584    });
19585    assert_eq!(
19586        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19587        "\n\n\n\n4444\n5555\n6666\n\n",
19588        "After unfolding the second buffer, its text should be displayed"
19589    );
19590
19591    multi_buffer_editor.update(cx, |editor, cx| {
19592        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19593    });
19594    assert_eq!(
19595        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19596        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19597        "After unfolding the first buffer, its text should be displayed"
19598    );
19599
19600    multi_buffer_editor.update(cx, |editor, cx| {
19601        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19602    });
19603    assert_eq!(
19604        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19605        full_text,
19606        "After unfolding all buffers, all original text should be displayed"
19607    );
19608}
19609
19610#[gpui::test]
19611async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19612    init_test(cx, |_| {});
19613
19614    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19615
19616    let fs = FakeFs::new(cx.executor());
19617    fs.insert_tree(
19618        path!("/a"),
19619        json!({
19620            "main.rs": sample_text,
19621        }),
19622    )
19623    .await;
19624    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19625    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19626    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19627    let worktree = project.update(cx, |project, cx| {
19628        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19629        assert_eq!(worktrees.len(), 1);
19630        worktrees.pop().unwrap()
19631    });
19632    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19633
19634    let buffer_1 = project
19635        .update(cx, |project, cx| {
19636            project.open_buffer((worktree_id, "main.rs"), cx)
19637        })
19638        .await
19639        .unwrap();
19640
19641    let multi_buffer = cx.new(|cx| {
19642        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19643        multi_buffer.push_excerpts(
19644            buffer_1.clone(),
19645            [ExcerptRange::new(
19646                Point::new(0, 0)
19647                    ..Point::new(
19648                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19649                        0,
19650                    ),
19651            )],
19652            cx,
19653        );
19654        multi_buffer
19655    });
19656    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19657        Editor::new(
19658            EditorMode::full(),
19659            multi_buffer,
19660            Some(project.clone()),
19661            window,
19662            cx,
19663        )
19664    });
19665
19666    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19667    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19668        enum TestHighlight {}
19669        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19670        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19671        editor.highlight_text::<TestHighlight>(
19672            vec![highlight_range.clone()],
19673            HighlightStyle::color(Hsla::green()),
19674            cx,
19675        );
19676        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19677    });
19678
19679    let full_text = format!("\n\n{sample_text}");
19680    assert_eq!(
19681        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19682        full_text,
19683    );
19684}
19685
19686#[gpui::test]
19687async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19688    init_test(cx, |_| {});
19689    cx.update(|cx| {
19690        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19691            "keymaps/default-linux.json",
19692            cx,
19693        )
19694        .unwrap();
19695        cx.bind_keys(default_key_bindings);
19696    });
19697
19698    let (editor, cx) = cx.add_window_view(|window, cx| {
19699        let multi_buffer = MultiBuffer::build_multi(
19700            [
19701                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19702                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19703                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19704                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19705            ],
19706            cx,
19707        );
19708        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19709
19710        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19711        // fold all but the second buffer, so that we test navigating between two
19712        // adjacent folded buffers, as well as folded buffers at the start and
19713        // end the multibuffer
19714        editor.fold_buffer(buffer_ids[0], cx);
19715        editor.fold_buffer(buffer_ids[2], cx);
19716        editor.fold_buffer(buffer_ids[3], cx);
19717
19718        editor
19719    });
19720    cx.simulate_resize(size(px(1000.), px(1000.)));
19721
19722    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19723    cx.assert_excerpts_with_selections(indoc! {"
19724        [EXCERPT]
19725        ˇ[FOLDED]
19726        [EXCERPT]
19727        a1
19728        b1
19729        [EXCERPT]
19730        [FOLDED]
19731        [EXCERPT]
19732        [FOLDED]
19733        "
19734    });
19735    cx.simulate_keystroke("down");
19736    cx.assert_excerpts_with_selections(indoc! {"
19737        [EXCERPT]
19738        [FOLDED]
19739        [EXCERPT]
19740        ˇa1
19741        b1
19742        [EXCERPT]
19743        [FOLDED]
19744        [EXCERPT]
19745        [FOLDED]
19746        "
19747    });
19748    cx.simulate_keystroke("down");
19749    cx.assert_excerpts_with_selections(indoc! {"
19750        [EXCERPT]
19751        [FOLDED]
19752        [EXCERPT]
19753        a1
19754        ˇb1
19755        [EXCERPT]
19756        [FOLDED]
19757        [EXCERPT]
19758        [FOLDED]
19759        "
19760    });
19761    cx.simulate_keystroke("down");
19762    cx.assert_excerpts_with_selections(indoc! {"
19763        [EXCERPT]
19764        [FOLDED]
19765        [EXCERPT]
19766        a1
19767        b1
19768        ˇ[EXCERPT]
19769        [FOLDED]
19770        [EXCERPT]
19771        [FOLDED]
19772        "
19773    });
19774    cx.simulate_keystroke("down");
19775    cx.assert_excerpts_with_selections(indoc! {"
19776        [EXCERPT]
19777        [FOLDED]
19778        [EXCERPT]
19779        a1
19780        b1
19781        [EXCERPT]
19782        ˇ[FOLDED]
19783        [EXCERPT]
19784        [FOLDED]
19785        "
19786    });
19787    for _ in 0..5 {
19788        cx.simulate_keystroke("down");
19789        cx.assert_excerpts_with_selections(indoc! {"
19790            [EXCERPT]
19791            [FOLDED]
19792            [EXCERPT]
19793            a1
19794            b1
19795            [EXCERPT]
19796            [FOLDED]
19797            [EXCERPT]
19798            ˇ[FOLDED]
19799            "
19800        });
19801    }
19802
19803    cx.simulate_keystroke("up");
19804    cx.assert_excerpts_with_selections(indoc! {"
19805        [EXCERPT]
19806        [FOLDED]
19807        [EXCERPT]
19808        a1
19809        b1
19810        [EXCERPT]
19811        ˇ[FOLDED]
19812        [EXCERPT]
19813        [FOLDED]
19814        "
19815    });
19816    cx.simulate_keystroke("up");
19817    cx.assert_excerpts_with_selections(indoc! {"
19818        [EXCERPT]
19819        [FOLDED]
19820        [EXCERPT]
19821        a1
19822        b1
19823        ˇ[EXCERPT]
19824        [FOLDED]
19825        [EXCERPT]
19826        [FOLDED]
19827        "
19828    });
19829    cx.simulate_keystroke("up");
19830    cx.assert_excerpts_with_selections(indoc! {"
19831        [EXCERPT]
19832        [FOLDED]
19833        [EXCERPT]
19834        a1
19835        ˇb1
19836        [EXCERPT]
19837        [FOLDED]
19838        [EXCERPT]
19839        [FOLDED]
19840        "
19841    });
19842    cx.simulate_keystroke("up");
19843    cx.assert_excerpts_with_selections(indoc! {"
19844        [EXCERPT]
19845        [FOLDED]
19846        [EXCERPT]
19847        ˇa1
19848        b1
19849        [EXCERPT]
19850        [FOLDED]
19851        [EXCERPT]
19852        [FOLDED]
19853        "
19854    });
19855    for _ in 0..5 {
19856        cx.simulate_keystroke("up");
19857        cx.assert_excerpts_with_selections(indoc! {"
19858            [EXCERPT]
19859            ˇ[FOLDED]
19860            [EXCERPT]
19861            a1
19862            b1
19863            [EXCERPT]
19864            [FOLDED]
19865            [EXCERPT]
19866            [FOLDED]
19867            "
19868        });
19869    }
19870}
19871
19872#[gpui::test]
19873async fn test_inline_completion_text(cx: &mut TestAppContext) {
19874    init_test(cx, |_| {});
19875
19876    // Simple insertion
19877    assert_highlighted_edits(
19878        "Hello, world!",
19879        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19880        true,
19881        cx,
19882        |highlighted_edits, cx| {
19883            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19884            assert_eq!(highlighted_edits.highlights.len(), 1);
19885            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19886            assert_eq!(
19887                highlighted_edits.highlights[0].1.background_color,
19888                Some(cx.theme().status().created_background)
19889            );
19890        },
19891    )
19892    .await;
19893
19894    // Replacement
19895    assert_highlighted_edits(
19896        "This is a test.",
19897        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19898        false,
19899        cx,
19900        |highlighted_edits, cx| {
19901            assert_eq!(highlighted_edits.text, "That is a test.");
19902            assert_eq!(highlighted_edits.highlights.len(), 1);
19903            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19904            assert_eq!(
19905                highlighted_edits.highlights[0].1.background_color,
19906                Some(cx.theme().status().created_background)
19907            );
19908        },
19909    )
19910    .await;
19911
19912    // Multiple edits
19913    assert_highlighted_edits(
19914        "Hello, world!",
19915        vec![
19916            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19917            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19918        ],
19919        false,
19920        cx,
19921        |highlighted_edits, cx| {
19922            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19923            assert_eq!(highlighted_edits.highlights.len(), 2);
19924            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19925            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19926            assert_eq!(
19927                highlighted_edits.highlights[0].1.background_color,
19928                Some(cx.theme().status().created_background)
19929            );
19930            assert_eq!(
19931                highlighted_edits.highlights[1].1.background_color,
19932                Some(cx.theme().status().created_background)
19933            );
19934        },
19935    )
19936    .await;
19937
19938    // Multiple lines with edits
19939    assert_highlighted_edits(
19940        "First line\nSecond line\nThird line\nFourth line",
19941        vec![
19942            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19943            (
19944                Point::new(2, 0)..Point::new(2, 10),
19945                "New third line".to_string(),
19946            ),
19947            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19948        ],
19949        false,
19950        cx,
19951        |highlighted_edits, cx| {
19952            assert_eq!(
19953                highlighted_edits.text,
19954                "Second modified\nNew third line\nFourth updated line"
19955            );
19956            assert_eq!(highlighted_edits.highlights.len(), 3);
19957            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19958            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19959            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19960            for highlight in &highlighted_edits.highlights {
19961                assert_eq!(
19962                    highlight.1.background_color,
19963                    Some(cx.theme().status().created_background)
19964                );
19965            }
19966        },
19967    )
19968    .await;
19969}
19970
19971#[gpui::test]
19972async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19973    init_test(cx, |_| {});
19974
19975    // Deletion
19976    assert_highlighted_edits(
19977        "Hello, world!",
19978        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19979        true,
19980        cx,
19981        |highlighted_edits, cx| {
19982            assert_eq!(highlighted_edits.text, "Hello, world!");
19983            assert_eq!(highlighted_edits.highlights.len(), 1);
19984            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19985            assert_eq!(
19986                highlighted_edits.highlights[0].1.background_color,
19987                Some(cx.theme().status().deleted_background)
19988            );
19989        },
19990    )
19991    .await;
19992
19993    // Insertion
19994    assert_highlighted_edits(
19995        "Hello, world!",
19996        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19997        true,
19998        cx,
19999        |highlighted_edits, cx| {
20000            assert_eq!(highlighted_edits.highlights.len(), 1);
20001            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20002            assert_eq!(
20003                highlighted_edits.highlights[0].1.background_color,
20004                Some(cx.theme().status().created_background)
20005            );
20006        },
20007    )
20008    .await;
20009}
20010
20011async fn assert_highlighted_edits(
20012    text: &str,
20013    edits: Vec<(Range<Point>, String)>,
20014    include_deletions: bool,
20015    cx: &mut TestAppContext,
20016    assertion_fn: impl Fn(HighlightedText, &App),
20017) {
20018    let window = cx.add_window(|window, cx| {
20019        let buffer = MultiBuffer::build_simple(text, cx);
20020        Editor::new(EditorMode::full(), buffer, None, window, cx)
20021    });
20022    let cx = &mut VisualTestContext::from_window(*window, cx);
20023
20024    let (buffer, snapshot) = window
20025        .update(cx, |editor, _window, cx| {
20026            (
20027                editor.buffer().clone(),
20028                editor.buffer().read(cx).snapshot(cx),
20029            )
20030        })
20031        .unwrap();
20032
20033    let edits = edits
20034        .into_iter()
20035        .map(|(range, edit)| {
20036            (
20037                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20038                edit,
20039            )
20040        })
20041        .collect::<Vec<_>>();
20042
20043    let text_anchor_edits = edits
20044        .clone()
20045        .into_iter()
20046        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20047        .collect::<Vec<_>>();
20048
20049    let edit_preview = window
20050        .update(cx, |_, _window, cx| {
20051            buffer
20052                .read(cx)
20053                .as_singleton()
20054                .unwrap()
20055                .read(cx)
20056                .preview_edits(text_anchor_edits.into(), cx)
20057        })
20058        .unwrap()
20059        .await;
20060
20061    cx.update(|_window, cx| {
20062        let highlighted_edits = inline_completion_edit_text(
20063            &snapshot.as_singleton().unwrap().2,
20064            &edits,
20065            &edit_preview,
20066            include_deletions,
20067            cx,
20068        );
20069        assertion_fn(highlighted_edits, cx)
20070    });
20071}
20072
20073#[track_caller]
20074fn assert_breakpoint(
20075    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20076    path: &Arc<Path>,
20077    expected: Vec<(u32, Breakpoint)>,
20078) {
20079    if expected.len() == 0usize {
20080        assert!(!breakpoints.contains_key(path), "{}", path.display());
20081    } else {
20082        let mut breakpoint = breakpoints
20083            .get(path)
20084            .unwrap()
20085            .into_iter()
20086            .map(|breakpoint| {
20087                (
20088                    breakpoint.row,
20089                    Breakpoint {
20090                        message: breakpoint.message.clone(),
20091                        state: breakpoint.state,
20092                        condition: breakpoint.condition.clone(),
20093                        hit_condition: breakpoint.hit_condition.clone(),
20094                    },
20095                )
20096            })
20097            .collect::<Vec<_>>();
20098
20099        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20100
20101        assert_eq!(expected, breakpoint);
20102    }
20103}
20104
20105fn add_log_breakpoint_at_cursor(
20106    editor: &mut Editor,
20107    log_message: &str,
20108    window: &mut Window,
20109    cx: &mut Context<Editor>,
20110) {
20111    let (anchor, bp) = editor
20112        .breakpoints_at_cursors(window, cx)
20113        .first()
20114        .and_then(|(anchor, bp)| {
20115            if let Some(bp) = bp {
20116                Some((*anchor, bp.clone()))
20117            } else {
20118                None
20119            }
20120        })
20121        .unwrap_or_else(|| {
20122            let cursor_position: Point = editor.selections.newest(cx).head();
20123
20124            let breakpoint_position = editor
20125                .snapshot(window, cx)
20126                .display_snapshot
20127                .buffer_snapshot
20128                .anchor_before(Point::new(cursor_position.row, 0));
20129
20130            (breakpoint_position, Breakpoint::new_log(&log_message))
20131        });
20132
20133    editor.edit_breakpoint_at_anchor(
20134        anchor,
20135        bp,
20136        BreakpointEditAction::EditLogMessage(log_message.into()),
20137        cx,
20138    );
20139}
20140
20141#[gpui::test]
20142async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20143    init_test(cx, |_| {});
20144
20145    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20146    let fs = FakeFs::new(cx.executor());
20147    fs.insert_tree(
20148        path!("/a"),
20149        json!({
20150            "main.rs": sample_text,
20151        }),
20152    )
20153    .await;
20154    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20155    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20156    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20157
20158    let fs = FakeFs::new(cx.executor());
20159    fs.insert_tree(
20160        path!("/a"),
20161        json!({
20162            "main.rs": sample_text,
20163        }),
20164    )
20165    .await;
20166    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20167    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20168    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20169    let worktree_id = workspace
20170        .update(cx, |workspace, _window, cx| {
20171            workspace.project().update(cx, |project, cx| {
20172                project.worktrees(cx).next().unwrap().read(cx).id()
20173            })
20174        })
20175        .unwrap();
20176
20177    let buffer = project
20178        .update(cx, |project, cx| {
20179            project.open_buffer((worktree_id, "main.rs"), cx)
20180        })
20181        .await
20182        .unwrap();
20183
20184    let (editor, cx) = cx.add_window_view(|window, cx| {
20185        Editor::new(
20186            EditorMode::full(),
20187            MultiBuffer::build_from_buffer(buffer, cx),
20188            Some(project.clone()),
20189            window,
20190            cx,
20191        )
20192    });
20193
20194    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20195    let abs_path = project.read_with(cx, |project, cx| {
20196        project
20197            .absolute_path(&project_path, cx)
20198            .map(|path_buf| Arc::from(path_buf.to_owned()))
20199            .unwrap()
20200    });
20201
20202    // assert we can add breakpoint on the first line
20203    editor.update_in(cx, |editor, window, cx| {
20204        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20205        editor.move_to_end(&MoveToEnd, window, cx);
20206        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20207    });
20208
20209    let breakpoints = editor.update(cx, |editor, cx| {
20210        editor
20211            .breakpoint_store()
20212            .as_ref()
20213            .unwrap()
20214            .read(cx)
20215            .all_source_breakpoints(cx)
20216            .clone()
20217    });
20218
20219    assert_eq!(1, breakpoints.len());
20220    assert_breakpoint(
20221        &breakpoints,
20222        &abs_path,
20223        vec![
20224            (0, Breakpoint::new_standard()),
20225            (3, Breakpoint::new_standard()),
20226        ],
20227    );
20228
20229    editor.update_in(cx, |editor, window, cx| {
20230        editor.move_to_beginning(&MoveToBeginning, window, cx);
20231        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20232    });
20233
20234    let breakpoints = editor.update(cx, |editor, cx| {
20235        editor
20236            .breakpoint_store()
20237            .as_ref()
20238            .unwrap()
20239            .read(cx)
20240            .all_source_breakpoints(cx)
20241            .clone()
20242    });
20243
20244    assert_eq!(1, breakpoints.len());
20245    assert_breakpoint(
20246        &breakpoints,
20247        &abs_path,
20248        vec![(3, Breakpoint::new_standard())],
20249    );
20250
20251    editor.update_in(cx, |editor, window, cx| {
20252        editor.move_to_end(&MoveToEnd, window, cx);
20253        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20254    });
20255
20256    let breakpoints = editor.update(cx, |editor, cx| {
20257        editor
20258            .breakpoint_store()
20259            .as_ref()
20260            .unwrap()
20261            .read(cx)
20262            .all_source_breakpoints(cx)
20263            .clone()
20264    });
20265
20266    assert_eq!(0, breakpoints.len());
20267    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20268}
20269
20270#[gpui::test]
20271async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20272    init_test(cx, |_| {});
20273
20274    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20275
20276    let fs = FakeFs::new(cx.executor());
20277    fs.insert_tree(
20278        path!("/a"),
20279        json!({
20280            "main.rs": sample_text,
20281        }),
20282    )
20283    .await;
20284    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20285    let (workspace, cx) =
20286        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20287
20288    let worktree_id = workspace.update(cx, |workspace, cx| {
20289        workspace.project().update(cx, |project, cx| {
20290            project.worktrees(cx).next().unwrap().read(cx).id()
20291        })
20292    });
20293
20294    let buffer = project
20295        .update(cx, |project, cx| {
20296            project.open_buffer((worktree_id, "main.rs"), cx)
20297        })
20298        .await
20299        .unwrap();
20300
20301    let (editor, cx) = cx.add_window_view(|window, cx| {
20302        Editor::new(
20303            EditorMode::full(),
20304            MultiBuffer::build_from_buffer(buffer, cx),
20305            Some(project.clone()),
20306            window,
20307            cx,
20308        )
20309    });
20310
20311    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20312    let abs_path = project.read_with(cx, |project, cx| {
20313        project
20314            .absolute_path(&project_path, cx)
20315            .map(|path_buf| Arc::from(path_buf.to_owned()))
20316            .unwrap()
20317    });
20318
20319    editor.update_in(cx, |editor, window, cx| {
20320        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20321    });
20322
20323    let breakpoints = editor.update(cx, |editor, cx| {
20324        editor
20325            .breakpoint_store()
20326            .as_ref()
20327            .unwrap()
20328            .read(cx)
20329            .all_source_breakpoints(cx)
20330            .clone()
20331    });
20332
20333    assert_breakpoint(
20334        &breakpoints,
20335        &abs_path,
20336        vec![(0, Breakpoint::new_log("hello world"))],
20337    );
20338
20339    // Removing a log message from a log breakpoint should remove it
20340    editor.update_in(cx, |editor, window, cx| {
20341        add_log_breakpoint_at_cursor(editor, "", window, cx);
20342    });
20343
20344    let breakpoints = editor.update(cx, |editor, cx| {
20345        editor
20346            .breakpoint_store()
20347            .as_ref()
20348            .unwrap()
20349            .read(cx)
20350            .all_source_breakpoints(cx)
20351            .clone()
20352    });
20353
20354    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20355
20356    editor.update_in(cx, |editor, window, cx| {
20357        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20358        editor.move_to_end(&MoveToEnd, window, cx);
20359        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20360        // Not adding a log message to a standard breakpoint shouldn't remove it
20361        add_log_breakpoint_at_cursor(editor, "", window, cx);
20362    });
20363
20364    let breakpoints = editor.update(cx, |editor, cx| {
20365        editor
20366            .breakpoint_store()
20367            .as_ref()
20368            .unwrap()
20369            .read(cx)
20370            .all_source_breakpoints(cx)
20371            .clone()
20372    });
20373
20374    assert_breakpoint(
20375        &breakpoints,
20376        &abs_path,
20377        vec![
20378            (0, Breakpoint::new_standard()),
20379            (3, Breakpoint::new_standard()),
20380        ],
20381    );
20382
20383    editor.update_in(cx, |editor, window, cx| {
20384        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20385    });
20386
20387    let breakpoints = editor.update(cx, |editor, cx| {
20388        editor
20389            .breakpoint_store()
20390            .as_ref()
20391            .unwrap()
20392            .read(cx)
20393            .all_source_breakpoints(cx)
20394            .clone()
20395    });
20396
20397    assert_breakpoint(
20398        &breakpoints,
20399        &abs_path,
20400        vec![
20401            (0, Breakpoint::new_standard()),
20402            (3, Breakpoint::new_log("hello world")),
20403        ],
20404    );
20405
20406    editor.update_in(cx, |editor, window, cx| {
20407        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20408    });
20409
20410    let breakpoints = editor.update(cx, |editor, cx| {
20411        editor
20412            .breakpoint_store()
20413            .as_ref()
20414            .unwrap()
20415            .read(cx)
20416            .all_source_breakpoints(cx)
20417            .clone()
20418    });
20419
20420    assert_breakpoint(
20421        &breakpoints,
20422        &abs_path,
20423        vec![
20424            (0, Breakpoint::new_standard()),
20425            (3, Breakpoint::new_log("hello Earth!!")),
20426        ],
20427    );
20428}
20429
20430/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20431/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20432/// or when breakpoints were placed out of order. This tests for a regression too
20433#[gpui::test]
20434async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20435    init_test(cx, |_| {});
20436
20437    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20438    let fs = FakeFs::new(cx.executor());
20439    fs.insert_tree(
20440        path!("/a"),
20441        json!({
20442            "main.rs": sample_text,
20443        }),
20444    )
20445    .await;
20446    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20447    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20448    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20449
20450    let fs = FakeFs::new(cx.executor());
20451    fs.insert_tree(
20452        path!("/a"),
20453        json!({
20454            "main.rs": sample_text,
20455        }),
20456    )
20457    .await;
20458    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20459    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20460    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20461    let worktree_id = workspace
20462        .update(cx, |workspace, _window, cx| {
20463            workspace.project().update(cx, |project, cx| {
20464                project.worktrees(cx).next().unwrap().read(cx).id()
20465            })
20466        })
20467        .unwrap();
20468
20469    let buffer = project
20470        .update(cx, |project, cx| {
20471            project.open_buffer((worktree_id, "main.rs"), cx)
20472        })
20473        .await
20474        .unwrap();
20475
20476    let (editor, cx) = cx.add_window_view(|window, cx| {
20477        Editor::new(
20478            EditorMode::full(),
20479            MultiBuffer::build_from_buffer(buffer, cx),
20480            Some(project.clone()),
20481            window,
20482            cx,
20483        )
20484    });
20485
20486    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20487    let abs_path = project.read_with(cx, |project, cx| {
20488        project
20489            .absolute_path(&project_path, cx)
20490            .map(|path_buf| Arc::from(path_buf.to_owned()))
20491            .unwrap()
20492    });
20493
20494    // assert we can add breakpoint on the first line
20495    editor.update_in(cx, |editor, window, cx| {
20496        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20497        editor.move_to_end(&MoveToEnd, window, cx);
20498        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20499        editor.move_up(&MoveUp, window, cx);
20500        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20501    });
20502
20503    let breakpoints = editor.update(cx, |editor, cx| {
20504        editor
20505            .breakpoint_store()
20506            .as_ref()
20507            .unwrap()
20508            .read(cx)
20509            .all_source_breakpoints(cx)
20510            .clone()
20511    });
20512
20513    assert_eq!(1, breakpoints.len());
20514    assert_breakpoint(
20515        &breakpoints,
20516        &abs_path,
20517        vec![
20518            (0, Breakpoint::new_standard()),
20519            (2, Breakpoint::new_standard()),
20520            (3, Breakpoint::new_standard()),
20521        ],
20522    );
20523
20524    editor.update_in(cx, |editor, window, cx| {
20525        editor.move_to_beginning(&MoveToBeginning, window, cx);
20526        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20527        editor.move_to_end(&MoveToEnd, window, cx);
20528        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20529        // Disabling a breakpoint that doesn't exist should do nothing
20530        editor.move_up(&MoveUp, window, cx);
20531        editor.move_up(&MoveUp, window, cx);
20532        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20533    });
20534
20535    let breakpoints = editor.update(cx, |editor, cx| {
20536        editor
20537            .breakpoint_store()
20538            .as_ref()
20539            .unwrap()
20540            .read(cx)
20541            .all_source_breakpoints(cx)
20542            .clone()
20543    });
20544
20545    let disable_breakpoint = {
20546        let mut bp = Breakpoint::new_standard();
20547        bp.state = BreakpointState::Disabled;
20548        bp
20549    };
20550
20551    assert_eq!(1, breakpoints.len());
20552    assert_breakpoint(
20553        &breakpoints,
20554        &abs_path,
20555        vec![
20556            (0, disable_breakpoint.clone()),
20557            (2, Breakpoint::new_standard()),
20558            (3, disable_breakpoint.clone()),
20559        ],
20560    );
20561
20562    editor.update_in(cx, |editor, window, cx| {
20563        editor.move_to_beginning(&MoveToBeginning, window, cx);
20564        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20565        editor.move_to_end(&MoveToEnd, window, cx);
20566        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20567        editor.move_up(&MoveUp, window, cx);
20568        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20569    });
20570
20571    let breakpoints = editor.update(cx, |editor, cx| {
20572        editor
20573            .breakpoint_store()
20574            .as_ref()
20575            .unwrap()
20576            .read(cx)
20577            .all_source_breakpoints(cx)
20578            .clone()
20579    });
20580
20581    assert_eq!(1, breakpoints.len());
20582    assert_breakpoint(
20583        &breakpoints,
20584        &abs_path,
20585        vec![
20586            (0, Breakpoint::new_standard()),
20587            (2, disable_breakpoint),
20588            (3, Breakpoint::new_standard()),
20589        ],
20590    );
20591}
20592
20593#[gpui::test]
20594async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20595    init_test(cx, |_| {});
20596    let capabilities = lsp::ServerCapabilities {
20597        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20598            prepare_provider: Some(true),
20599            work_done_progress_options: Default::default(),
20600        })),
20601        ..Default::default()
20602    };
20603    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20604
20605    cx.set_state(indoc! {"
20606        struct Fˇoo {}
20607    "});
20608
20609    cx.update_editor(|editor, _, cx| {
20610        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20611        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20612        editor.highlight_background::<DocumentHighlightRead>(
20613            &[highlight_range],
20614            |theme| theme.colors().editor_document_highlight_read_background,
20615            cx,
20616        );
20617    });
20618
20619    let mut prepare_rename_handler = cx
20620        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20621            move |_, _, _| async move {
20622                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20623                    start: lsp::Position {
20624                        line: 0,
20625                        character: 7,
20626                    },
20627                    end: lsp::Position {
20628                        line: 0,
20629                        character: 10,
20630                    },
20631                })))
20632            },
20633        );
20634    let prepare_rename_task = cx
20635        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20636        .expect("Prepare rename was not started");
20637    prepare_rename_handler.next().await.unwrap();
20638    prepare_rename_task.await.expect("Prepare rename failed");
20639
20640    let mut rename_handler =
20641        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20642            let edit = lsp::TextEdit {
20643                range: lsp::Range {
20644                    start: lsp::Position {
20645                        line: 0,
20646                        character: 7,
20647                    },
20648                    end: lsp::Position {
20649                        line: 0,
20650                        character: 10,
20651                    },
20652                },
20653                new_text: "FooRenamed".to_string(),
20654            };
20655            Ok(Some(lsp::WorkspaceEdit::new(
20656                // Specify the same edit twice
20657                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20658            )))
20659        });
20660    let rename_task = cx
20661        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20662        .expect("Confirm rename was not started");
20663    rename_handler.next().await.unwrap();
20664    rename_task.await.expect("Confirm rename failed");
20665    cx.run_until_parked();
20666
20667    // Despite two edits, only one is actually applied as those are identical
20668    cx.assert_editor_state(indoc! {"
20669        struct FooRenamedˇ {}
20670    "});
20671}
20672
20673#[gpui::test]
20674async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20675    init_test(cx, |_| {});
20676    // These capabilities indicate that the server does not support prepare rename.
20677    let capabilities = lsp::ServerCapabilities {
20678        rename_provider: Some(lsp::OneOf::Left(true)),
20679        ..Default::default()
20680    };
20681    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20682
20683    cx.set_state(indoc! {"
20684        struct Fˇoo {}
20685    "});
20686
20687    cx.update_editor(|editor, _window, cx| {
20688        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20689        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20690        editor.highlight_background::<DocumentHighlightRead>(
20691            &[highlight_range],
20692            |theme| theme.colors().editor_document_highlight_read_background,
20693            cx,
20694        );
20695    });
20696
20697    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20698        .expect("Prepare rename was not started")
20699        .await
20700        .expect("Prepare rename failed");
20701
20702    let mut rename_handler =
20703        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20704            let edit = lsp::TextEdit {
20705                range: lsp::Range {
20706                    start: lsp::Position {
20707                        line: 0,
20708                        character: 7,
20709                    },
20710                    end: lsp::Position {
20711                        line: 0,
20712                        character: 10,
20713                    },
20714                },
20715                new_text: "FooRenamed".to_string(),
20716            };
20717            Ok(Some(lsp::WorkspaceEdit::new(
20718                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20719            )))
20720        });
20721    let rename_task = cx
20722        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20723        .expect("Confirm rename was not started");
20724    rename_handler.next().await.unwrap();
20725    rename_task.await.expect("Confirm rename failed");
20726    cx.run_until_parked();
20727
20728    // Correct range is renamed, as `surrounding_word` is used to find it.
20729    cx.assert_editor_state(indoc! {"
20730        struct FooRenamedˇ {}
20731    "});
20732}
20733
20734#[gpui::test]
20735async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20736    init_test(cx, |_| {});
20737    let mut cx = EditorTestContext::new(cx).await;
20738
20739    let language = Arc::new(
20740        Language::new(
20741            LanguageConfig::default(),
20742            Some(tree_sitter_html::LANGUAGE.into()),
20743        )
20744        .with_brackets_query(
20745            r#"
20746            ("<" @open "/>" @close)
20747            ("</" @open ">" @close)
20748            ("<" @open ">" @close)
20749            ("\"" @open "\"" @close)
20750            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20751        "#,
20752        )
20753        .unwrap(),
20754    );
20755    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20756
20757    cx.set_state(indoc! {"
20758        <span>ˇ</span>
20759    "});
20760    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20761    cx.assert_editor_state(indoc! {"
20762        <span>
20763        ˇ
20764        </span>
20765    "});
20766
20767    cx.set_state(indoc! {"
20768        <span><span></span>ˇ</span>
20769    "});
20770    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20771    cx.assert_editor_state(indoc! {"
20772        <span><span></span>
20773        ˇ</span>
20774    "});
20775
20776    cx.set_state(indoc! {"
20777        <span>ˇ
20778        </span>
20779    "});
20780    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20781    cx.assert_editor_state(indoc! {"
20782        <span>
20783        ˇ
20784        </span>
20785    "});
20786}
20787
20788#[gpui::test(iterations = 10)]
20789async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20790    init_test(cx, |_| {});
20791
20792    let fs = FakeFs::new(cx.executor());
20793    fs.insert_tree(
20794        path!("/dir"),
20795        json!({
20796            "a.ts": "a",
20797        }),
20798    )
20799    .await;
20800
20801    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20803    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20804
20805    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20806    language_registry.add(Arc::new(Language::new(
20807        LanguageConfig {
20808            name: "TypeScript".into(),
20809            matcher: LanguageMatcher {
20810                path_suffixes: vec!["ts".to_string()],
20811                ..Default::default()
20812            },
20813            ..Default::default()
20814        },
20815        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20816    )));
20817    let mut fake_language_servers = language_registry.register_fake_lsp(
20818        "TypeScript",
20819        FakeLspAdapter {
20820            capabilities: lsp::ServerCapabilities {
20821                code_lens_provider: Some(lsp::CodeLensOptions {
20822                    resolve_provider: Some(true),
20823                }),
20824                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20825                    commands: vec!["_the/command".to_string()],
20826                    ..lsp::ExecuteCommandOptions::default()
20827                }),
20828                ..lsp::ServerCapabilities::default()
20829            },
20830            ..FakeLspAdapter::default()
20831        },
20832    );
20833
20834    let (buffer, _handle) = project
20835        .update(cx, |p, cx| {
20836            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20837        })
20838        .await
20839        .unwrap();
20840    cx.executor().run_until_parked();
20841
20842    let fake_server = fake_language_servers.next().await.unwrap();
20843
20844    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20845    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20846    drop(buffer_snapshot);
20847    let actions = cx
20848        .update_window(*workspace, |_, window, cx| {
20849            project.code_actions(&buffer, anchor..anchor, window, cx)
20850        })
20851        .unwrap();
20852
20853    fake_server
20854        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20855            Ok(Some(vec![
20856                lsp::CodeLens {
20857                    range: lsp::Range::default(),
20858                    command: Some(lsp::Command {
20859                        title: "Code lens command".to_owned(),
20860                        command: "_the/command".to_owned(),
20861                        arguments: None,
20862                    }),
20863                    data: None,
20864                },
20865                lsp::CodeLens {
20866                    range: lsp::Range::default(),
20867                    command: Some(lsp::Command {
20868                        title: "Command not in capabilities".to_owned(),
20869                        command: "not in capabilities".to_owned(),
20870                        arguments: None,
20871                    }),
20872                    data: None,
20873                },
20874                lsp::CodeLens {
20875                    range: lsp::Range {
20876                        start: lsp::Position {
20877                            line: 1,
20878                            character: 1,
20879                        },
20880                        end: lsp::Position {
20881                            line: 1,
20882                            character: 1,
20883                        },
20884                    },
20885                    command: Some(lsp::Command {
20886                        title: "Command not in range".to_owned(),
20887                        command: "_the/command".to_owned(),
20888                        arguments: None,
20889                    }),
20890                    data: None,
20891                },
20892            ]))
20893        })
20894        .next()
20895        .await;
20896
20897    let actions = actions.await.unwrap();
20898    assert_eq!(
20899        actions.len(),
20900        1,
20901        "Should have only one valid action for the 0..0 range"
20902    );
20903    let action = actions[0].clone();
20904    let apply = project.update(cx, |project, cx| {
20905        project.apply_code_action(buffer.clone(), action, true, cx)
20906    });
20907
20908    // Resolving the code action does not populate its edits. In absence of
20909    // edits, we must execute the given command.
20910    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20911        |mut lens, _| async move {
20912            let lens_command = lens.command.as_mut().expect("should have a command");
20913            assert_eq!(lens_command.title, "Code lens command");
20914            lens_command.arguments = Some(vec![json!("the-argument")]);
20915            Ok(lens)
20916        },
20917    );
20918
20919    // While executing the command, the language server sends the editor
20920    // a `workspaceEdit` request.
20921    fake_server
20922        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20923            let fake = fake_server.clone();
20924            move |params, _| {
20925                assert_eq!(params.command, "_the/command");
20926                let fake = fake.clone();
20927                async move {
20928                    fake.server
20929                        .request::<lsp::request::ApplyWorkspaceEdit>(
20930                            lsp::ApplyWorkspaceEditParams {
20931                                label: None,
20932                                edit: lsp::WorkspaceEdit {
20933                                    changes: Some(
20934                                        [(
20935                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20936                                            vec![lsp::TextEdit {
20937                                                range: lsp::Range::new(
20938                                                    lsp::Position::new(0, 0),
20939                                                    lsp::Position::new(0, 0),
20940                                                ),
20941                                                new_text: "X".into(),
20942                                            }],
20943                                        )]
20944                                        .into_iter()
20945                                        .collect(),
20946                                    ),
20947                                    ..Default::default()
20948                                },
20949                            },
20950                        )
20951                        .await
20952                        .into_response()
20953                        .unwrap();
20954                    Ok(Some(json!(null)))
20955                }
20956            }
20957        })
20958        .next()
20959        .await;
20960
20961    // Applying the code lens command returns a project transaction containing the edits
20962    // sent by the language server in its `workspaceEdit` request.
20963    let transaction = apply.await.unwrap();
20964    assert!(transaction.0.contains_key(&buffer));
20965    buffer.update(cx, |buffer, cx| {
20966        assert_eq!(buffer.text(), "Xa");
20967        buffer.undo(cx);
20968        assert_eq!(buffer.text(), "a");
20969    });
20970}
20971
20972#[gpui::test]
20973async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20974    init_test(cx, |_| {});
20975
20976    let fs = FakeFs::new(cx.executor());
20977    let main_text = r#"fn main() {
20978println!("1");
20979println!("2");
20980println!("3");
20981println!("4");
20982println!("5");
20983}"#;
20984    let lib_text = "mod foo {}";
20985    fs.insert_tree(
20986        path!("/a"),
20987        json!({
20988            "lib.rs": lib_text,
20989            "main.rs": main_text,
20990        }),
20991    )
20992    .await;
20993
20994    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20995    let (workspace, cx) =
20996        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20997    let worktree_id = workspace.update(cx, |workspace, cx| {
20998        workspace.project().update(cx, |project, cx| {
20999            project.worktrees(cx).next().unwrap().read(cx).id()
21000        })
21001    });
21002
21003    let expected_ranges = vec![
21004        Point::new(0, 0)..Point::new(0, 0),
21005        Point::new(1, 0)..Point::new(1, 1),
21006        Point::new(2, 0)..Point::new(2, 2),
21007        Point::new(3, 0)..Point::new(3, 3),
21008    ];
21009
21010    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21011    let editor_1 = workspace
21012        .update_in(cx, |workspace, window, cx| {
21013            workspace.open_path(
21014                (worktree_id, "main.rs"),
21015                Some(pane_1.downgrade()),
21016                true,
21017                window,
21018                cx,
21019            )
21020        })
21021        .unwrap()
21022        .await
21023        .downcast::<Editor>()
21024        .unwrap();
21025    pane_1.update(cx, |pane, cx| {
21026        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21027        open_editor.update(cx, |editor, cx| {
21028            assert_eq!(
21029                editor.display_text(cx),
21030                main_text,
21031                "Original main.rs text on initial open",
21032            );
21033            assert_eq!(
21034                editor
21035                    .selections
21036                    .all::<Point>(cx)
21037                    .into_iter()
21038                    .map(|s| s.range())
21039                    .collect::<Vec<_>>(),
21040                vec![Point::zero()..Point::zero()],
21041                "Default selections on initial open",
21042            );
21043        })
21044    });
21045    editor_1.update_in(cx, |editor, window, cx| {
21046        editor.change_selections(None, window, cx, |s| {
21047            s.select_ranges(expected_ranges.clone());
21048        });
21049    });
21050
21051    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21052        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21053    });
21054    let editor_2 = workspace
21055        .update_in(cx, |workspace, window, cx| {
21056            workspace.open_path(
21057                (worktree_id, "main.rs"),
21058                Some(pane_2.downgrade()),
21059                true,
21060                window,
21061                cx,
21062            )
21063        })
21064        .unwrap()
21065        .await
21066        .downcast::<Editor>()
21067        .unwrap();
21068    pane_2.update(cx, |pane, cx| {
21069        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21070        open_editor.update(cx, |editor, cx| {
21071            assert_eq!(
21072                editor.display_text(cx),
21073                main_text,
21074                "Original main.rs text on initial open in another panel",
21075            );
21076            assert_eq!(
21077                editor
21078                    .selections
21079                    .all::<Point>(cx)
21080                    .into_iter()
21081                    .map(|s| s.range())
21082                    .collect::<Vec<_>>(),
21083                vec![Point::zero()..Point::zero()],
21084                "Default selections on initial open in another panel",
21085            );
21086        })
21087    });
21088
21089    editor_2.update_in(cx, |editor, window, cx| {
21090        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21091    });
21092
21093    let _other_editor_1 = workspace
21094        .update_in(cx, |workspace, window, cx| {
21095            workspace.open_path(
21096                (worktree_id, "lib.rs"),
21097                Some(pane_1.downgrade()),
21098                true,
21099                window,
21100                cx,
21101            )
21102        })
21103        .unwrap()
21104        .await
21105        .downcast::<Editor>()
21106        .unwrap();
21107    pane_1
21108        .update_in(cx, |pane, window, cx| {
21109            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21110        })
21111        .await
21112        .unwrap();
21113    drop(editor_1);
21114    pane_1.update(cx, |pane, cx| {
21115        pane.active_item()
21116            .unwrap()
21117            .downcast::<Editor>()
21118            .unwrap()
21119            .update(cx, |editor, cx| {
21120                assert_eq!(
21121                    editor.display_text(cx),
21122                    lib_text,
21123                    "Other file should be open and active",
21124                );
21125            });
21126        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21127    });
21128
21129    let _other_editor_2 = workspace
21130        .update_in(cx, |workspace, window, cx| {
21131            workspace.open_path(
21132                (worktree_id, "lib.rs"),
21133                Some(pane_2.downgrade()),
21134                true,
21135                window,
21136                cx,
21137            )
21138        })
21139        .unwrap()
21140        .await
21141        .downcast::<Editor>()
21142        .unwrap();
21143    pane_2
21144        .update_in(cx, |pane, window, cx| {
21145            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21146        })
21147        .await
21148        .unwrap();
21149    drop(editor_2);
21150    pane_2.update(cx, |pane, cx| {
21151        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21152        open_editor.update(cx, |editor, cx| {
21153            assert_eq!(
21154                editor.display_text(cx),
21155                lib_text,
21156                "Other file should be open and active in another panel too",
21157            );
21158        });
21159        assert_eq!(
21160            pane.items().count(),
21161            1,
21162            "No other editors should be open in another pane",
21163        );
21164    });
21165
21166    let _editor_1_reopened = workspace
21167        .update_in(cx, |workspace, window, cx| {
21168            workspace.open_path(
21169                (worktree_id, "main.rs"),
21170                Some(pane_1.downgrade()),
21171                true,
21172                window,
21173                cx,
21174            )
21175        })
21176        .unwrap()
21177        .await
21178        .downcast::<Editor>()
21179        .unwrap();
21180    let _editor_2_reopened = workspace
21181        .update_in(cx, |workspace, window, cx| {
21182            workspace.open_path(
21183                (worktree_id, "main.rs"),
21184                Some(pane_2.downgrade()),
21185                true,
21186                window,
21187                cx,
21188            )
21189        })
21190        .unwrap()
21191        .await
21192        .downcast::<Editor>()
21193        .unwrap();
21194    pane_1.update(cx, |pane, cx| {
21195        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21196        open_editor.update(cx, |editor, cx| {
21197            assert_eq!(
21198                editor.display_text(cx),
21199                main_text,
21200                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21201            );
21202            assert_eq!(
21203                editor
21204                    .selections
21205                    .all::<Point>(cx)
21206                    .into_iter()
21207                    .map(|s| s.range())
21208                    .collect::<Vec<_>>(),
21209                expected_ranges,
21210                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21211            );
21212        })
21213    });
21214    pane_2.update(cx, |pane, cx| {
21215        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21216        open_editor.update(cx, |editor, cx| {
21217            assert_eq!(
21218                editor.display_text(cx),
21219                r#"fn main() {
21220⋯rintln!("1");
21221⋯intln!("2");
21222⋯ntln!("3");
21223println!("4");
21224println!("5");
21225}"#,
21226                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21227            );
21228            assert_eq!(
21229                editor
21230                    .selections
21231                    .all::<Point>(cx)
21232                    .into_iter()
21233                    .map(|s| s.range())
21234                    .collect::<Vec<_>>(),
21235                vec![Point::zero()..Point::zero()],
21236                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21237            );
21238        })
21239    });
21240}
21241
21242#[gpui::test]
21243async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21244    init_test(cx, |_| {});
21245
21246    let fs = FakeFs::new(cx.executor());
21247    let main_text = r#"fn main() {
21248println!("1");
21249println!("2");
21250println!("3");
21251println!("4");
21252println!("5");
21253}"#;
21254    let lib_text = "mod foo {}";
21255    fs.insert_tree(
21256        path!("/a"),
21257        json!({
21258            "lib.rs": lib_text,
21259            "main.rs": main_text,
21260        }),
21261    )
21262    .await;
21263
21264    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21265    let (workspace, cx) =
21266        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21267    let worktree_id = workspace.update(cx, |workspace, cx| {
21268        workspace.project().update(cx, |project, cx| {
21269            project.worktrees(cx).next().unwrap().read(cx).id()
21270        })
21271    });
21272
21273    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21274    let editor = workspace
21275        .update_in(cx, |workspace, window, cx| {
21276            workspace.open_path(
21277                (worktree_id, "main.rs"),
21278                Some(pane.downgrade()),
21279                true,
21280                window,
21281                cx,
21282            )
21283        })
21284        .unwrap()
21285        .await
21286        .downcast::<Editor>()
21287        .unwrap();
21288    pane.update(cx, |pane, cx| {
21289        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21290        open_editor.update(cx, |editor, cx| {
21291            assert_eq!(
21292                editor.display_text(cx),
21293                main_text,
21294                "Original main.rs text on initial open",
21295            );
21296        })
21297    });
21298    editor.update_in(cx, |editor, window, cx| {
21299        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21300    });
21301
21302    cx.update_global(|store: &mut SettingsStore, cx| {
21303        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21304            s.restore_on_file_reopen = Some(false);
21305        });
21306    });
21307    editor.update_in(cx, |editor, window, cx| {
21308        editor.fold_ranges(
21309            vec![
21310                Point::new(1, 0)..Point::new(1, 1),
21311                Point::new(2, 0)..Point::new(2, 2),
21312                Point::new(3, 0)..Point::new(3, 3),
21313            ],
21314            false,
21315            window,
21316            cx,
21317        );
21318    });
21319    pane.update_in(cx, |pane, window, cx| {
21320        pane.close_all_items(&CloseAllItems::default(), window, cx)
21321    })
21322    .await
21323    .unwrap();
21324    pane.update(cx, |pane, _| {
21325        assert!(pane.active_item().is_none());
21326    });
21327    cx.update_global(|store: &mut SettingsStore, cx| {
21328        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21329            s.restore_on_file_reopen = Some(true);
21330        });
21331    });
21332
21333    let _editor_reopened = workspace
21334        .update_in(cx, |workspace, window, cx| {
21335            workspace.open_path(
21336                (worktree_id, "main.rs"),
21337                Some(pane.downgrade()),
21338                true,
21339                window,
21340                cx,
21341            )
21342        })
21343        .unwrap()
21344        .await
21345        .downcast::<Editor>()
21346        .unwrap();
21347    pane.update(cx, |pane, cx| {
21348        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21349        open_editor.update(cx, |editor, cx| {
21350            assert_eq!(
21351                editor.display_text(cx),
21352                main_text,
21353                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21354            );
21355        })
21356    });
21357}
21358
21359#[gpui::test]
21360async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21361    struct EmptyModalView {
21362        focus_handle: gpui::FocusHandle,
21363    }
21364    impl EventEmitter<DismissEvent> for EmptyModalView {}
21365    impl Render for EmptyModalView {
21366        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21367            div()
21368        }
21369    }
21370    impl Focusable for EmptyModalView {
21371        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21372            self.focus_handle.clone()
21373        }
21374    }
21375    impl workspace::ModalView for EmptyModalView {}
21376    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21377        EmptyModalView {
21378            focus_handle: cx.focus_handle(),
21379        }
21380    }
21381
21382    init_test(cx, |_| {});
21383
21384    let fs = FakeFs::new(cx.executor());
21385    let project = Project::test(fs, [], cx).await;
21386    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21387    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21388    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21389    let editor = cx.new_window_entity(|window, cx| {
21390        Editor::new(
21391            EditorMode::full(),
21392            buffer,
21393            Some(project.clone()),
21394            window,
21395            cx,
21396        )
21397    });
21398    workspace
21399        .update(cx, |workspace, window, cx| {
21400            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21401        })
21402        .unwrap();
21403    editor.update_in(cx, |editor, window, cx| {
21404        editor.open_context_menu(&OpenContextMenu, window, cx);
21405        assert!(editor.mouse_context_menu.is_some());
21406    });
21407    workspace
21408        .update(cx, |workspace, window, cx| {
21409            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21410        })
21411        .unwrap();
21412    cx.read(|cx| {
21413        assert!(editor.read(cx).mouse_context_menu.is_none());
21414    });
21415}
21416
21417#[gpui::test]
21418async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21419    init_test(cx, |_| {});
21420
21421    let fs = FakeFs::new(cx.executor());
21422    fs.insert_file(path!("/file.html"), Default::default())
21423        .await;
21424
21425    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21426
21427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21428    let html_language = Arc::new(Language::new(
21429        LanguageConfig {
21430            name: "HTML".into(),
21431            matcher: LanguageMatcher {
21432                path_suffixes: vec!["html".to_string()],
21433                ..LanguageMatcher::default()
21434            },
21435            brackets: BracketPairConfig {
21436                pairs: vec![BracketPair {
21437                    start: "<".into(),
21438                    end: ">".into(),
21439                    close: true,
21440                    ..Default::default()
21441                }],
21442                ..Default::default()
21443            },
21444            ..Default::default()
21445        },
21446        Some(tree_sitter_html::LANGUAGE.into()),
21447    ));
21448    language_registry.add(html_language);
21449    let mut fake_servers = language_registry.register_fake_lsp(
21450        "HTML",
21451        FakeLspAdapter {
21452            capabilities: lsp::ServerCapabilities {
21453                completion_provider: Some(lsp::CompletionOptions {
21454                    resolve_provider: Some(true),
21455                    ..Default::default()
21456                }),
21457                ..Default::default()
21458            },
21459            ..Default::default()
21460        },
21461    );
21462
21463    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21464    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21465
21466    let worktree_id = workspace
21467        .update(cx, |workspace, _window, cx| {
21468            workspace.project().update(cx, |project, cx| {
21469                project.worktrees(cx).next().unwrap().read(cx).id()
21470            })
21471        })
21472        .unwrap();
21473    project
21474        .update(cx, |project, cx| {
21475            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21476        })
21477        .await
21478        .unwrap();
21479    let editor = workspace
21480        .update(cx, |workspace, window, cx| {
21481            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21482        })
21483        .unwrap()
21484        .await
21485        .unwrap()
21486        .downcast::<Editor>()
21487        .unwrap();
21488
21489    let fake_server = fake_servers.next().await.unwrap();
21490    editor.update_in(cx, |editor, window, cx| {
21491        editor.set_text("<ad></ad>", window, cx);
21492        editor.change_selections(None, window, cx, |selections| {
21493            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21494        });
21495        let Some((buffer, _)) = editor
21496            .buffer
21497            .read(cx)
21498            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21499        else {
21500            panic!("Failed to get buffer for selection position");
21501        };
21502        let buffer = buffer.read(cx);
21503        let buffer_id = buffer.remote_id();
21504        let opening_range =
21505            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21506        let closing_range =
21507            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21508        let mut linked_ranges = HashMap::default();
21509        linked_ranges.insert(
21510            buffer_id,
21511            vec![(opening_range.clone(), vec![closing_range.clone()])],
21512        );
21513        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21514    });
21515    let mut completion_handle =
21516        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21517            Ok(Some(lsp::CompletionResponse::Array(vec![
21518                lsp::CompletionItem {
21519                    label: "head".to_string(),
21520                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21521                        lsp::InsertReplaceEdit {
21522                            new_text: "head".to_string(),
21523                            insert: lsp::Range::new(
21524                                lsp::Position::new(0, 1),
21525                                lsp::Position::new(0, 3),
21526                            ),
21527                            replace: lsp::Range::new(
21528                                lsp::Position::new(0, 1),
21529                                lsp::Position::new(0, 3),
21530                            ),
21531                        },
21532                    )),
21533                    ..Default::default()
21534                },
21535            ])))
21536        });
21537    editor.update_in(cx, |editor, window, cx| {
21538        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21539    });
21540    cx.run_until_parked();
21541    completion_handle.next().await.unwrap();
21542    editor.update(cx, |editor, _| {
21543        assert!(
21544            editor.context_menu_visible(),
21545            "Completion menu should be visible"
21546        );
21547    });
21548    editor.update_in(cx, |editor, window, cx| {
21549        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21550    });
21551    cx.executor().run_until_parked();
21552    editor.update(cx, |editor, cx| {
21553        assert_eq!(editor.text(cx), "<head></head>");
21554    });
21555}
21556
21557#[gpui::test]
21558async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21559    init_test(cx, |_| {});
21560
21561    let fs = FakeFs::new(cx.executor());
21562    fs.insert_tree(
21563        path!("/root"),
21564        json!({
21565            "a": {
21566                "main.rs": "fn main() {}",
21567            },
21568            "foo": {
21569                "bar": {
21570                    "external_file.rs": "pub mod external {}",
21571                }
21572            }
21573        }),
21574    )
21575    .await;
21576
21577    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21578    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21579    language_registry.add(rust_lang());
21580    let _fake_servers = language_registry.register_fake_lsp(
21581        "Rust",
21582        FakeLspAdapter {
21583            ..FakeLspAdapter::default()
21584        },
21585    );
21586    let (workspace, cx) =
21587        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21588    let worktree_id = workspace.update(cx, |workspace, cx| {
21589        workspace.project().update(cx, |project, cx| {
21590            project.worktrees(cx).next().unwrap().read(cx).id()
21591        })
21592    });
21593
21594    let assert_language_servers_count =
21595        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21596            project.update(cx, |project, cx| {
21597                let current = project
21598                    .lsp_store()
21599                    .read(cx)
21600                    .as_local()
21601                    .unwrap()
21602                    .language_servers
21603                    .len();
21604                assert_eq!(expected, current, "{context}");
21605            });
21606        };
21607
21608    assert_language_servers_count(
21609        0,
21610        "No servers should be running before any file is open",
21611        cx,
21612    );
21613    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21614    let main_editor = workspace
21615        .update_in(cx, |workspace, window, cx| {
21616            workspace.open_path(
21617                (worktree_id, "main.rs"),
21618                Some(pane.downgrade()),
21619                true,
21620                window,
21621                cx,
21622            )
21623        })
21624        .unwrap()
21625        .await
21626        .downcast::<Editor>()
21627        .unwrap();
21628    pane.update(cx, |pane, cx| {
21629        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21630        open_editor.update(cx, |editor, cx| {
21631            assert_eq!(
21632                editor.display_text(cx),
21633                "fn main() {}",
21634                "Original main.rs text on initial open",
21635            );
21636        });
21637        assert_eq!(open_editor, main_editor);
21638    });
21639    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21640
21641    let external_editor = workspace
21642        .update_in(cx, |workspace, window, cx| {
21643            workspace.open_abs_path(
21644                PathBuf::from("/root/foo/bar/external_file.rs"),
21645                OpenOptions::default(),
21646                window,
21647                cx,
21648            )
21649        })
21650        .await
21651        .expect("opening external file")
21652        .downcast::<Editor>()
21653        .expect("downcasted external file's open element to editor");
21654    pane.update(cx, |pane, cx| {
21655        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21656        open_editor.update(cx, |editor, cx| {
21657            assert_eq!(
21658                editor.display_text(cx),
21659                "pub mod external {}",
21660                "External file is open now",
21661            );
21662        });
21663        assert_eq!(open_editor, external_editor);
21664    });
21665    assert_language_servers_count(
21666        1,
21667        "Second, external, *.rs file should join the existing server",
21668        cx,
21669    );
21670
21671    pane.update_in(cx, |pane, window, cx| {
21672        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21673    })
21674    .await
21675    .unwrap();
21676    pane.update_in(cx, |pane, window, cx| {
21677        pane.navigate_backward(window, cx);
21678    });
21679    cx.run_until_parked();
21680    pane.update(cx, |pane, cx| {
21681        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21682        open_editor.update(cx, |editor, cx| {
21683            assert_eq!(
21684                editor.display_text(cx),
21685                "pub mod external {}",
21686                "External file is open now",
21687            );
21688        });
21689    });
21690    assert_language_servers_count(
21691        1,
21692        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21693        cx,
21694    );
21695
21696    cx.update(|_, cx| {
21697        workspace::reload(&workspace::Reload::default(), cx);
21698    });
21699    assert_language_servers_count(
21700        1,
21701        "After reloading the worktree with local and external files opened, only one project should be started",
21702        cx,
21703    );
21704}
21705
21706#[gpui::test]
21707async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21708    init_test(cx, |_| {});
21709
21710    let mut cx = EditorTestContext::new(cx).await;
21711    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21713
21714    // test cursor move to start of each line on tab
21715    // for `if`, `elif`, `else`, `while`, `with` and `for`
21716    cx.set_state(indoc! {"
21717        def main():
21718        ˇ    for item in items:
21719        ˇ        while item.active:
21720        ˇ            if item.value > 10:
21721        ˇ                continue
21722        ˇ            elif item.value < 0:
21723        ˇ                break
21724        ˇ            else:
21725        ˇ                with item.context() as ctx:
21726        ˇ                    yield count
21727        ˇ        else:
21728        ˇ            log('while else')
21729        ˇ    else:
21730        ˇ        log('for else')
21731    "});
21732    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21733    cx.assert_editor_state(indoc! {"
21734        def main():
21735            ˇfor item in items:
21736                ˇwhile item.active:
21737                    ˇif item.value > 10:
21738                        ˇcontinue
21739                    ˇelif item.value < 0:
21740                        ˇbreak
21741                    ˇelse:
21742                        ˇwith item.context() as ctx:
21743                            ˇyield count
21744                ˇelse:
21745                    ˇlog('while else')
21746            ˇelse:
21747                ˇlog('for else')
21748    "});
21749    // test relative indent is preserved when tab
21750    // for `if`, `elif`, `else`, `while`, `with` and `for`
21751    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21752    cx.assert_editor_state(indoc! {"
21753        def main():
21754                ˇfor item in items:
21755                    ˇwhile item.active:
21756                        ˇif item.value > 10:
21757                            ˇcontinue
21758                        ˇelif item.value < 0:
21759                            ˇbreak
21760                        ˇelse:
21761                            ˇwith item.context() as ctx:
21762                                ˇyield count
21763                    ˇelse:
21764                        ˇlog('while else')
21765                ˇelse:
21766                    ˇlog('for else')
21767    "});
21768
21769    // test cursor move to start of each line on tab
21770    // for `try`, `except`, `else`, `finally`, `match` and `def`
21771    cx.set_state(indoc! {"
21772        def main():
21773        ˇ    try:
21774        ˇ        fetch()
21775        ˇ    except ValueError:
21776        ˇ        handle_error()
21777        ˇ    else:
21778        ˇ        match value:
21779        ˇ            case _:
21780        ˇ    finally:
21781        ˇ        def status():
21782        ˇ            return 0
21783    "});
21784    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21785    cx.assert_editor_state(indoc! {"
21786        def main():
21787            ˇtry:
21788                ˇfetch()
21789            ˇexcept ValueError:
21790                ˇhandle_error()
21791            ˇelse:
21792                ˇmatch value:
21793                    ˇcase _:
21794            ˇfinally:
21795                ˇdef status():
21796                    ˇreturn 0
21797    "});
21798    // test relative indent is preserved when tab
21799    // for `try`, `except`, `else`, `finally`, `match` and `def`
21800    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21801    cx.assert_editor_state(indoc! {"
21802        def main():
21803                ˇtry:
21804                    ˇfetch()
21805                ˇexcept ValueError:
21806                    ˇhandle_error()
21807                ˇelse:
21808                    ˇmatch value:
21809                        ˇcase _:
21810                ˇfinally:
21811                    ˇdef status():
21812                        ˇreturn 0
21813    "});
21814}
21815
21816#[gpui::test]
21817async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21818    init_test(cx, |_| {});
21819
21820    let mut cx = EditorTestContext::new(cx).await;
21821    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21822    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21823
21824    // test `else` auto outdents when typed inside `if` block
21825    cx.set_state(indoc! {"
21826        def main():
21827            if i == 2:
21828                return
21829                ˇ
21830    "});
21831    cx.update_editor(|editor, window, cx| {
21832        editor.handle_input("else:", window, cx);
21833    });
21834    cx.assert_editor_state(indoc! {"
21835        def main():
21836            if i == 2:
21837                return
21838            else:ˇ
21839    "});
21840
21841    // test `except` auto outdents when typed inside `try` block
21842    cx.set_state(indoc! {"
21843        def main():
21844            try:
21845                i = 2
21846                ˇ
21847    "});
21848    cx.update_editor(|editor, window, cx| {
21849        editor.handle_input("except:", window, cx);
21850    });
21851    cx.assert_editor_state(indoc! {"
21852        def main():
21853            try:
21854                i = 2
21855            except:ˇ
21856    "});
21857
21858    // test `else` auto outdents when typed inside `except` block
21859    cx.set_state(indoc! {"
21860        def main():
21861            try:
21862                i = 2
21863            except:
21864                j = 2
21865                ˇ
21866    "});
21867    cx.update_editor(|editor, window, cx| {
21868        editor.handle_input("else:", window, cx);
21869    });
21870    cx.assert_editor_state(indoc! {"
21871        def main():
21872            try:
21873                i = 2
21874            except:
21875                j = 2
21876            else:ˇ
21877    "});
21878
21879    // test `finally` auto outdents when typed inside `else` block
21880    cx.set_state(indoc! {"
21881        def main():
21882            try:
21883                i = 2
21884            except:
21885                j = 2
21886            else:
21887                k = 2
21888                ˇ
21889    "});
21890    cx.update_editor(|editor, window, cx| {
21891        editor.handle_input("finally:", window, cx);
21892    });
21893    cx.assert_editor_state(indoc! {"
21894        def main():
21895            try:
21896                i = 2
21897            except:
21898                j = 2
21899            else:
21900                k = 2
21901            finally:ˇ
21902    "});
21903
21904    // test `else` does not outdents when typed inside `except` block right after for block
21905    cx.set_state(indoc! {"
21906        def main():
21907            try:
21908                i = 2
21909            except:
21910                for i in range(n):
21911                    pass
21912                ˇ
21913    "});
21914    cx.update_editor(|editor, window, cx| {
21915        editor.handle_input("else:", window, cx);
21916    });
21917    cx.assert_editor_state(indoc! {"
21918        def main():
21919            try:
21920                i = 2
21921            except:
21922                for i in range(n):
21923                    pass
21924                else:ˇ
21925    "});
21926
21927    // test `finally` auto outdents when typed inside `else` block right after for block
21928    cx.set_state(indoc! {"
21929        def main():
21930            try:
21931                i = 2
21932            except:
21933                j = 2
21934            else:
21935                for i in range(n):
21936                    pass
21937                ˇ
21938    "});
21939    cx.update_editor(|editor, window, cx| {
21940        editor.handle_input("finally:", window, cx);
21941    });
21942    cx.assert_editor_state(indoc! {"
21943        def main():
21944            try:
21945                i = 2
21946            except:
21947                j = 2
21948            else:
21949                for i in range(n):
21950                    pass
21951            finally:ˇ
21952    "});
21953
21954    // test `except` outdents to inner "try" block
21955    cx.set_state(indoc! {"
21956        def main():
21957            try:
21958                i = 2
21959                if i == 2:
21960                    try:
21961                        i = 3
21962                        ˇ
21963    "});
21964    cx.update_editor(|editor, window, cx| {
21965        editor.handle_input("except:", window, cx);
21966    });
21967    cx.assert_editor_state(indoc! {"
21968        def main():
21969            try:
21970                i = 2
21971                if i == 2:
21972                    try:
21973                        i = 3
21974                    except:ˇ
21975    "});
21976
21977    // test `except` outdents to outer "try" block
21978    cx.set_state(indoc! {"
21979        def main():
21980            try:
21981                i = 2
21982                if i == 2:
21983                    try:
21984                        i = 3
21985                ˇ
21986    "});
21987    cx.update_editor(|editor, window, cx| {
21988        editor.handle_input("except:", window, cx);
21989    });
21990    cx.assert_editor_state(indoc! {"
21991        def main():
21992            try:
21993                i = 2
21994                if i == 2:
21995                    try:
21996                        i = 3
21997            except:ˇ
21998    "});
21999
22000    // test `else` stays at correct indent when typed after `for` block
22001    cx.set_state(indoc! {"
22002        def main():
22003            for i in range(10):
22004                if i == 3:
22005                    break
22006            ˇ
22007    "});
22008    cx.update_editor(|editor, window, cx| {
22009        editor.handle_input("else:", window, cx);
22010    });
22011    cx.assert_editor_state(indoc! {"
22012        def main():
22013            for i in range(10):
22014                if i == 3:
22015                    break
22016            else:ˇ
22017    "});
22018
22019    // test does not outdent on typing after line with square brackets
22020    cx.set_state(indoc! {"
22021        def f() -> list[str]:
22022            ˇ
22023    "});
22024    cx.update_editor(|editor, window, cx| {
22025        editor.handle_input("a", window, cx);
22026    });
22027    cx.assert_editor_state(indoc! {"
22028        def f() -> list[str]:
2202922030    "});
22031}
22032
22033#[gpui::test]
22034async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22035    init_test(cx, |_| {});
22036    update_test_language_settings(cx, |settings| {
22037        settings.defaults.extend_comment_on_newline = Some(false);
22038    });
22039    let mut cx = EditorTestContext::new(cx).await;
22040    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22041    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22042
22043    // test correct indent after newline on comment
22044    cx.set_state(indoc! {"
22045        # COMMENT:ˇ
22046    "});
22047    cx.update_editor(|editor, window, cx| {
22048        editor.newline(&Newline, window, cx);
22049    });
22050    cx.assert_editor_state(indoc! {"
22051        # COMMENT:
22052        ˇ
22053    "});
22054
22055    // test correct indent after newline in brackets
22056    cx.set_state(indoc! {"
22057        {ˇ}
22058    "});
22059    cx.update_editor(|editor, window, cx| {
22060        editor.newline(&Newline, window, cx);
22061    });
22062    cx.run_until_parked();
22063    cx.assert_editor_state(indoc! {"
22064        {
22065            ˇ
22066        }
22067    "});
22068
22069    cx.set_state(indoc! {"
22070        (ˇ)
22071    "});
22072    cx.update_editor(|editor, window, cx| {
22073        editor.newline(&Newline, window, cx);
22074    });
22075    cx.run_until_parked();
22076    cx.assert_editor_state(indoc! {"
22077        (
22078            ˇ
22079        )
22080    "});
22081
22082    // do not indent after empty lists or dictionaries
22083    cx.set_state(indoc! {"
22084        a = []ˇ
22085    "});
22086    cx.update_editor(|editor, window, cx| {
22087        editor.newline(&Newline, window, cx);
22088    });
22089    cx.run_until_parked();
22090    cx.assert_editor_state(indoc! {"
22091        a = []
22092        ˇ
22093    "});
22094}
22095
22096fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22097    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22098    point..point
22099}
22100
22101#[track_caller]
22102fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22103    let (text, ranges) = marked_text_ranges(marked_text, true);
22104    assert_eq!(editor.text(cx), text);
22105    assert_eq!(
22106        editor.selections.ranges(cx),
22107        ranges,
22108        "Assert selections are {}",
22109        marked_text
22110    );
22111}
22112
22113pub fn handle_signature_help_request(
22114    cx: &mut EditorLspTestContext,
22115    mocked_response: lsp::SignatureHelp,
22116) -> impl Future<Output = ()> + use<> {
22117    let mut request =
22118        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22119            let mocked_response = mocked_response.clone();
22120            async move { Ok(Some(mocked_response)) }
22121        });
22122
22123    async move {
22124        request.next().await;
22125    }
22126}
22127
22128#[track_caller]
22129pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22130    cx.update_editor(|editor, _, _| {
22131        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22132            let entries = menu.entries.borrow();
22133            let entries = entries
22134                .iter()
22135                .map(|entry| entry.string.as_str())
22136                .collect::<Vec<_>>();
22137            assert_eq!(entries, expected);
22138        } else {
22139            panic!("Expected completions menu");
22140        }
22141    });
22142}
22143
22144/// Handle completion request passing a marked string specifying where the completion
22145/// should be triggered from using '|' character, what range should be replaced, and what completions
22146/// should be returned using '<' and '>' to delimit the range.
22147///
22148/// Also see `handle_completion_request_with_insert_and_replace`.
22149#[track_caller]
22150pub fn handle_completion_request(
22151    marked_string: &str,
22152    completions: Vec<&'static str>,
22153    is_incomplete: bool,
22154    counter: Arc<AtomicUsize>,
22155    cx: &mut EditorLspTestContext,
22156) -> impl Future<Output = ()> {
22157    let complete_from_marker: TextRangeMarker = '|'.into();
22158    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22159    let (_, mut marked_ranges) = marked_text_ranges_by(
22160        marked_string,
22161        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22162    );
22163
22164    let complete_from_position =
22165        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22166    let replace_range =
22167        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22168
22169    let mut request =
22170        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22171            let completions = completions.clone();
22172            counter.fetch_add(1, atomic::Ordering::Release);
22173            async move {
22174                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22175                assert_eq!(
22176                    params.text_document_position.position,
22177                    complete_from_position
22178                );
22179                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22180                    is_incomplete: is_incomplete,
22181                    item_defaults: None,
22182                    items: completions
22183                        .iter()
22184                        .map(|completion_text| lsp::CompletionItem {
22185                            label: completion_text.to_string(),
22186                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22187                                range: replace_range,
22188                                new_text: completion_text.to_string(),
22189                            })),
22190                            ..Default::default()
22191                        })
22192                        .collect(),
22193                })))
22194            }
22195        });
22196
22197    async move {
22198        request.next().await;
22199    }
22200}
22201
22202/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22203/// given instead, which also contains an `insert` range.
22204///
22205/// This function uses markers to define ranges:
22206/// - `|` marks the cursor position
22207/// - `<>` marks the replace range
22208/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22209pub fn handle_completion_request_with_insert_and_replace(
22210    cx: &mut EditorLspTestContext,
22211    marked_string: &str,
22212    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22213    counter: Arc<AtomicUsize>,
22214) -> impl Future<Output = ()> {
22215    let complete_from_marker: TextRangeMarker = '|'.into();
22216    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22217    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22218
22219    let (_, mut marked_ranges) = marked_text_ranges_by(
22220        marked_string,
22221        vec![
22222            complete_from_marker.clone(),
22223            replace_range_marker.clone(),
22224            insert_range_marker.clone(),
22225        ],
22226    );
22227
22228    let complete_from_position =
22229        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22230    let replace_range =
22231        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22232
22233    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22234        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22235        _ => lsp::Range {
22236            start: replace_range.start,
22237            end: complete_from_position,
22238        },
22239    };
22240
22241    let mut request =
22242        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22243            let completions = completions.clone();
22244            counter.fetch_add(1, atomic::Ordering::Release);
22245            async move {
22246                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22247                assert_eq!(
22248                    params.text_document_position.position, complete_from_position,
22249                    "marker `|` position doesn't match",
22250                );
22251                Ok(Some(lsp::CompletionResponse::Array(
22252                    completions
22253                        .iter()
22254                        .map(|(label, new_text)| lsp::CompletionItem {
22255                            label: label.to_string(),
22256                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22257                                lsp::InsertReplaceEdit {
22258                                    insert: insert_range,
22259                                    replace: replace_range,
22260                                    new_text: new_text.to_string(),
22261                                },
22262                            )),
22263                            ..Default::default()
22264                        })
22265                        .collect(),
22266                )))
22267            }
22268        });
22269
22270    async move {
22271        request.next().await;
22272    }
22273}
22274
22275fn handle_resolve_completion_request(
22276    cx: &mut EditorLspTestContext,
22277    edits: Option<Vec<(&'static str, &'static str)>>,
22278) -> impl Future<Output = ()> {
22279    let edits = edits.map(|edits| {
22280        edits
22281            .iter()
22282            .map(|(marked_string, new_text)| {
22283                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22284                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22285                lsp::TextEdit::new(replace_range, new_text.to_string())
22286            })
22287            .collect::<Vec<_>>()
22288    });
22289
22290    let mut request =
22291        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22292            let edits = edits.clone();
22293            async move {
22294                Ok(lsp::CompletionItem {
22295                    additional_text_edits: edits,
22296                    ..Default::default()
22297                })
22298            }
22299        });
22300
22301    async move {
22302        request.next().await;
22303    }
22304}
22305
22306pub(crate) fn update_test_language_settings(
22307    cx: &mut TestAppContext,
22308    f: impl Fn(&mut AllLanguageSettingsContent),
22309) {
22310    cx.update(|cx| {
22311        SettingsStore::update_global(cx, |store, cx| {
22312            store.update_user_settings::<AllLanguageSettings>(cx, f);
22313        });
22314    });
22315}
22316
22317pub(crate) fn update_test_project_settings(
22318    cx: &mut TestAppContext,
22319    f: impl Fn(&mut ProjectSettings),
22320) {
22321    cx.update(|cx| {
22322        SettingsStore::update_global(cx, |store, cx| {
22323            store.update_user_settings::<ProjectSettings>(cx, f);
22324        });
22325    });
22326}
22327
22328pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22329    cx.update(|cx| {
22330        assets::Assets.load_test_fonts(cx);
22331        let store = SettingsStore::test(cx);
22332        cx.set_global(store);
22333        theme::init(theme::LoadThemes::JustBase, cx);
22334        release_channel::init(SemanticVersion::default(), cx);
22335        client::init_settings(cx);
22336        language::init(cx);
22337        Project::init_settings(cx);
22338        workspace::init_settings(cx);
22339        crate::init(cx);
22340    });
22341
22342    update_test_language_settings(cx, f);
22343}
22344
22345#[track_caller]
22346fn assert_hunk_revert(
22347    not_reverted_text_with_selections: &str,
22348    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22349    expected_reverted_text_with_selections: &str,
22350    base_text: &str,
22351    cx: &mut EditorLspTestContext,
22352) {
22353    cx.set_state(not_reverted_text_with_selections);
22354    cx.set_head_text(base_text);
22355    cx.executor().run_until_parked();
22356
22357    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22358        let snapshot = editor.snapshot(window, cx);
22359        let reverted_hunk_statuses = snapshot
22360            .buffer_snapshot
22361            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22362            .map(|hunk| hunk.status().kind)
22363            .collect::<Vec<_>>();
22364
22365        editor.git_restore(&Default::default(), window, cx);
22366        reverted_hunk_statuses
22367    });
22368    cx.executor().run_until_parked();
22369    cx.assert_editor_state(expected_reverted_text_with_selections);
22370    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22371}
22372
22373#[gpui::test(iterations = 10)]
22374async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22375    init_test(cx, |_| {});
22376
22377    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22378    let counter = diagnostic_requests.clone();
22379
22380    let fs = FakeFs::new(cx.executor());
22381    fs.insert_tree(
22382        path!("/a"),
22383        json!({
22384            "first.rs": "fn main() { let a = 5; }",
22385            "second.rs": "// Test file",
22386        }),
22387    )
22388    .await;
22389
22390    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22391    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22392    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22393
22394    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22395    language_registry.add(rust_lang());
22396    let mut fake_servers = language_registry.register_fake_lsp(
22397        "Rust",
22398        FakeLspAdapter {
22399            capabilities: lsp::ServerCapabilities {
22400                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22401                    lsp::DiagnosticOptions {
22402                        identifier: None,
22403                        inter_file_dependencies: true,
22404                        workspace_diagnostics: true,
22405                        work_done_progress_options: Default::default(),
22406                    },
22407                )),
22408                ..Default::default()
22409            },
22410            ..Default::default()
22411        },
22412    );
22413
22414    let editor = workspace
22415        .update(cx, |workspace, window, cx| {
22416            workspace.open_abs_path(
22417                PathBuf::from(path!("/a/first.rs")),
22418                OpenOptions::default(),
22419                window,
22420                cx,
22421            )
22422        })
22423        .unwrap()
22424        .await
22425        .unwrap()
22426        .downcast::<Editor>()
22427        .unwrap();
22428    let fake_server = fake_servers.next().await.unwrap();
22429    let server_id = fake_server.server.server_id();
22430    let mut first_request = fake_server
22431        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22432            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22433            let result_id = Some(new_result_id.to_string());
22434            assert_eq!(
22435                params.text_document.uri,
22436                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22437            );
22438            async move {
22439                Ok(lsp::DocumentDiagnosticReportResult::Report(
22440                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22441                        related_documents: None,
22442                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22443                            items: Vec::new(),
22444                            result_id,
22445                        },
22446                    }),
22447                ))
22448            }
22449        });
22450
22451    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22452        project.update(cx, |project, cx| {
22453            let buffer_id = editor
22454                .read(cx)
22455                .buffer()
22456                .read(cx)
22457                .as_singleton()
22458                .expect("created a singleton buffer")
22459                .read(cx)
22460                .remote_id();
22461            let buffer_result_id = project
22462                .lsp_store()
22463                .read(cx)
22464                .result_id(server_id, buffer_id, cx);
22465            assert_eq!(expected, buffer_result_id);
22466        });
22467    };
22468
22469    ensure_result_id(None, cx);
22470    cx.executor().advance_clock(Duration::from_millis(60));
22471    cx.executor().run_until_parked();
22472    assert_eq!(
22473        diagnostic_requests.load(atomic::Ordering::Acquire),
22474        1,
22475        "Opening file should trigger diagnostic request"
22476    );
22477    first_request
22478        .next()
22479        .await
22480        .expect("should have sent the first diagnostics pull request");
22481    ensure_result_id(Some("1".to_string()), cx);
22482
22483    // Editing should trigger diagnostics
22484    editor.update_in(cx, |editor, window, cx| {
22485        editor.handle_input("2", window, cx)
22486    });
22487    cx.executor().advance_clock(Duration::from_millis(60));
22488    cx.executor().run_until_parked();
22489    assert_eq!(
22490        diagnostic_requests.load(atomic::Ordering::Acquire),
22491        2,
22492        "Editing should trigger diagnostic request"
22493    );
22494    ensure_result_id(Some("2".to_string()), cx);
22495
22496    // Moving cursor should not trigger diagnostic request
22497    editor.update_in(cx, |editor, window, cx| {
22498        editor.change_selections(None, window, cx, |s| {
22499            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22500        });
22501    });
22502    cx.executor().advance_clock(Duration::from_millis(60));
22503    cx.executor().run_until_parked();
22504    assert_eq!(
22505        diagnostic_requests.load(atomic::Ordering::Acquire),
22506        2,
22507        "Cursor movement should not trigger diagnostic request"
22508    );
22509    ensure_result_id(Some("2".to_string()), cx);
22510    // Multiple rapid edits should be debounced
22511    for _ in 0..5 {
22512        editor.update_in(cx, |editor, window, cx| {
22513            editor.handle_input("x", window, cx)
22514        });
22515    }
22516    cx.executor().advance_clock(Duration::from_millis(60));
22517    cx.executor().run_until_parked();
22518
22519    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22520    assert!(
22521        final_requests <= 4,
22522        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22523    );
22524    ensure_result_id(Some(final_requests.to_string()), cx);
22525}
22526
22527#[gpui::test]
22528async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22529    // Regression test for issue #11671
22530    // Previously, adding a cursor after moving multiple cursors would reset
22531    // the cursor count instead of adding to the existing cursors.
22532    init_test(cx, |_| {});
22533    let mut cx = EditorTestContext::new(cx).await;
22534
22535    // Create a simple buffer with cursor at start
22536    cx.set_state(indoc! {"
22537        ˇaaaa
22538        bbbb
22539        cccc
22540        dddd
22541        eeee
22542        ffff
22543        gggg
22544        hhhh"});
22545
22546    // Add 2 cursors below (so we have 3 total)
22547    cx.update_editor(|editor, window, cx| {
22548        editor.add_selection_below(&Default::default(), window, cx);
22549        editor.add_selection_below(&Default::default(), window, cx);
22550    });
22551
22552    // Verify we have 3 cursors
22553    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22554    assert_eq!(
22555        initial_count, 3,
22556        "Should have 3 cursors after adding 2 below"
22557    );
22558
22559    // Move down one line
22560    cx.update_editor(|editor, window, cx| {
22561        editor.move_down(&MoveDown, window, cx);
22562    });
22563
22564    // Add another cursor below
22565    cx.update_editor(|editor, window, cx| {
22566        editor.add_selection_below(&Default::default(), window, cx);
22567    });
22568
22569    // Should now have 4 cursors (3 original + 1 new)
22570    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22571    assert_eq!(
22572        final_count, 4,
22573        "Should have 4 cursors after moving and adding another"
22574    );
22575}
22576
22577#[gpui::test]
22578async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
22579    let expected_color = Rgba {
22580        r: 0.33,
22581        g: 0.33,
22582        b: 0.33,
22583        a: 0.33,
22584    };
22585
22586    init_test(cx, |_| {});
22587
22588    let fs = FakeFs::new(cx.executor());
22589    fs.insert_tree(
22590        path!("/a"),
22591        json!({
22592            "first.rs": "fn main() { let a = 5; }",
22593        }),
22594    )
22595    .await;
22596
22597    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22598    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22599    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22600
22601    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22602    language_registry.add(rust_lang());
22603    let mut fake_servers = language_registry.register_fake_lsp(
22604        "Rust",
22605        FakeLspAdapter {
22606            capabilities: lsp::ServerCapabilities {
22607                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22608                ..lsp::ServerCapabilities::default()
22609            },
22610            ..FakeLspAdapter::default()
22611        },
22612    );
22613
22614    let editor = workspace
22615        .update(cx, |workspace, window, cx| {
22616            workspace.open_abs_path(
22617                PathBuf::from(path!("/a/first.rs")),
22618                OpenOptions::default(),
22619                window,
22620                cx,
22621            )
22622        })
22623        .unwrap()
22624        .await
22625        .unwrap()
22626        .downcast::<Editor>()
22627        .unwrap();
22628    let fake_language_server = fake_servers.next().await.unwrap();
22629    let requests_made = Arc::new(AtomicUsize::new(0));
22630    let closure_requests_made = Arc::clone(&requests_made);
22631    let mut color_request_handle = fake_language_server
22632        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22633            let requests_made = Arc::clone(&closure_requests_made);
22634            async move {
22635                assert_eq!(
22636                    params.text_document.uri,
22637                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22638                );
22639                requests_made.fetch_add(1, atomic::Ordering::Release);
22640                Ok(vec![lsp::ColorInformation {
22641                    range: lsp::Range {
22642                        start: lsp::Position {
22643                            line: 0,
22644                            character: 0,
22645                        },
22646                        end: lsp::Position {
22647                            line: 0,
22648                            character: 1,
22649                        },
22650                    },
22651                    color: lsp::Color {
22652                        red: 0.33,
22653                        green: 0.33,
22654                        blue: 0.33,
22655                        alpha: 0.33,
22656                    },
22657                }])
22658            }
22659        });
22660    color_request_handle.next().await.unwrap();
22661    cx.run_until_parked();
22662    color_request_handle.next().await.unwrap();
22663    cx.run_until_parked();
22664    assert_eq!(
22665        2,
22666        requests_made.load(atomic::Ordering::Acquire),
22667        "Should query for colors once per editor open and once after the language server startup"
22668    );
22669
22670    cx.executor().advance_clock(Duration::from_millis(500));
22671    let save = editor.update_in(cx, |editor, window, cx| {
22672        assert_eq!(
22673            vec![expected_color],
22674            extract_color_inlays(editor, cx),
22675            "Should have an initial inlay"
22676        );
22677
22678        editor.move_to_end(&MoveToEnd, window, cx);
22679        editor.handle_input("dirty", window, cx);
22680        editor.save(
22681            SaveOptions {
22682                format: true,
22683                autosave: true,
22684            },
22685            project.clone(),
22686            window,
22687            cx,
22688        )
22689    });
22690    save.await.unwrap();
22691
22692    color_request_handle.next().await.unwrap();
22693    cx.run_until_parked();
22694    color_request_handle.next().await.unwrap();
22695    cx.run_until_parked();
22696    assert_eq!(
22697        4,
22698        requests_made.load(atomic::Ordering::Acquire),
22699        "Should query for colors once per save and once per formatting after save"
22700    );
22701
22702    drop(editor);
22703    let close = workspace
22704        .update(cx, |workspace, window, cx| {
22705            workspace.active_pane().update(cx, |pane, cx| {
22706                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22707            })
22708        })
22709        .unwrap();
22710    close.await.unwrap();
22711    assert_eq!(
22712        4,
22713        requests_made.load(atomic::Ordering::Acquire),
22714        "After saving and closing the editor, no extra requests should be made"
22715    );
22716
22717    workspace
22718        .update(cx, |workspace, window, cx| {
22719            workspace.active_pane().update(cx, |pane, cx| {
22720                pane.navigate_backward(window, cx);
22721            })
22722        })
22723        .unwrap();
22724    color_request_handle.next().await.unwrap();
22725    cx.run_until_parked();
22726    assert_eq!(
22727        5,
22728        requests_made.load(atomic::Ordering::Acquire),
22729        "After navigating back to an editor and reopening it, another color request should be made"
22730    );
22731    let editor = workspace
22732        .update(cx, |workspace, _, cx| {
22733            workspace
22734                .active_item(cx)
22735                .expect("Should have reopened the editor again after navigating back")
22736                .downcast::<Editor>()
22737                .expect("Should be an editor")
22738        })
22739        .unwrap();
22740    editor.update(cx, |editor, cx| {
22741        assert_eq!(
22742            vec![expected_color],
22743            extract_color_inlays(editor, cx),
22744            "Should have an initial inlay"
22745        );
22746    });
22747}
22748
22749#[track_caller]
22750fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22751    editor
22752        .all_inlays(cx)
22753        .into_iter()
22754        .filter_map(|inlay| inlay.get_color())
22755        .map(Rgba::from)
22756        .collect()
22757}