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(SelectionEffects::no_scroll(), window, cx, |s| {
  183            s.select_ranges([0..0])
  184        });
  185
  186        editor.backspace(&Backspace, window, cx);
  187    });
  188    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  189}
  190
  191#[gpui::test]
  192fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  193    init_test(cx, |_| {});
  194
  195    let mut now = Instant::now();
  196    let group_interval = Duration::from_millis(1);
  197    let buffer = cx.new(|cx| {
  198        let mut buf = language::Buffer::local("123456", cx);
  199        buf.set_group_interval(group_interval);
  200        buf
  201    });
  202    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  203    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  204
  205    _ = editor.update(cx, |editor, window, cx| {
  206        editor.start_transaction_at(now, window, cx);
  207        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  208            s.select_ranges([2..4])
  209        });
  210
  211        editor.insert("cd", window, cx);
  212        editor.end_transaction_at(now, cx);
  213        assert_eq!(editor.text(cx), "12cd56");
  214        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  215
  216        editor.start_transaction_at(now, window, cx);
  217        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  218            s.select_ranges([4..5])
  219        });
  220        editor.insert("e", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cde6");
  223        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  224
  225        now += group_interval + Duration::from_millis(1);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([2..2])
  228        });
  229
  230        // Simulate an edit in another editor
  231        buffer.update(cx, |buffer, cx| {
  232            buffer.start_transaction_at(now, cx);
  233            buffer.edit([(0..1, "a")], None, cx);
  234            buffer.edit([(1..1, "b")], None, cx);
  235            buffer.end_transaction_at(now, cx);
  236        });
  237
  238        assert_eq!(editor.text(cx), "ab2cde6");
  239        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  240
  241        // Last transaction happened past the group interval in a different editor.
  242        // Undo it individually and don't restore selections.
  243        editor.undo(&Undo, window, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  246
  247        // First two transactions happened within the group interval in this editor.
  248        // Undo them together and restore selections.
  249        editor.undo(&Undo, window, cx);
  250        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  251        assert_eq!(editor.text(cx), "123456");
  252        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  253
  254        // Redo the first two transactions together.
  255        editor.redo(&Redo, window, cx);
  256        assert_eq!(editor.text(cx), "12cde6");
  257        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  258
  259        // Redo the last transaction on its own.
  260        editor.redo(&Redo, window, cx);
  261        assert_eq!(editor.text(cx), "ab2cde6");
  262        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  263
  264        // Test empty transactions.
  265        editor.start_transaction_at(now, window, cx);
  266        editor.end_transaction_at(now, cx);
  267        editor.undo(&Undo, window, cx);
  268        assert_eq!(editor.text(cx), "12cde6");
  269    });
  270}
  271
  272#[gpui::test]
  273fn test_ime_composition(cx: &mut TestAppContext) {
  274    init_test(cx, |_| {});
  275
  276    let buffer = cx.new(|cx| {
  277        let mut buffer = language::Buffer::local("abcde", cx);
  278        // Ensure automatic grouping doesn't occur.
  279        buffer.set_group_interval(Duration::ZERO);
  280        buffer
  281    });
  282
  283    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  284    cx.add_window(|window, cx| {
  285        let mut editor = build_editor(buffer.clone(), window, cx);
  286
  287        // Start a new IME composition.
  288        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  289        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  291        assert_eq!(editor.text(cx), "äbcde");
  292        assert_eq!(
  293            editor.marked_text_ranges(cx),
  294            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  295        );
  296
  297        // Finalize IME composition.
  298        editor.replace_text_in_range(None, "ā", window, cx);
  299        assert_eq!(editor.text(cx), "ābcde");
  300        assert_eq!(editor.marked_text_ranges(cx), None);
  301
  302        // IME composition edits are grouped and are undone/redone at once.
  303        editor.undo(&Default::default(), window, cx);
  304        assert_eq!(editor.text(cx), "abcde");
  305        assert_eq!(editor.marked_text_ranges(cx), None);
  306        editor.redo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "ābcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309
  310        // Start a new IME composition.
  311        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  312        assert_eq!(
  313            editor.marked_text_ranges(cx),
  314            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  315        );
  316
  317        // Undoing during an IME composition cancels it.
  318        editor.undo(&Default::default(), window, cx);
  319        assert_eq!(editor.text(cx), "ābcde");
  320        assert_eq!(editor.marked_text_ranges(cx), None);
  321
  322        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  323        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  324        assert_eq!(editor.text(cx), "ābcdè");
  325        assert_eq!(
  326            editor.marked_text_ranges(cx),
  327            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  328        );
  329
  330        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  331        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  332        assert_eq!(editor.text(cx), "ābcdę");
  333        assert_eq!(editor.marked_text_ranges(cx), None);
  334
  335        // Start a new IME composition with multiple cursors.
  336        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  337            s.select_ranges([
  338                OffsetUtf16(1)..OffsetUtf16(1),
  339                OffsetUtf16(3)..OffsetUtf16(3),
  340                OffsetUtf16(5)..OffsetUtf16(5),
  341            ])
  342        });
  343        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  344        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  345        assert_eq!(
  346            editor.marked_text_ranges(cx),
  347            Some(vec![
  348                OffsetUtf16(0)..OffsetUtf16(3),
  349                OffsetUtf16(4)..OffsetUtf16(7),
  350                OffsetUtf16(8)..OffsetUtf16(11)
  351            ])
  352        );
  353
  354        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  355        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  356        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  357        assert_eq!(
  358            editor.marked_text_ranges(cx),
  359            Some(vec![
  360                OffsetUtf16(1)..OffsetUtf16(2),
  361                OffsetUtf16(5)..OffsetUtf16(6),
  362                OffsetUtf16(9)..OffsetUtf16(10)
  363            ])
  364        );
  365
  366        // Finalize IME composition with multiple cursors.
  367        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  368        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  369        assert_eq!(editor.marked_text_ranges(cx), None);
  370
  371        editor
  372    });
  373}
  374
  375#[gpui::test]
  376fn test_selection_with_mouse(cx: &mut TestAppContext) {
  377    init_test(cx, |_| {});
  378
  379    let editor = cx.add_window(|window, cx| {
  380        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  381        build_editor(buffer, window, cx)
  382    });
  383
  384    _ = editor.update(cx, |editor, window, cx| {
  385        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  386    });
  387    assert_eq!(
  388        editor
  389            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  390            .unwrap(),
  391        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  392    );
  393
  394    _ = editor.update(cx, |editor, window, cx| {
  395        editor.update_selection(
  396            DisplayPoint::new(DisplayRow(3), 3),
  397            0,
  398            gpui::Point::<f32>::default(),
  399            window,
  400            cx,
  401        );
  402    });
  403
  404    assert_eq!(
  405        editor
  406            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  407            .unwrap(),
  408        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  409    );
  410
  411    _ = editor.update(cx, |editor, window, cx| {
  412        editor.update_selection(
  413            DisplayPoint::new(DisplayRow(1), 1),
  414            0,
  415            gpui::Point::<f32>::default(),
  416            window,
  417            cx,
  418        );
  419    });
  420
  421    assert_eq!(
  422        editor
  423            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  424            .unwrap(),
  425        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  426    );
  427
  428    _ = editor.update(cx, |editor, window, cx| {
  429        editor.end_selection(window, cx);
  430        editor.update_selection(
  431            DisplayPoint::new(DisplayRow(3), 3),
  432            0,
  433            gpui::Point::<f32>::default(),
  434            window,
  435            cx,
  436        );
  437    });
  438
  439    assert_eq!(
  440        editor
  441            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  442            .unwrap(),
  443        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  444    );
  445
  446    _ = editor.update(cx, |editor, window, cx| {
  447        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  448        editor.update_selection(
  449            DisplayPoint::new(DisplayRow(0), 0),
  450            0,
  451            gpui::Point::<f32>::default(),
  452            window,
  453            cx,
  454        );
  455    });
  456
  457    assert_eq!(
  458        editor
  459            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  460            .unwrap(),
  461        [
  462            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  463            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  464        ]
  465    );
  466
  467    _ = editor.update(cx, |editor, window, cx| {
  468        editor.end_selection(window, cx);
  469    });
  470
  471    assert_eq!(
  472        editor
  473            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  474            .unwrap(),
  475        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  476    );
  477}
  478
  479#[gpui::test]
  480fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  481    init_test(cx, |_| {});
  482
  483    let editor = cx.add_window(|window, cx| {
  484        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  485        build_editor(buffer, window, cx)
  486    });
  487
  488    _ = editor.update(cx, |editor, window, cx| {
  489        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  490    });
  491
  492    _ = editor.update(cx, |editor, window, cx| {
  493        editor.end_selection(window, cx);
  494    });
  495
  496    _ = editor.update(cx, |editor, window, cx| {
  497        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  498    });
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.end_selection(window, cx);
  502    });
  503
  504    assert_eq!(
  505        editor
  506            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  507            .unwrap(),
  508        [
  509            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  510            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  511        ]
  512    );
  513
  514    _ = editor.update(cx, |editor, window, cx| {
  515        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.end_selection(window, cx);
  520    });
  521
  522    assert_eq!(
  523        editor
  524            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  525            .unwrap(),
  526        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  527    );
  528}
  529
  530#[gpui::test]
  531fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  532    init_test(cx, |_| {});
  533
  534    let editor = cx.add_window(|window, cx| {
  535        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  536        build_editor(buffer, window, cx)
  537    });
  538
  539    _ = editor.update(cx, |editor, window, cx| {
  540        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  541        assert_eq!(
  542            editor.selections.display_ranges(cx),
  543            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  544        );
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.update_selection(
  549            DisplayPoint::new(DisplayRow(3), 3),
  550            0,
  551            gpui::Point::<f32>::default(),
  552            window,
  553            cx,
  554        );
  555        assert_eq!(
  556            editor.selections.display_ranges(cx),
  557            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  558        );
  559    });
  560
  561    _ = editor.update(cx, |editor, window, cx| {
  562        editor.cancel(&Cancel, window, cx);
  563        editor.update_selection(
  564            DisplayPoint::new(DisplayRow(1), 1),
  565            0,
  566            gpui::Point::<f32>::default(),
  567            window,
  568            cx,
  569        );
  570        assert_eq!(
  571            editor.selections.display_ranges(cx),
  572            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  573        );
  574    });
  575}
  576
  577#[gpui::test]
  578fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  579    init_test(cx, |_| {});
  580
  581    let editor = cx.add_window(|window, cx| {
  582        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  583        build_editor(buffer, window, cx)
  584    });
  585
  586    _ = editor.update(cx, |editor, window, cx| {
  587        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  588        assert_eq!(
  589            editor.selections.display_ranges(cx),
  590            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  591        );
  592
  593        editor.move_down(&Default::default(), window, cx);
  594        assert_eq!(
  595            editor.selections.display_ranges(cx),
  596            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  597        );
  598
  599        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  603        );
  604
  605        editor.move_up(&Default::default(), window, cx);
  606        assert_eq!(
  607            editor.selections.display_ranges(cx),
  608            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  609        );
  610    });
  611}
  612
  613#[gpui::test]
  614fn test_clone(cx: &mut TestAppContext) {
  615    init_test(cx, |_| {});
  616
  617    let (text, selection_ranges) = marked_text_ranges(
  618        indoc! {"
  619            one
  620            two
  621            threeˇ
  622            four
  623            fiveˇ
  624        "},
  625        true,
  626    );
  627
  628    let editor = cx.add_window(|window, cx| {
  629        let buffer = MultiBuffer::build_simple(&text, cx);
  630        build_editor(buffer, window, cx)
  631    });
  632
  633    _ = editor.update(cx, |editor, window, cx| {
  634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  635            s.select_ranges(selection_ranges.clone())
  636        });
  637        editor.fold_creases(
  638            vec![
  639                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  640                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  641            ],
  642            true,
  643            window,
  644            cx,
  645        );
  646    });
  647
  648    let cloned_editor = editor
  649        .update(cx, |editor, _, cx| {
  650            cx.open_window(Default::default(), |window, cx| {
  651                cx.new(|cx| editor.clone(window, cx))
  652            })
  653        })
  654        .unwrap()
  655        .unwrap();
  656
  657    let snapshot = editor
  658        .update(cx, |e, window, cx| e.snapshot(window, cx))
  659        .unwrap();
  660    let cloned_snapshot = cloned_editor
  661        .update(cx, |e, window, cx| e.snapshot(window, cx))
  662        .unwrap();
  663
  664    assert_eq!(
  665        cloned_editor
  666            .update(cx, |e, _, cx| e.display_text(cx))
  667            .unwrap(),
  668        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  669    );
  670    assert_eq!(
  671        cloned_snapshot
  672            .folds_in_range(0..text.len())
  673            .collect::<Vec<_>>(),
  674        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  675    );
  676    assert_set_eq!(
  677        cloned_editor
  678            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  679            .unwrap(),
  680        editor
  681            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  682            .unwrap()
  683    );
  684    assert_set_eq!(
  685        cloned_editor
  686            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  687            .unwrap(),
  688        editor
  689            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  690            .unwrap()
  691    );
  692}
  693
  694#[gpui::test]
  695async fn test_navigation_history(cx: &mut TestAppContext) {
  696    init_test(cx, |_| {});
  697
  698    use workspace::item::Item;
  699
  700    let fs = FakeFs::new(cx.executor());
  701    let project = Project::test(fs, [], cx).await;
  702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  703    let pane = workspace
  704        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  705        .unwrap();
  706
  707    _ = workspace.update(cx, |_v, window, cx| {
  708        cx.new(|cx| {
  709            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  710            let mut editor = build_editor(buffer.clone(), window, cx);
  711            let handle = cx.entity();
  712            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  713
  714            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  715                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  716            }
  717
  718            // Move the cursor a small distance.
  719            // Nothing is added to the navigation history.
  720            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  721                s.select_display_ranges([
  722                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  723                ])
  724            });
  725            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  726                s.select_display_ranges([
  727                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  728                ])
  729            });
  730            assert!(pop_history(&mut editor, cx).is_none());
  731
  732            // Move the cursor a large distance.
  733            // The history can jump back to the previous position.
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  737                ])
  738            });
  739            let nav_entry = pop_history(&mut editor, cx).unwrap();
  740            editor.navigate(nav_entry.data.unwrap(), window, cx);
  741            assert_eq!(nav_entry.item.id(), cx.entity_id());
  742            assert_eq!(
  743                editor.selections.display_ranges(cx),
  744                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  745            );
  746            assert!(pop_history(&mut editor, cx).is_none());
  747
  748            // Move the cursor a small distance via the mouse.
  749            // Nothing is added to the navigation history.
  750            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  751            editor.end_selection(window, cx);
  752            assert_eq!(
  753                editor.selections.display_ranges(cx),
  754                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  755            );
  756            assert!(pop_history(&mut editor, cx).is_none());
  757
  758            // Move the cursor a large distance via the mouse.
  759            // The history can jump back to the previous position.
  760            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  761            editor.end_selection(window, cx);
  762            assert_eq!(
  763                editor.selections.display_ranges(cx),
  764                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  765            );
  766            let nav_entry = pop_history(&mut editor, cx).unwrap();
  767            editor.navigate(nav_entry.data.unwrap(), window, cx);
  768            assert_eq!(nav_entry.item.id(), cx.entity_id());
  769            assert_eq!(
  770                editor.selections.display_ranges(cx),
  771                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  772            );
  773            assert!(pop_history(&mut editor, cx).is_none());
  774
  775            // Set scroll position to check later
  776            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  777            let original_scroll_position = editor.scroll_manager.anchor();
  778
  779            // Jump to the end of the document and adjust scroll
  780            editor.move_to_end(&MoveToEnd, window, cx);
  781            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  782            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  783
  784            let nav_entry = pop_history(&mut editor, cx).unwrap();
  785            editor.navigate(nav_entry.data.unwrap(), window, cx);
  786            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  787
  788            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  789            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  790            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  791            let invalid_point = Point::new(9999, 0);
  792            editor.navigate(
  793                Box::new(NavigationData {
  794                    cursor_anchor: invalid_anchor,
  795                    cursor_position: invalid_point,
  796                    scroll_anchor: ScrollAnchor {
  797                        anchor: invalid_anchor,
  798                        offset: Default::default(),
  799                    },
  800                    scroll_top_row: invalid_point.row,
  801                }),
  802                window,
  803                cx,
  804            );
  805            assert_eq!(
  806                editor.selections.display_ranges(cx),
  807                &[editor.max_point(cx)..editor.max_point(cx)]
  808            );
  809            assert_eq!(
  810                editor.scroll_position(cx),
  811                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  812            );
  813
  814            editor
  815        })
  816    });
  817}
  818
  819#[gpui::test]
  820fn test_cancel(cx: &mut TestAppContext) {
  821    init_test(cx, |_| {});
  822
  823    let editor = cx.add_window(|window, cx| {
  824        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  825        build_editor(buffer, window, cx)
  826    });
  827
  828    _ = editor.update(cx, |editor, window, cx| {
  829        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  830        editor.update_selection(
  831            DisplayPoint::new(DisplayRow(1), 1),
  832            0,
  833            gpui::Point::<f32>::default(),
  834            window,
  835            cx,
  836        );
  837        editor.end_selection(window, cx);
  838
  839        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  840        editor.update_selection(
  841            DisplayPoint::new(DisplayRow(0), 3),
  842            0,
  843            gpui::Point::<f32>::default(),
  844            window,
  845            cx,
  846        );
  847        editor.end_selection(window, cx);
  848        assert_eq!(
  849            editor.selections.display_ranges(cx),
  850            [
  851                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  852                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  853            ]
  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(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  862        );
  863    });
  864
  865    _ = editor.update(cx, |editor, window, cx| {
  866        editor.cancel(&Cancel, window, cx);
  867        assert_eq!(
  868            editor.selections.display_ranges(cx),
  869            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  870        );
  871    });
  872}
  873
  874#[gpui::test]
  875fn test_fold_action(cx: &mut TestAppContext) {
  876    init_test(cx, |_| {});
  877
  878    let editor = cx.add_window(|window, cx| {
  879        let buffer = MultiBuffer::build_simple(
  880            &"
  881                impl Foo {
  882                    // Hello!
  883
  884                    fn a() {
  885                        1
  886                    }
  887
  888                    fn b() {
  889                        2
  890                    }
  891
  892                    fn c() {
  893                        3
  894                    }
  895                }
  896            "
  897            .unindent(),
  898            cx,
  899        );
  900        build_editor(buffer.clone(), window, cx)
  901    });
  902
  903    _ = editor.update(cx, |editor, window, cx| {
  904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  905            s.select_display_ranges([
  906                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  907            ]);
  908        });
  909        editor.fold(&Fold, window, cx);
  910        assert_eq!(
  911            editor.display_text(cx),
  912            "
  913                impl Foo {
  914                    // Hello!
  915
  916                    fn a() {
  917                        1
  918                    }
  919
  920                    fn b() {⋯
  921                    }
  922
  923                    fn c() {⋯
  924                    }
  925                }
  926            "
  927            .unindent(),
  928        );
  929
  930        editor.fold(&Fold, window, cx);
  931        assert_eq!(
  932            editor.display_text(cx),
  933            "
  934                impl Foo {⋯
  935                }
  936            "
  937            .unindent(),
  938        );
  939
  940        editor.unfold_lines(&UnfoldLines, window, cx);
  941        assert_eq!(
  942            editor.display_text(cx),
  943            "
  944                impl Foo {
  945                    // Hello!
  946
  947                    fn a() {
  948                        1
  949                    }
  950
  951                    fn b() {⋯
  952                    }
  953
  954                    fn c() {⋯
  955                    }
  956                }
  957            "
  958            .unindent(),
  959        );
  960
  961        editor.unfold_lines(&UnfoldLines, window, cx);
  962        assert_eq!(
  963            editor.display_text(cx),
  964            editor.buffer.read(cx).read(cx).text()
  965        );
  966    });
  967}
  968
  969#[gpui::test]
  970fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  971    init_test(cx, |_| {});
  972
  973    let editor = cx.add_window(|window, cx| {
  974        let buffer = MultiBuffer::build_simple(
  975            &"
  976                class Foo:
  977                    # Hello!
  978
  979                    def a():
  980                        print(1)
  981
  982                    def b():
  983                        print(2)
  984
  985                    def c():
  986                        print(3)
  987            "
  988            .unindent(),
  989            cx,
  990        );
  991        build_editor(buffer.clone(), window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  996            s.select_display_ranges([
  997                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  998            ]);
  999        });
 1000        editor.fold(&Fold, window, cx);
 1001        assert_eq!(
 1002            editor.display_text(cx),
 1003            "
 1004                class Foo:
 1005                    # Hello!
 1006
 1007                    def a():
 1008                        print(1)
 1009
 1010                    def b():⋯
 1011
 1012                    def c():⋯
 1013            "
 1014            .unindent(),
 1015        );
 1016
 1017        editor.fold(&Fold, window, cx);
 1018        assert_eq!(
 1019            editor.display_text(cx),
 1020            "
 1021                class Foo:⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.unfold_lines(&UnfoldLines, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:
 1031                    # Hello!
 1032
 1033                    def a():
 1034                        print(1)
 1035
 1036                    def b():⋯
 1037
 1038                    def c():⋯
 1039            "
 1040            .unindent(),
 1041        );
 1042
 1043        editor.unfold_lines(&UnfoldLines, window, cx);
 1044        assert_eq!(
 1045            editor.display_text(cx),
 1046            editor.buffer.read(cx).read(cx).text()
 1047        );
 1048    });
 1049}
 1050
 1051#[gpui::test]
 1052fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1053    init_test(cx, |_| {});
 1054
 1055    let editor = cx.add_window(|window, cx| {
 1056        let buffer = MultiBuffer::build_simple(
 1057            &"
 1058                class Foo:
 1059                    # Hello!
 1060
 1061                    def a():
 1062                        print(1)
 1063
 1064                    def b():
 1065                        print(2)
 1066
 1067
 1068                    def c():
 1069                        print(3)
 1070
 1071
 1072            "
 1073            .unindent(),
 1074            cx,
 1075        );
 1076        build_editor(buffer.clone(), window, cx)
 1077    });
 1078
 1079    _ = editor.update(cx, |editor, window, cx| {
 1080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1081            s.select_display_ranges([
 1082                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1083            ]);
 1084        });
 1085        editor.fold(&Fold, window, cx);
 1086        assert_eq!(
 1087            editor.display_text(cx),
 1088            "
 1089                class Foo:
 1090                    # Hello!
 1091
 1092                    def a():
 1093                        print(1)
 1094
 1095                    def b():⋯
 1096
 1097
 1098                    def c():⋯
 1099
 1100
 1101            "
 1102            .unindent(),
 1103        );
 1104
 1105        editor.fold(&Fold, window, cx);
 1106        assert_eq!(
 1107            editor.display_text(cx),
 1108            "
 1109                class Foo:⋯
 1110
 1111
 1112            "
 1113            .unindent(),
 1114        );
 1115
 1116        editor.unfold_lines(&UnfoldLines, window, cx);
 1117        assert_eq!(
 1118            editor.display_text(cx),
 1119            "
 1120                class Foo:
 1121                    # Hello!
 1122
 1123                    def a():
 1124                        print(1)
 1125
 1126                    def b():⋯
 1127
 1128
 1129                    def c():⋯
 1130
 1131
 1132            "
 1133            .unindent(),
 1134        );
 1135
 1136        editor.unfold_lines(&UnfoldLines, window, cx);
 1137        assert_eq!(
 1138            editor.display_text(cx),
 1139            editor.buffer.read(cx).read(cx).text()
 1140        );
 1141    });
 1142}
 1143
 1144#[gpui::test]
 1145fn test_fold_at_level(cx: &mut TestAppContext) {
 1146    init_test(cx, |_| {});
 1147
 1148    let editor = cx.add_window(|window, cx| {
 1149        let buffer = MultiBuffer::build_simple(
 1150            &"
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():
 1158                        print(2)
 1159
 1160
 1161                class Bar:
 1162                    # World!
 1163
 1164                    def a():
 1165                        print(1)
 1166
 1167                    def b():
 1168                        print(2)
 1169
 1170
 1171            "
 1172            .unindent(),
 1173            cx,
 1174        );
 1175        build_editor(buffer.clone(), window, cx)
 1176    });
 1177
 1178    _ = editor.update(cx, |editor, window, cx| {
 1179        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1180        assert_eq!(
 1181            editor.display_text(cx),
 1182            "
 1183                class Foo:
 1184                    # Hello!
 1185
 1186                    def a():⋯
 1187
 1188                    def b():⋯
 1189
 1190
 1191                class Bar:
 1192                    # World!
 1193
 1194                    def a():⋯
 1195
 1196                    def b():⋯
 1197
 1198
 1199            "
 1200            .unindent(),
 1201        );
 1202
 1203        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1204        assert_eq!(
 1205            editor.display_text(cx),
 1206            "
 1207                class Foo:⋯
 1208
 1209
 1210                class Bar:⋯
 1211
 1212
 1213            "
 1214            .unindent(),
 1215        );
 1216
 1217        editor.unfold_all(&UnfoldAll, window, cx);
 1218        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1219        assert_eq!(
 1220            editor.display_text(cx),
 1221            "
 1222                class Foo:
 1223                    # Hello!
 1224
 1225                    def a():
 1226                        print(1)
 1227
 1228                    def b():
 1229                        print(2)
 1230
 1231
 1232                class Bar:
 1233                    # World!
 1234
 1235                    def a():
 1236                        print(1)
 1237
 1238                    def b():
 1239                        print(2)
 1240
 1241
 1242            "
 1243            .unindent(),
 1244        );
 1245
 1246        assert_eq!(
 1247            editor.display_text(cx),
 1248            editor.buffer.read(cx).read(cx).text()
 1249        );
 1250    });
 1251}
 1252
 1253#[gpui::test]
 1254fn test_move_cursor(cx: &mut TestAppContext) {
 1255    init_test(cx, |_| {});
 1256
 1257    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1258    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1259
 1260    buffer.update(cx, |buffer, cx| {
 1261        buffer.edit(
 1262            vec![
 1263                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1264                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1265            ],
 1266            None,
 1267            cx,
 1268        );
 1269    });
 1270    _ = editor.update(cx, |editor, window, cx| {
 1271        assert_eq!(
 1272            editor.selections.display_ranges(cx),
 1273            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1274        );
 1275
 1276        editor.move_down(&MoveDown, window, cx);
 1277        assert_eq!(
 1278            editor.selections.display_ranges(cx),
 1279            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1280        );
 1281
 1282        editor.move_right(&MoveRight, window, cx);
 1283        assert_eq!(
 1284            editor.selections.display_ranges(cx),
 1285            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1286        );
 1287
 1288        editor.move_left(&MoveLeft, window, cx);
 1289        assert_eq!(
 1290            editor.selections.display_ranges(cx),
 1291            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1292        );
 1293
 1294        editor.move_up(&MoveUp, window, cx);
 1295        assert_eq!(
 1296            editor.selections.display_ranges(cx),
 1297            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1298        );
 1299
 1300        editor.move_to_end(&MoveToEnd, window, cx);
 1301        assert_eq!(
 1302            editor.selections.display_ranges(cx),
 1303            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1304        );
 1305
 1306        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1307        assert_eq!(
 1308            editor.selections.display_ranges(cx),
 1309            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1310        );
 1311
 1312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1313            s.select_display_ranges([
 1314                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1315            ]);
 1316        });
 1317        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1318        assert_eq!(
 1319            editor.selections.display_ranges(cx),
 1320            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1321        );
 1322
 1323        editor.select_to_end(&SelectToEnd, window, cx);
 1324        assert_eq!(
 1325            editor.selections.display_ranges(cx),
 1326            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1327        );
 1328    });
 1329}
 1330
 1331#[gpui::test]
 1332fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1333    init_test(cx, |_| {});
 1334
 1335    let editor = cx.add_window(|window, cx| {
 1336        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1337        build_editor(buffer.clone(), window, cx)
 1338    });
 1339
 1340    assert_eq!('🟥'.len_utf8(), 4);
 1341    assert_eq!('α'.len_utf8(), 2);
 1342
 1343    _ = editor.update(cx, |editor, window, cx| {
 1344        editor.fold_creases(
 1345            vec![
 1346                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1347                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1349            ],
 1350            true,
 1351            window,
 1352            cx,
 1353        );
 1354        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1355
 1356        editor.move_right(&MoveRight, window, cx);
 1357        assert_eq!(
 1358            editor.selections.display_ranges(cx),
 1359            &[empty_range(0, "🟥".len())]
 1360        );
 1361        editor.move_right(&MoveRight, window, cx);
 1362        assert_eq!(
 1363            editor.selections.display_ranges(cx),
 1364            &[empty_range(0, "🟥🟧".len())]
 1365        );
 1366        editor.move_right(&MoveRight, window, cx);
 1367        assert_eq!(
 1368            editor.selections.display_ranges(cx),
 1369            &[empty_range(0, "🟥🟧⋯".len())]
 1370        );
 1371
 1372        editor.move_down(&MoveDown, window, cx);
 1373        assert_eq!(
 1374            editor.selections.display_ranges(cx),
 1375            &[empty_range(1, "ab⋯e".len())]
 1376        );
 1377        editor.move_left(&MoveLeft, window, cx);
 1378        assert_eq!(
 1379            editor.selections.display_ranges(cx),
 1380            &[empty_range(1, "ab⋯".len())]
 1381        );
 1382        editor.move_left(&MoveLeft, window, cx);
 1383        assert_eq!(
 1384            editor.selections.display_ranges(cx),
 1385            &[empty_range(1, "ab".len())]
 1386        );
 1387        editor.move_left(&MoveLeft, window, cx);
 1388        assert_eq!(
 1389            editor.selections.display_ranges(cx),
 1390            &[empty_range(1, "a".len())]
 1391        );
 1392
 1393        editor.move_down(&MoveDown, window, cx);
 1394        assert_eq!(
 1395            editor.selections.display_ranges(cx),
 1396            &[empty_range(2, "α".len())]
 1397        );
 1398        editor.move_right(&MoveRight, window, cx);
 1399        assert_eq!(
 1400            editor.selections.display_ranges(cx),
 1401            &[empty_range(2, "αβ".len())]
 1402        );
 1403        editor.move_right(&MoveRight, window, cx);
 1404        assert_eq!(
 1405            editor.selections.display_ranges(cx),
 1406            &[empty_range(2, "αβ⋯".len())]
 1407        );
 1408        editor.move_right(&MoveRight, window, cx);
 1409        assert_eq!(
 1410            editor.selections.display_ranges(cx),
 1411            &[empty_range(2, "αβ⋯ε".len())]
 1412        );
 1413
 1414        editor.move_up(&MoveUp, window, cx);
 1415        assert_eq!(
 1416            editor.selections.display_ranges(cx),
 1417            &[empty_range(1, "ab⋯e".len())]
 1418        );
 1419        editor.move_down(&MoveDown, window, cx);
 1420        assert_eq!(
 1421            editor.selections.display_ranges(cx),
 1422            &[empty_range(2, "αβ⋯ε".len())]
 1423        );
 1424        editor.move_up(&MoveUp, window, cx);
 1425        assert_eq!(
 1426            editor.selections.display_ranges(cx),
 1427            &[empty_range(1, "ab⋯e".len())]
 1428        );
 1429
 1430        editor.move_up(&MoveUp, window, cx);
 1431        assert_eq!(
 1432            editor.selections.display_ranges(cx),
 1433            &[empty_range(0, "🟥🟧".len())]
 1434        );
 1435        editor.move_left(&MoveLeft, window, cx);
 1436        assert_eq!(
 1437            editor.selections.display_ranges(cx),
 1438            &[empty_range(0, "🟥".len())]
 1439        );
 1440        editor.move_left(&MoveLeft, window, cx);
 1441        assert_eq!(
 1442            editor.selections.display_ranges(cx),
 1443            &[empty_range(0, "".len())]
 1444        );
 1445    });
 1446}
 1447
 1448#[gpui::test]
 1449fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1450    init_test(cx, |_| {});
 1451
 1452    let editor = cx.add_window(|window, cx| {
 1453        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1454        build_editor(buffer.clone(), window, cx)
 1455    });
 1456    _ = editor.update(cx, |editor, window, cx| {
 1457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1458            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1459        });
 1460
 1461        // moving above start of document should move selection to start of document,
 1462        // but the next move down should still be at the original goal_x
 1463        editor.move_up(&MoveUp, window, cx);
 1464        assert_eq!(
 1465            editor.selections.display_ranges(cx),
 1466            &[empty_range(0, "".len())]
 1467        );
 1468
 1469        editor.move_down(&MoveDown, window, cx);
 1470        assert_eq!(
 1471            editor.selections.display_ranges(cx),
 1472            &[empty_range(1, "abcd".len())]
 1473        );
 1474
 1475        editor.move_down(&MoveDown, window, cx);
 1476        assert_eq!(
 1477            editor.selections.display_ranges(cx),
 1478            &[empty_range(2, "αβγ".len())]
 1479        );
 1480
 1481        editor.move_down(&MoveDown, window, cx);
 1482        assert_eq!(
 1483            editor.selections.display_ranges(cx),
 1484            &[empty_range(3, "abcd".len())]
 1485        );
 1486
 1487        editor.move_down(&MoveDown, window, cx);
 1488        assert_eq!(
 1489            editor.selections.display_ranges(cx),
 1490            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1491        );
 1492
 1493        // moving past end of document should not change goal_x
 1494        editor.move_down(&MoveDown, window, cx);
 1495        assert_eq!(
 1496            editor.selections.display_ranges(cx),
 1497            &[empty_range(5, "".len())]
 1498        );
 1499
 1500        editor.move_down(&MoveDown, window, cx);
 1501        assert_eq!(
 1502            editor.selections.display_ranges(cx),
 1503            &[empty_range(5, "".len())]
 1504        );
 1505
 1506        editor.move_up(&MoveUp, window, cx);
 1507        assert_eq!(
 1508            editor.selections.display_ranges(cx),
 1509            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1510        );
 1511
 1512        editor.move_up(&MoveUp, window, cx);
 1513        assert_eq!(
 1514            editor.selections.display_ranges(cx),
 1515            &[empty_range(3, "abcd".len())]
 1516        );
 1517
 1518        editor.move_up(&MoveUp, window, cx);
 1519        assert_eq!(
 1520            editor.selections.display_ranges(cx),
 1521            &[empty_range(2, "αβγ".len())]
 1522        );
 1523    });
 1524}
 1525
 1526#[gpui::test]
 1527fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1528    init_test(cx, |_| {});
 1529    let move_to_beg = MoveToBeginningOfLine {
 1530        stop_at_soft_wraps: true,
 1531        stop_at_indent: true,
 1532    };
 1533
 1534    let delete_to_beg = DeleteToBeginningOfLine {
 1535        stop_at_indent: false,
 1536    };
 1537
 1538    let move_to_end = MoveToEndOfLine {
 1539        stop_at_soft_wraps: true,
 1540    };
 1541
 1542    let editor = cx.add_window(|window, cx| {
 1543        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1544        build_editor(buffer, window, cx)
 1545    });
 1546    _ = editor.update(cx, |editor, window, cx| {
 1547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1548            s.select_display_ranges([
 1549                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1550                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1551            ]);
 1552        });
 1553    });
 1554
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1557        assert_eq!(
 1558            editor.selections.display_ranges(cx),
 1559            &[
 1560                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1561                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1562            ]
 1563        );
 1564    });
 1565
 1566    _ = editor.update(cx, |editor, window, cx| {
 1567        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1568        assert_eq!(
 1569            editor.selections.display_ranges(cx),
 1570            &[
 1571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1572                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1573            ]
 1574        );
 1575    });
 1576
 1577    _ = editor.update(cx, |editor, window, cx| {
 1578        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1579        assert_eq!(
 1580            editor.selections.display_ranges(cx),
 1581            &[
 1582                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1583                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1584            ]
 1585        );
 1586    });
 1587
 1588    _ = editor.update(cx, |editor, window, cx| {
 1589        editor.move_to_end_of_line(&move_to_end, window, cx);
 1590        assert_eq!(
 1591            editor.selections.display_ranges(cx),
 1592            &[
 1593                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1594                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1595            ]
 1596        );
 1597    });
 1598
 1599    // Moving to the end of line again is a no-op.
 1600    _ = editor.update(cx, |editor, window, cx| {
 1601        editor.move_to_end_of_line(&move_to_end, window, cx);
 1602        assert_eq!(
 1603            editor.selections.display_ranges(cx),
 1604            &[
 1605                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1606                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1607            ]
 1608        );
 1609    });
 1610
 1611    _ = editor.update(cx, |editor, window, cx| {
 1612        editor.move_left(&MoveLeft, window, cx);
 1613        editor.select_to_beginning_of_line(
 1614            &SelectToBeginningOfLine {
 1615                stop_at_soft_wraps: true,
 1616                stop_at_indent: true,
 1617            },
 1618            window,
 1619            cx,
 1620        );
 1621        assert_eq!(
 1622            editor.selections.display_ranges(cx),
 1623            &[
 1624                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1625                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1626            ]
 1627        );
 1628    });
 1629
 1630    _ = editor.update(cx, |editor, window, cx| {
 1631        editor.select_to_beginning_of_line(
 1632            &SelectToBeginningOfLine {
 1633                stop_at_soft_wraps: true,
 1634                stop_at_indent: true,
 1635            },
 1636            window,
 1637            cx,
 1638        );
 1639        assert_eq!(
 1640            editor.selections.display_ranges(cx),
 1641            &[
 1642                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1643                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1644            ]
 1645        );
 1646    });
 1647
 1648    _ = editor.update(cx, |editor, window, cx| {
 1649        editor.select_to_beginning_of_line(
 1650            &SelectToBeginningOfLine {
 1651                stop_at_soft_wraps: true,
 1652                stop_at_indent: true,
 1653            },
 1654            window,
 1655            cx,
 1656        );
 1657        assert_eq!(
 1658            editor.selections.display_ranges(cx),
 1659            &[
 1660                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1661                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1662            ]
 1663        );
 1664    });
 1665
 1666    _ = editor.update(cx, |editor, window, cx| {
 1667        editor.select_to_end_of_line(
 1668            &SelectToEndOfLine {
 1669                stop_at_soft_wraps: true,
 1670            },
 1671            window,
 1672            cx,
 1673        );
 1674        assert_eq!(
 1675            editor.selections.display_ranges(cx),
 1676            &[
 1677                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1678                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1679            ]
 1680        );
 1681    });
 1682
 1683    _ = editor.update(cx, |editor, window, cx| {
 1684        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1685        assert_eq!(editor.display_text(cx), "ab\n  de");
 1686        assert_eq!(
 1687            editor.selections.display_ranges(cx),
 1688            &[
 1689                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1690                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1691            ]
 1692        );
 1693    });
 1694
 1695    _ = editor.update(cx, |editor, window, cx| {
 1696        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1697        assert_eq!(editor.display_text(cx), "\n");
 1698        assert_eq!(
 1699            editor.selections.display_ranges(cx),
 1700            &[
 1701                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1702                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1703            ]
 1704        );
 1705    });
 1706}
 1707
 1708#[gpui::test]
 1709fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1710    init_test(cx, |_| {});
 1711    let move_to_beg = MoveToBeginningOfLine {
 1712        stop_at_soft_wraps: false,
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: false,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724
 1725    _ = editor.update(cx, |editor, window, cx| {
 1726        editor.set_wrap_width(Some(140.0.into()), cx);
 1727
 1728        // We expect the following lines after wrapping
 1729        // ```
 1730        // thequickbrownfox
 1731        // jumpedoverthelazydo
 1732        // gs
 1733        // ```
 1734        // The final `gs` was soft-wrapped onto a new line.
 1735        assert_eq!(
 1736            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1737            editor.display_text(cx),
 1738        );
 1739
 1740        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1741        // Start the cursor at the `k` on the first line
 1742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1743            s.select_display_ranges([
 1744                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1745            ]);
 1746        });
 1747
 1748        // Moving to the beginning of the line should put us at the beginning of the line.
 1749        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1750        assert_eq!(
 1751            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1752            editor.selections.display_ranges(cx)
 1753        );
 1754
 1755        // Moving to the end of the line should put us at the end of the line.
 1756        editor.move_to_end_of_line(&move_to_end, window, cx);
 1757        assert_eq!(
 1758            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1759            editor.selections.display_ranges(cx)
 1760        );
 1761
 1762        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1763        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1764        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1765            s.select_display_ranges([
 1766                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1767            ]);
 1768        });
 1769
 1770        // Moving to the beginning of the line should put us at the start of the second line of
 1771        // display text, i.e., the `j`.
 1772        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1773        assert_eq!(
 1774            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1775            editor.selections.display_ranges(cx)
 1776        );
 1777
 1778        // Moving to the beginning of the line again should be a no-op.
 1779        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1780        assert_eq!(
 1781            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1782            editor.selections.display_ranges(cx)
 1783        );
 1784
 1785        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1786        // next display line.
 1787        editor.move_to_end_of_line(&move_to_end, window, cx);
 1788        assert_eq!(
 1789            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1790            editor.selections.display_ranges(cx)
 1791        );
 1792
 1793        // Moving to the end of the line again should be a no-op.
 1794        editor.move_to_end_of_line(&move_to_end, window, cx);
 1795        assert_eq!(
 1796            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1797            editor.selections.display_ranges(cx)
 1798        );
 1799    });
 1800}
 1801
 1802#[gpui::test]
 1803fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1804    init_test(cx, |_| {});
 1805
 1806    let move_to_beg = MoveToBeginningOfLine {
 1807        stop_at_soft_wraps: true,
 1808        stop_at_indent: true,
 1809    };
 1810
 1811    let select_to_beg = SelectToBeginningOfLine {
 1812        stop_at_soft_wraps: true,
 1813        stop_at_indent: true,
 1814    };
 1815
 1816    let delete_to_beg = DeleteToBeginningOfLine {
 1817        stop_at_indent: true,
 1818    };
 1819
 1820    let move_to_end = MoveToEndOfLine {
 1821        stop_at_soft_wraps: false,
 1822    };
 1823
 1824    let editor = cx.add_window(|window, cx| {
 1825        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1826        build_editor(buffer, window, cx)
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1831            s.select_display_ranges([
 1832                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1833                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1834            ]);
 1835        });
 1836
 1837        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1838        // and the second cursor at the first non-whitespace character in the line.
 1839        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1840        assert_eq!(
 1841            editor.selections.display_ranges(cx),
 1842            &[
 1843                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1844                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1845            ]
 1846        );
 1847
 1848        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1849        // and should move the second cursor to the beginning of the line.
 1850        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1851        assert_eq!(
 1852            editor.selections.display_ranges(cx),
 1853            &[
 1854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1855                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1856            ]
 1857        );
 1858
 1859        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1860        // and should move the second cursor back to the first non-whitespace character in the line.
 1861        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1862        assert_eq!(
 1863            editor.selections.display_ranges(cx),
 1864            &[
 1865                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1866                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1867            ]
 1868        );
 1869
 1870        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1871        // and to the first non-whitespace character in the line for the second cursor.
 1872        editor.move_to_end_of_line(&move_to_end, window, cx);
 1873        editor.move_left(&MoveLeft, window, cx);
 1874        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1875        assert_eq!(
 1876            editor.selections.display_ranges(cx),
 1877            &[
 1878                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1879                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1880            ]
 1881        );
 1882
 1883        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1884        // and should select to the beginning of the line for the second cursor.
 1885        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1886        assert_eq!(
 1887            editor.selections.display_ranges(cx),
 1888            &[
 1889                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1890                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1891            ]
 1892        );
 1893
 1894        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1895        // and should delete to the first non-whitespace character in the line for the second cursor.
 1896        editor.move_to_end_of_line(&move_to_end, window, cx);
 1897        editor.move_left(&MoveLeft, window, cx);
 1898        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1899        assert_eq!(editor.text(cx), "c\n  f");
 1900    });
 1901}
 1902
 1903#[gpui::test]
 1904fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1905    init_test(cx, |_| {});
 1906
 1907    let editor = cx.add_window(|window, cx| {
 1908        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1909        build_editor(buffer, window, cx)
 1910    });
 1911    _ = editor.update(cx, |editor, window, cx| {
 1912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1913            s.select_display_ranges([
 1914                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1915                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1916            ])
 1917        });
 1918        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1919        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1920
 1921        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1922        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1923
 1924        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1925        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1926
 1927        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1928        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1929
 1930        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1931        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1932
 1933        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1934        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1935
 1936        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1937        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1938
 1939        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1940        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1941
 1942        editor.move_right(&MoveRight, window, cx);
 1943        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1944        assert_selection_ranges(
 1945            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1946            editor,
 1947            cx,
 1948        );
 1949
 1950        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1951        assert_selection_ranges(
 1952            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1953            editor,
 1954            cx,
 1955        );
 1956
 1957        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1958        assert_selection_ranges(
 1959            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1960            editor,
 1961            cx,
 1962        );
 1963    });
 1964}
 1965
 1966#[gpui::test]
 1967fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1968    init_test(cx, |_| {});
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.set_wrap_width(Some(140.0.into()), cx);
 1977        assert_eq!(
 1978            editor.display_text(cx),
 1979            "use one::{\n    two::three::\n    four::five\n};"
 1980        );
 1981
 1982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1983            s.select_display_ranges([
 1984                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1985            ]);
 1986        });
 1987
 1988        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1989        assert_eq!(
 1990            editor.selections.display_ranges(cx),
 1991            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1992        );
 1993
 1994        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1995        assert_eq!(
 1996            editor.selections.display_ranges(cx),
 1997            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1998        );
 1999
 2000        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2001        assert_eq!(
 2002            editor.selections.display_ranges(cx),
 2003            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2004        );
 2005
 2006        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2007        assert_eq!(
 2008            editor.selections.display_ranges(cx),
 2009            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2010        );
 2011
 2012        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2013        assert_eq!(
 2014            editor.selections.display_ranges(cx),
 2015            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2016        );
 2017
 2018        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2019        assert_eq!(
 2020            editor.selections.display_ranges(cx),
 2021            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2022        );
 2023    });
 2024}
 2025
 2026#[gpui::test]
 2027async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2028    init_test(cx, |_| {});
 2029    let mut cx = EditorTestContext::new(cx).await;
 2030
 2031    let line_height = cx.editor(|editor, window, _| {
 2032        editor
 2033            .style()
 2034            .unwrap()
 2035            .text
 2036            .line_height_in_pixels(window.rem_size())
 2037    });
 2038    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2039
 2040    cx.set_state(
 2041        &r#"ˇone
 2042        two
 2043
 2044        three
 2045        fourˇ
 2046        five
 2047
 2048        six"#
 2049            .unindent(),
 2050    );
 2051
 2052    cx.update_editor(|editor, window, cx| {
 2053        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2054    });
 2055    cx.assert_editor_state(
 2056        &r#"one
 2057        two
 2058        ˇ
 2059        three
 2060        four
 2061        five
 2062        ˇ
 2063        six"#
 2064            .unindent(),
 2065    );
 2066
 2067    cx.update_editor(|editor, window, cx| {
 2068        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2069    });
 2070    cx.assert_editor_state(
 2071        &r#"one
 2072        two
 2073
 2074        three
 2075        four
 2076        five
 2077        ˇ
 2078        sixˇ"#
 2079            .unindent(),
 2080    );
 2081
 2082    cx.update_editor(|editor, window, cx| {
 2083        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2084    });
 2085    cx.assert_editor_state(
 2086        &r#"one
 2087        two
 2088
 2089        three
 2090        four
 2091        five
 2092
 2093        sixˇ"#
 2094            .unindent(),
 2095    );
 2096
 2097    cx.update_editor(|editor, window, cx| {
 2098        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2099    });
 2100    cx.assert_editor_state(
 2101        &r#"one
 2102        two
 2103
 2104        three
 2105        four
 2106        five
 2107        ˇ
 2108        six"#
 2109            .unindent(),
 2110    );
 2111
 2112    cx.update_editor(|editor, window, cx| {
 2113        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2114    });
 2115    cx.assert_editor_state(
 2116        &r#"one
 2117        two
 2118        ˇ
 2119        three
 2120        four
 2121        five
 2122
 2123        six"#
 2124            .unindent(),
 2125    );
 2126
 2127    cx.update_editor(|editor, window, cx| {
 2128        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2129    });
 2130    cx.assert_editor_state(
 2131        &r#"ˇone
 2132        two
 2133
 2134        three
 2135        four
 2136        five
 2137
 2138        six"#
 2139            .unindent(),
 2140    );
 2141}
 2142
 2143#[gpui::test]
 2144async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2145    init_test(cx, |_| {});
 2146    let mut cx = EditorTestContext::new(cx).await;
 2147    let line_height = cx.editor(|editor, window, _| {
 2148        editor
 2149            .style()
 2150            .unwrap()
 2151            .text
 2152            .line_height_in_pixels(window.rem_size())
 2153    });
 2154    let window = cx.window;
 2155    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2156
 2157    cx.set_state(
 2158        r#"ˇone
 2159        two
 2160        three
 2161        four
 2162        five
 2163        six
 2164        seven
 2165        eight
 2166        nine
 2167        ten
 2168        "#,
 2169    );
 2170
 2171    cx.update_editor(|editor, window, cx| {
 2172        assert_eq!(
 2173            editor.snapshot(window, cx).scroll_position(),
 2174            gpui::Point::new(0., 0.)
 2175        );
 2176        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2177        assert_eq!(
 2178            editor.snapshot(window, cx).scroll_position(),
 2179            gpui::Point::new(0., 3.)
 2180        );
 2181        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2182        assert_eq!(
 2183            editor.snapshot(window, cx).scroll_position(),
 2184            gpui::Point::new(0., 6.)
 2185        );
 2186        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2187        assert_eq!(
 2188            editor.snapshot(window, cx).scroll_position(),
 2189            gpui::Point::new(0., 3.)
 2190        );
 2191
 2192        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2193        assert_eq!(
 2194            editor.snapshot(window, cx).scroll_position(),
 2195            gpui::Point::new(0., 1.)
 2196        );
 2197        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2198        assert_eq!(
 2199            editor.snapshot(window, cx).scroll_position(),
 2200            gpui::Point::new(0., 3.)
 2201        );
 2202    });
 2203}
 2204
 2205#[gpui::test]
 2206async fn test_autoscroll(cx: &mut TestAppContext) {
 2207    init_test(cx, |_| {});
 2208    let mut cx = EditorTestContext::new(cx).await;
 2209
 2210    let line_height = cx.update_editor(|editor, window, cx| {
 2211        editor.set_vertical_scroll_margin(2, cx);
 2212        editor
 2213            .style()
 2214            .unwrap()
 2215            .text
 2216            .line_height_in_pixels(window.rem_size())
 2217    });
 2218    let window = cx.window;
 2219    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2220
 2221    cx.set_state(
 2222        r#"ˇone
 2223            two
 2224            three
 2225            four
 2226            five
 2227            six
 2228            seven
 2229            eight
 2230            nine
 2231            ten
 2232        "#,
 2233    );
 2234    cx.update_editor(|editor, window, cx| {
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 0.0)
 2238        );
 2239    });
 2240
 2241    // Add a cursor below the visible area. Since both cursors cannot fit
 2242    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2243    // allows the vertical scroll margin below that cursor.
 2244    cx.update_editor(|editor, window, cx| {
 2245        editor.change_selections(Default::default(), window, cx, |selections| {
 2246            selections.select_ranges([
 2247                Point::new(0, 0)..Point::new(0, 0),
 2248                Point::new(6, 0)..Point::new(6, 0),
 2249            ]);
 2250        })
 2251    });
 2252    cx.update_editor(|editor, window, cx| {
 2253        assert_eq!(
 2254            editor.snapshot(window, cx).scroll_position(),
 2255            gpui::Point::new(0., 3.0)
 2256        );
 2257    });
 2258
 2259    // Move down. The editor cursor scrolls down to track the newest cursor.
 2260    cx.update_editor(|editor, window, cx| {
 2261        editor.move_down(&Default::default(), window, cx);
 2262    });
 2263    cx.update_editor(|editor, window, cx| {
 2264        assert_eq!(
 2265            editor.snapshot(window, cx).scroll_position(),
 2266            gpui::Point::new(0., 4.0)
 2267        );
 2268    });
 2269
 2270    // Add a cursor above the visible area. Since both cursors fit on screen,
 2271    // the editor scrolls to show both.
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.change_selections(Default::default(), window, cx, |selections| {
 2274            selections.select_ranges([
 2275                Point::new(1, 0)..Point::new(1, 0),
 2276                Point::new(6, 0)..Point::new(6, 0),
 2277            ]);
 2278        })
 2279    });
 2280    cx.update_editor(|editor, window, cx| {
 2281        assert_eq!(
 2282            editor.snapshot(window, cx).scroll_position(),
 2283            gpui::Point::new(0., 1.0)
 2284        );
 2285    });
 2286}
 2287
 2288#[gpui::test]
 2289async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2290    init_test(cx, |_| {});
 2291    let mut cx = EditorTestContext::new(cx).await;
 2292
 2293    let line_height = cx.editor(|editor, window, _cx| {
 2294        editor
 2295            .style()
 2296            .unwrap()
 2297            .text
 2298            .line_height_in_pixels(window.rem_size())
 2299    });
 2300    let window = cx.window;
 2301    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2302    cx.set_state(
 2303        &r#"
 2304        ˇone
 2305        two
 2306        threeˇ
 2307        four
 2308        five
 2309        six
 2310        seven
 2311        eight
 2312        nine
 2313        ten
 2314        "#
 2315        .unindent(),
 2316    );
 2317
 2318    cx.update_editor(|editor, window, cx| {
 2319        editor.move_page_down(&MovePageDown::default(), window, cx)
 2320    });
 2321    cx.assert_editor_state(
 2322        &r#"
 2323        one
 2324        two
 2325        three
 2326        ˇfour
 2327        five
 2328        sixˇ
 2329        seven
 2330        eight
 2331        nine
 2332        ten
 2333        "#
 2334        .unindent(),
 2335    );
 2336
 2337    cx.update_editor(|editor, window, cx| {
 2338        editor.move_page_down(&MovePageDown::default(), window, cx)
 2339    });
 2340    cx.assert_editor_state(
 2341        &r#"
 2342        one
 2343        two
 2344        three
 2345        four
 2346        five
 2347        six
 2348        ˇseven
 2349        eight
 2350        nineˇ
 2351        ten
 2352        "#
 2353        .unindent(),
 2354    );
 2355
 2356    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2357    cx.assert_editor_state(
 2358        &r#"
 2359        one
 2360        two
 2361        three
 2362        ˇfour
 2363        five
 2364        sixˇ
 2365        seven
 2366        eight
 2367        nine
 2368        ten
 2369        "#
 2370        .unindent(),
 2371    );
 2372
 2373    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2374    cx.assert_editor_state(
 2375        &r#"
 2376        ˇone
 2377        two
 2378        threeˇ
 2379        four
 2380        five
 2381        six
 2382        seven
 2383        eight
 2384        nine
 2385        ten
 2386        "#
 2387        .unindent(),
 2388    );
 2389
 2390    // Test select collapsing
 2391    cx.update_editor(|editor, window, cx| {
 2392        editor.move_page_down(&MovePageDown::default(), window, cx);
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395    });
 2396    cx.assert_editor_state(
 2397        &r#"
 2398        one
 2399        two
 2400        three
 2401        four
 2402        five
 2403        six
 2404        seven
 2405        eight
 2406        nine
 2407        ˇten
 2408        ˇ"#
 2409        .unindent(),
 2410    );
 2411}
 2412
 2413#[gpui::test]
 2414async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2415    init_test(cx, |_| {});
 2416    let mut cx = EditorTestContext::new(cx).await;
 2417    cx.set_state("one «two threeˇ» four");
 2418    cx.update_editor(|editor, window, cx| {
 2419        editor.delete_to_beginning_of_line(
 2420            &DeleteToBeginningOfLine {
 2421                stop_at_indent: false,
 2422            },
 2423            window,
 2424            cx,
 2425        );
 2426        assert_eq!(editor.text(cx), " four");
 2427    });
 2428}
 2429
 2430#[gpui::test]
 2431fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2432    init_test(cx, |_| {});
 2433
 2434    let editor = cx.add_window(|window, cx| {
 2435        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2436        build_editor(buffer.clone(), window, cx)
 2437    });
 2438
 2439    _ = editor.update(cx, |editor, window, cx| {
 2440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2441            s.select_display_ranges([
 2442                // an empty selection - the preceding word fragment is deleted
 2443                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2444                // characters selected - they are deleted
 2445                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2446            ])
 2447        });
 2448        editor.delete_to_previous_word_start(
 2449            &DeleteToPreviousWordStart {
 2450                ignore_newlines: false,
 2451            },
 2452            window,
 2453            cx,
 2454        );
 2455        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2456    });
 2457
 2458    _ = editor.update(cx, |editor, window, cx| {
 2459        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2460            s.select_display_ranges([
 2461                // an empty selection - the following word fragment is deleted
 2462                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2463                // characters selected - they are deleted
 2464                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2465            ])
 2466        });
 2467        editor.delete_to_next_word_end(
 2468            &DeleteToNextWordEnd {
 2469                ignore_newlines: false,
 2470            },
 2471            window,
 2472            cx,
 2473        );
 2474        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let editor = cx.add_window(|window, cx| {
 2483        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2484        build_editor(buffer.clone(), window, cx)
 2485    });
 2486    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2487        ignore_newlines: false,
 2488    };
 2489    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2490        ignore_newlines: true,
 2491    };
 2492
 2493    _ = editor.update(cx, |editor, window, cx| {
 2494        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2495            s.select_display_ranges([
 2496                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2497            ])
 2498        });
 2499        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2500        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2501        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2502        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2503        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2504        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2505        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2506        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2507        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2508        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2509        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2510        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2511    });
 2512}
 2513
 2514#[gpui::test]
 2515fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2516    init_test(cx, |_| {});
 2517
 2518    let editor = cx.add_window(|window, cx| {
 2519        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2520        build_editor(buffer.clone(), window, cx)
 2521    });
 2522    let del_to_next_word_end = DeleteToNextWordEnd {
 2523        ignore_newlines: false,
 2524    };
 2525    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2526        ignore_newlines: true,
 2527    };
 2528
 2529    _ = editor.update(cx, |editor, window, cx| {
 2530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2531            s.select_display_ranges([
 2532                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2533            ])
 2534        });
 2535        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2536        assert_eq!(
 2537            editor.buffer.read(cx).read(cx).text(),
 2538            "one\n   two\nthree\n   four"
 2539        );
 2540        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2541        assert_eq!(
 2542            editor.buffer.read(cx).read(cx).text(),
 2543            "\n   two\nthree\n   four"
 2544        );
 2545        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2546        assert_eq!(
 2547            editor.buffer.read(cx).read(cx).text(),
 2548            "two\nthree\n   four"
 2549        );
 2550        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2551        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2552        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2553        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2554        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2555        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2556    });
 2557}
 2558
 2559#[gpui::test]
 2560fn test_newline(cx: &mut TestAppContext) {
 2561    init_test(cx, |_| {});
 2562
 2563    let editor = cx.add_window(|window, cx| {
 2564        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2565        build_editor(buffer.clone(), window, cx)
 2566    });
 2567
 2568    _ = editor.update(cx, |editor, window, cx| {
 2569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2570            s.select_display_ranges([
 2571                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2572                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2573                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2574            ])
 2575        });
 2576
 2577        editor.newline(&Newline, window, cx);
 2578        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2579    });
 2580}
 2581
 2582#[gpui::test]
 2583fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2584    init_test(cx, |_| {});
 2585
 2586    let editor = cx.add_window(|window, cx| {
 2587        let buffer = MultiBuffer::build_simple(
 2588            "
 2589                a
 2590                b(
 2591                    X
 2592                )
 2593                c(
 2594                    X
 2595                )
 2596            "
 2597            .unindent()
 2598            .as_str(),
 2599            cx,
 2600        );
 2601        let mut editor = build_editor(buffer.clone(), window, cx);
 2602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2603            s.select_ranges([
 2604                Point::new(2, 4)..Point::new(2, 5),
 2605                Point::new(5, 4)..Point::new(5, 5),
 2606            ])
 2607        });
 2608        editor
 2609    });
 2610
 2611    _ = editor.update(cx, |editor, window, cx| {
 2612        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2613        editor.buffer.update(cx, |buffer, cx| {
 2614            buffer.edit(
 2615                [
 2616                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2617                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2618                ],
 2619                None,
 2620                cx,
 2621            );
 2622            assert_eq!(
 2623                buffer.read(cx).text(),
 2624                "
 2625                    a
 2626                    b()
 2627                    c()
 2628                "
 2629                .unindent()
 2630            );
 2631        });
 2632        assert_eq!(
 2633            editor.selections.ranges(cx),
 2634            &[
 2635                Point::new(1, 2)..Point::new(1, 2),
 2636                Point::new(2, 2)..Point::new(2, 2),
 2637            ],
 2638        );
 2639
 2640        editor.newline(&Newline, window, cx);
 2641        assert_eq!(
 2642            editor.text(cx),
 2643            "
 2644                a
 2645                b(
 2646                )
 2647                c(
 2648                )
 2649            "
 2650            .unindent()
 2651        );
 2652
 2653        // The selections are moved after the inserted newlines
 2654        assert_eq!(
 2655            editor.selections.ranges(cx),
 2656            &[
 2657                Point::new(2, 0)..Point::new(2, 0),
 2658                Point::new(4, 0)..Point::new(4, 0),
 2659            ],
 2660        );
 2661    });
 2662}
 2663
 2664#[gpui::test]
 2665async fn test_newline_above(cx: &mut TestAppContext) {
 2666    init_test(cx, |settings| {
 2667        settings.defaults.tab_size = NonZeroU32::new(4)
 2668    });
 2669
 2670    let language = Arc::new(
 2671        Language::new(
 2672            LanguageConfig::default(),
 2673            Some(tree_sitter_rust::LANGUAGE.into()),
 2674        )
 2675        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2676        .unwrap(),
 2677    );
 2678
 2679    let mut cx = EditorTestContext::new(cx).await;
 2680    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2681    cx.set_state(indoc! {"
 2682        const a: ˇA = (
 2683 2684                «const_functionˇ»(ˇ),
 2685                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2686 2687        ˇ);ˇ
 2688    "});
 2689
 2690    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2691    cx.assert_editor_state(indoc! {"
 2692        ˇ
 2693        const a: A = (
 2694            ˇ
 2695            (
 2696                ˇ
 2697                ˇ
 2698                const_function(),
 2699                ˇ
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                something_else,
 2704                ˇ
 2705            )
 2706            ˇ
 2707            ˇ
 2708        );
 2709    "});
 2710}
 2711
 2712#[gpui::test]
 2713async fn test_newline_below(cx: &mut TestAppContext) {
 2714    init_test(cx, |settings| {
 2715        settings.defaults.tab_size = NonZeroU32::new(4)
 2716    });
 2717
 2718    let language = Arc::new(
 2719        Language::new(
 2720            LanguageConfig::default(),
 2721            Some(tree_sitter_rust::LANGUAGE.into()),
 2722        )
 2723        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2724        .unwrap(),
 2725    );
 2726
 2727    let mut cx = EditorTestContext::new(cx).await;
 2728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2729    cx.set_state(indoc! {"
 2730        const a: ˇA = (
 2731 2732                «const_functionˇ»(ˇ),
 2733                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2734 2735        ˇ);ˇ
 2736    "});
 2737
 2738    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2739    cx.assert_editor_state(indoc! {"
 2740        const a: A = (
 2741            ˇ
 2742            (
 2743                ˇ
 2744                const_function(),
 2745                ˇ
 2746                ˇ
 2747                something_else,
 2748                ˇ
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752            )
 2753            ˇ
 2754        );
 2755        ˇ
 2756        ˇ
 2757    "});
 2758}
 2759
 2760#[gpui::test]
 2761async fn test_newline_comments(cx: &mut TestAppContext) {
 2762    init_test(cx, |settings| {
 2763        settings.defaults.tab_size = NonZeroU32::new(4)
 2764    });
 2765
 2766    let language = Arc::new(Language::new(
 2767        LanguageConfig {
 2768            line_comments: vec!["// ".into()],
 2769            ..LanguageConfig::default()
 2770        },
 2771        None,
 2772    ));
 2773    {
 2774        let mut cx = EditorTestContext::new(cx).await;
 2775        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2776        cx.set_state(indoc! {"
 2777        // Fooˇ
 2778    "});
 2779
 2780        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2781        cx.assert_editor_state(indoc! {"
 2782        // Foo
 2783        // ˇ
 2784    "});
 2785        // Ensure that we add comment prefix when existing line contains space
 2786        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2787        cx.assert_editor_state(
 2788            indoc! {"
 2789        // Foo
 2790        //s
 2791        // ˇ
 2792    "}
 2793            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2794            .as_str(),
 2795        );
 2796        // Ensure that we add comment prefix when existing line does not contain space
 2797        cx.set_state(indoc! {"
 2798        // Foo
 2799        //ˇ
 2800    "});
 2801        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2802        cx.assert_editor_state(indoc! {"
 2803        // Foo
 2804        //
 2805        // ˇ
 2806    "});
 2807        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2808        cx.set_state(indoc! {"
 2809        ˇ// Foo
 2810    "});
 2811        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2812        cx.assert_editor_state(indoc! {"
 2813
 2814        ˇ// Foo
 2815    "});
 2816    }
 2817    // Ensure that comment continuations can be disabled.
 2818    update_test_language_settings(cx, |settings| {
 2819        settings.defaults.extend_comment_on_newline = Some(false);
 2820    });
 2821    let mut cx = EditorTestContext::new(cx).await;
 2822    cx.set_state(indoc! {"
 2823        // Fooˇ
 2824    "});
 2825    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2826    cx.assert_editor_state(indoc! {"
 2827        // Foo
 2828        ˇ
 2829    "});
 2830}
 2831
 2832#[gpui::test]
 2833async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2834    init_test(cx, |settings| {
 2835        settings.defaults.tab_size = NonZeroU32::new(4)
 2836    });
 2837
 2838    let language = Arc::new(Language::new(
 2839        LanguageConfig {
 2840            line_comments: vec!["// ".into(), "/// ".into()],
 2841            ..LanguageConfig::default()
 2842        },
 2843        None,
 2844    ));
 2845    {
 2846        let mut cx = EditorTestContext::new(cx).await;
 2847        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2848        cx.set_state(indoc! {"
 2849        //ˇ
 2850    "});
 2851        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2852        cx.assert_editor_state(indoc! {"
 2853        //
 2854        // ˇ
 2855    "});
 2856
 2857        cx.set_state(indoc! {"
 2858        ///ˇ
 2859    "});
 2860        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2861        cx.assert_editor_state(indoc! {"
 2862        ///
 2863        /// ˇ
 2864    "});
 2865    }
 2866}
 2867
 2868#[gpui::test]
 2869async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2870    init_test(cx, |settings| {
 2871        settings.defaults.tab_size = NonZeroU32::new(4)
 2872    });
 2873
 2874    let language = Arc::new(
 2875        Language::new(
 2876            LanguageConfig {
 2877                documentation: Some(language::DocumentationConfig {
 2878                    start: "/**".into(),
 2879                    end: "*/".into(),
 2880                    prefix: "* ".into(),
 2881                    tab_size: NonZeroU32::new(1).unwrap(),
 2882                }),
 2883
 2884                ..LanguageConfig::default()
 2885            },
 2886            Some(tree_sitter_rust::LANGUAGE.into()),
 2887        )
 2888        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2889        .unwrap(),
 2890    );
 2891
 2892    {
 2893        let mut cx = EditorTestContext::new(cx).await;
 2894        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2895        cx.set_state(indoc! {"
 2896        /**ˇ
 2897    "});
 2898
 2899        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2900        cx.assert_editor_state(indoc! {"
 2901        /**
 2902         * ˇ
 2903    "});
 2904        // Ensure that if cursor is before the comment start,
 2905        // we do not actually insert a comment prefix.
 2906        cx.set_state(indoc! {"
 2907        ˇ/**
 2908    "});
 2909        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2910        cx.assert_editor_state(indoc! {"
 2911
 2912        ˇ/**
 2913    "});
 2914        // Ensure that if cursor is between it doesn't add comment prefix.
 2915        cx.set_state(indoc! {"
 2916        /*ˇ*
 2917    "});
 2918        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2919        cx.assert_editor_state(indoc! {"
 2920        /*
 2921        ˇ*
 2922    "});
 2923        // Ensure that if suffix exists on same line after cursor it adds new line.
 2924        cx.set_state(indoc! {"
 2925        /**ˇ*/
 2926    "});
 2927        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2928        cx.assert_editor_state(indoc! {"
 2929        /**
 2930         * ˇ
 2931         */
 2932    "});
 2933        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2934        cx.set_state(indoc! {"
 2935        /**ˇ */
 2936    "});
 2937        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2938        cx.assert_editor_state(indoc! {"
 2939        /**
 2940         * ˇ
 2941         */
 2942    "});
 2943        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2944        cx.set_state(indoc! {"
 2945        /** ˇ*/
 2946    "});
 2947        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2948        cx.assert_editor_state(
 2949            indoc! {"
 2950        /**s
 2951         * ˇ
 2952         */
 2953    "}
 2954            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2955            .as_str(),
 2956        );
 2957        // Ensure that delimiter space is preserved when newline on already
 2958        // spaced delimiter.
 2959        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2960        cx.assert_editor_state(
 2961            indoc! {"
 2962        /**s
 2963         *s
 2964         * ˇ
 2965         */
 2966    "}
 2967            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2968            .as_str(),
 2969        );
 2970        // Ensure that delimiter space is preserved when space is not
 2971        // on existing delimiter.
 2972        cx.set_state(indoc! {"
 2973        /**
 2974 2975         */
 2976    "});
 2977        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2978        cx.assert_editor_state(indoc! {"
 2979        /**
 2980         *
 2981         * ˇ
 2982         */
 2983    "});
 2984        // Ensure that if suffix exists on same line after cursor it
 2985        // doesn't add extra new line if prefix is not on same line.
 2986        cx.set_state(indoc! {"
 2987        /**
 2988        ˇ*/
 2989    "});
 2990        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2991        cx.assert_editor_state(indoc! {"
 2992        /**
 2993
 2994        ˇ*/
 2995    "});
 2996        // Ensure that it detects suffix after existing prefix.
 2997        cx.set_state(indoc! {"
 2998        /**ˇ/
 2999    "});
 3000        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3001        cx.assert_editor_state(indoc! {"
 3002        /**
 3003        ˇ/
 3004    "});
 3005        // Ensure that if suffix exists on same line before
 3006        // cursor it does not add comment prefix.
 3007        cx.set_state(indoc! {"
 3008        /** */ˇ
 3009    "});
 3010        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3011        cx.assert_editor_state(indoc! {"
 3012        /** */
 3013        ˇ
 3014    "});
 3015        // Ensure that if suffix exists on same line before
 3016        // cursor it does not add comment prefix.
 3017        cx.set_state(indoc! {"
 3018        /**
 3019         *
 3020         */ˇ
 3021    "});
 3022        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3023        cx.assert_editor_state(indoc! {"
 3024        /**
 3025         *
 3026         */
 3027         ˇ
 3028    "});
 3029
 3030        // Ensure that inline comment followed by code
 3031        // doesn't add comment prefix on newline
 3032        cx.set_state(indoc! {"
 3033        /** */ textˇ
 3034    "});
 3035        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3036        cx.assert_editor_state(indoc! {"
 3037        /** */ text
 3038        ˇ
 3039    "});
 3040
 3041        // Ensure that text after comment end tag
 3042        // doesn't add comment prefix on newline
 3043        cx.set_state(indoc! {"
 3044        /**
 3045         *
 3046         */ˇtext
 3047    "});
 3048        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3049        cx.assert_editor_state(indoc! {"
 3050        /**
 3051         *
 3052         */
 3053         ˇtext
 3054    "});
 3055
 3056        // Ensure if not comment block it doesn't
 3057        // add comment prefix on newline
 3058        cx.set_state(indoc! {"
 3059        * textˇ
 3060    "});
 3061        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3062        cx.assert_editor_state(indoc! {"
 3063        * text
 3064        ˇ
 3065    "});
 3066    }
 3067    // Ensure that comment continuations can be disabled.
 3068    update_test_language_settings(cx, |settings| {
 3069        settings.defaults.extend_comment_on_newline = Some(false);
 3070    });
 3071    let mut cx = EditorTestContext::new(cx).await;
 3072    cx.set_state(indoc! {"
 3073        /**ˇ
 3074    "});
 3075    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3076    cx.assert_editor_state(indoc! {"
 3077        /**
 3078        ˇ
 3079    "});
 3080}
 3081
 3082#[gpui::test]
 3083fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3084    init_test(cx, |_| {});
 3085
 3086    let editor = cx.add_window(|window, cx| {
 3087        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3088        let mut editor = build_editor(buffer.clone(), window, cx);
 3089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3090            s.select_ranges([3..4, 11..12, 19..20])
 3091        });
 3092        editor
 3093    });
 3094
 3095    _ = editor.update(cx, |editor, window, cx| {
 3096        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3097        editor.buffer.update(cx, |buffer, cx| {
 3098            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3099            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3100        });
 3101        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3102
 3103        editor.insert("Z", window, cx);
 3104        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3105
 3106        // The selections are moved after the inserted characters
 3107        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3108    });
 3109}
 3110
 3111#[gpui::test]
 3112async fn test_tab(cx: &mut TestAppContext) {
 3113    init_test(cx, |settings| {
 3114        settings.defaults.tab_size = NonZeroU32::new(3)
 3115    });
 3116
 3117    let mut cx = EditorTestContext::new(cx).await;
 3118    cx.set_state(indoc! {"
 3119        ˇabˇc
 3120        ˇ🏀ˇ🏀ˇefg
 3121 3122    "});
 3123    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3124    cx.assert_editor_state(indoc! {"
 3125           ˇab ˇc
 3126           ˇ🏀  ˇ🏀  ˇefg
 3127        d  ˇ
 3128    "});
 3129
 3130    cx.set_state(indoc! {"
 3131        a
 3132        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3133    "});
 3134    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3135    cx.assert_editor_state(indoc! {"
 3136        a
 3137           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3138    "});
 3139}
 3140
 3141#[gpui::test]
 3142async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3143    init_test(cx, |_| {});
 3144
 3145    let mut cx = EditorTestContext::new(cx).await;
 3146    let language = Arc::new(
 3147        Language::new(
 3148            LanguageConfig::default(),
 3149            Some(tree_sitter_rust::LANGUAGE.into()),
 3150        )
 3151        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3152        .unwrap(),
 3153    );
 3154    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3155
 3156    // test when all cursors are not at suggested indent
 3157    // then simply move to their suggested indent location
 3158    cx.set_state(indoc! {"
 3159        const a: B = (
 3160            c(
 3161        ˇ
 3162        ˇ    )
 3163        );
 3164    "});
 3165    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3166    cx.assert_editor_state(indoc! {"
 3167        const a: B = (
 3168            c(
 3169                ˇ
 3170            ˇ)
 3171        );
 3172    "});
 3173
 3174    // test cursor already at suggested indent not moving when
 3175    // other cursors are yet to reach their suggested indents
 3176    cx.set_state(indoc! {"
 3177        ˇ
 3178        const a: B = (
 3179            c(
 3180                d(
 3181        ˇ
 3182                )
 3183        ˇ
 3184        ˇ    )
 3185        );
 3186    "});
 3187    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3188    cx.assert_editor_state(indoc! {"
 3189        ˇ
 3190        const a: B = (
 3191            c(
 3192                d(
 3193                    ˇ
 3194                )
 3195                ˇ
 3196            ˇ)
 3197        );
 3198    "});
 3199    // test when all cursors are at suggested indent then tab is inserted
 3200    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3201    cx.assert_editor_state(indoc! {"
 3202            ˇ
 3203        const a: B = (
 3204            c(
 3205                d(
 3206                        ˇ
 3207                )
 3208                    ˇ
 3209                ˇ)
 3210        );
 3211    "});
 3212
 3213    // test when current indent is less than suggested indent,
 3214    // we adjust line to match suggested indent and move cursor to it
 3215    //
 3216    // when no other cursor is at word boundary, all of them should move
 3217    cx.set_state(indoc! {"
 3218        const a: B = (
 3219            c(
 3220                d(
 3221        ˇ
 3222        ˇ   )
 3223        ˇ   )
 3224        );
 3225    "});
 3226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3227    cx.assert_editor_state(indoc! {"
 3228        const a: B = (
 3229            c(
 3230                d(
 3231                    ˇ
 3232                ˇ)
 3233            ˇ)
 3234        );
 3235    "});
 3236
 3237    // test when current indent is less than suggested indent,
 3238    // we adjust line to match suggested indent and move cursor to it
 3239    //
 3240    // when some other cursor is at word boundary, it should not move
 3241    cx.set_state(indoc! {"
 3242        const a: B = (
 3243            c(
 3244                d(
 3245        ˇ
 3246        ˇ   )
 3247           ˇ)
 3248        );
 3249    "});
 3250    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3251    cx.assert_editor_state(indoc! {"
 3252        const a: B = (
 3253            c(
 3254                d(
 3255                    ˇ
 3256                ˇ)
 3257            ˇ)
 3258        );
 3259    "});
 3260
 3261    // test when current indent is more than suggested indent,
 3262    // we just move cursor to current indent instead of suggested indent
 3263    //
 3264    // when no other cursor is at word boundary, all of them should move
 3265    cx.set_state(indoc! {"
 3266        const a: B = (
 3267            c(
 3268                d(
 3269        ˇ
 3270        ˇ                )
 3271        ˇ   )
 3272        );
 3273    "});
 3274    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3275    cx.assert_editor_state(indoc! {"
 3276        const a: B = (
 3277            c(
 3278                d(
 3279                    ˇ
 3280                        ˇ)
 3281            ˇ)
 3282        );
 3283    "});
 3284    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3285    cx.assert_editor_state(indoc! {"
 3286        const a: B = (
 3287            c(
 3288                d(
 3289                        ˇ
 3290                            ˇ)
 3291                ˇ)
 3292        );
 3293    "});
 3294
 3295    // test when current indent is more than suggested indent,
 3296    // we just move cursor to current indent instead of suggested indent
 3297    //
 3298    // when some other cursor is at word boundary, it doesn't move
 3299    cx.set_state(indoc! {"
 3300        const a: B = (
 3301            c(
 3302                d(
 3303        ˇ
 3304        ˇ                )
 3305            ˇ)
 3306        );
 3307    "});
 3308    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3309    cx.assert_editor_state(indoc! {"
 3310        const a: B = (
 3311            c(
 3312                d(
 3313                    ˇ
 3314                        ˇ)
 3315            ˇ)
 3316        );
 3317    "});
 3318
 3319    // handle auto-indent when there are multiple cursors on the same line
 3320    cx.set_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323        ˇ    ˇ
 3324        ˇ    )
 3325        );
 3326    "});
 3327    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3328    cx.assert_editor_state(indoc! {"
 3329        const a: B = (
 3330            c(
 3331                ˇ
 3332            ˇ)
 3333        );
 3334    "});
 3335}
 3336
 3337#[gpui::test]
 3338async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3339    init_test(cx, |settings| {
 3340        settings.defaults.tab_size = NonZeroU32::new(3)
 3341    });
 3342
 3343    let mut cx = EditorTestContext::new(cx).await;
 3344    cx.set_state(indoc! {"
 3345         ˇ
 3346        \t ˇ
 3347        \t  ˇ
 3348        \t   ˇ
 3349         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3350    "});
 3351
 3352    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3353    cx.assert_editor_state(indoc! {"
 3354           ˇ
 3355        \t   ˇ
 3356        \t   ˇ
 3357        \t      ˇ
 3358         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3359    "});
 3360}
 3361
 3362#[gpui::test]
 3363async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3364    init_test(cx, |settings| {
 3365        settings.defaults.tab_size = NonZeroU32::new(4)
 3366    });
 3367
 3368    let language = Arc::new(
 3369        Language::new(
 3370            LanguageConfig::default(),
 3371            Some(tree_sitter_rust::LANGUAGE.into()),
 3372        )
 3373        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3374        .unwrap(),
 3375    );
 3376
 3377    let mut cx = EditorTestContext::new(cx).await;
 3378    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3379    cx.set_state(indoc! {"
 3380        fn a() {
 3381            if b {
 3382        \t ˇc
 3383            }
 3384        }
 3385    "});
 3386
 3387    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3388    cx.assert_editor_state(indoc! {"
 3389        fn a() {
 3390            if b {
 3391                ˇc
 3392            }
 3393        }
 3394    "});
 3395}
 3396
 3397#[gpui::test]
 3398async fn test_indent_outdent(cx: &mut TestAppContext) {
 3399    init_test(cx, |settings| {
 3400        settings.defaults.tab_size = NonZeroU32::new(4);
 3401    });
 3402
 3403    let mut cx = EditorTestContext::new(cx).await;
 3404
 3405    cx.set_state(indoc! {"
 3406          «oneˇ» «twoˇ»
 3407        three
 3408         four
 3409    "});
 3410    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3411    cx.assert_editor_state(indoc! {"
 3412            «oneˇ» «twoˇ»
 3413        three
 3414         four
 3415    "});
 3416
 3417    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        «oneˇ» «twoˇ»
 3420        three
 3421         four
 3422    "});
 3423
 3424    // select across line ending
 3425    cx.set_state(indoc! {"
 3426        one two
 3427        t«hree
 3428        ˇ» four
 3429    "});
 3430    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3431    cx.assert_editor_state(indoc! {"
 3432        one two
 3433            t«hree
 3434        ˇ» four
 3435    "});
 3436
 3437    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3438    cx.assert_editor_state(indoc! {"
 3439        one two
 3440        t«hree
 3441        ˇ» four
 3442    "});
 3443
 3444    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3445    cx.set_state(indoc! {"
 3446        one two
 3447        ˇthree
 3448            four
 3449    "});
 3450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3451    cx.assert_editor_state(indoc! {"
 3452        one two
 3453            ˇthree
 3454            four
 3455    "});
 3456
 3457    cx.set_state(indoc! {"
 3458        one two
 3459        ˇ    three
 3460            four
 3461    "});
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        one two
 3465        ˇthree
 3466            four
 3467    "});
 3468}
 3469
 3470#[gpui::test]
 3471async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3472    init_test(cx, |settings| {
 3473        settings.defaults.hard_tabs = Some(true);
 3474    });
 3475
 3476    let mut cx = EditorTestContext::new(cx).await;
 3477
 3478    // select two ranges on one line
 3479    cx.set_state(indoc! {"
 3480        «oneˇ» «twoˇ»
 3481        three
 3482        four
 3483    "});
 3484    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3485    cx.assert_editor_state(indoc! {"
 3486        \t«oneˇ» «twoˇ»
 3487        three
 3488        four
 3489    "});
 3490    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3491    cx.assert_editor_state(indoc! {"
 3492        \t\t«oneˇ» «twoˇ»
 3493        three
 3494        four
 3495    "});
 3496    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3497    cx.assert_editor_state(indoc! {"
 3498        \t«oneˇ» «twoˇ»
 3499        three
 3500        four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504        «oneˇ» «twoˇ»
 3505        three
 3506        four
 3507    "});
 3508
 3509    // select across a line ending
 3510    cx.set_state(indoc! {"
 3511        one two
 3512        t«hree
 3513        ˇ»four
 3514    "});
 3515    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3516    cx.assert_editor_state(indoc! {"
 3517        one two
 3518        \tt«hree
 3519        ˇ»four
 3520    "});
 3521    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3522    cx.assert_editor_state(indoc! {"
 3523        one two
 3524        \t\tt«hree
 3525        ˇ»four
 3526    "});
 3527    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3528    cx.assert_editor_state(indoc! {"
 3529        one two
 3530        \tt«hree
 3531        ˇ»four
 3532    "});
 3533    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3534    cx.assert_editor_state(indoc! {"
 3535        one two
 3536        t«hree
 3537        ˇ»four
 3538    "});
 3539
 3540    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3541    cx.set_state(indoc! {"
 3542        one two
 3543        ˇthree
 3544        four
 3545    "});
 3546    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3547    cx.assert_editor_state(indoc! {"
 3548        one two
 3549        ˇthree
 3550        four
 3551    "});
 3552    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3553    cx.assert_editor_state(indoc! {"
 3554        one two
 3555        \tˇthree
 3556        four
 3557    "});
 3558    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3559    cx.assert_editor_state(indoc! {"
 3560        one two
 3561        ˇthree
 3562        four
 3563    "});
 3564}
 3565
 3566#[gpui::test]
 3567fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3568    init_test(cx, |settings| {
 3569        settings.languages.extend([
 3570            (
 3571                "TOML".into(),
 3572                LanguageSettingsContent {
 3573                    tab_size: NonZeroU32::new(2),
 3574                    ..Default::default()
 3575                },
 3576            ),
 3577            (
 3578                "Rust".into(),
 3579                LanguageSettingsContent {
 3580                    tab_size: NonZeroU32::new(4),
 3581                    ..Default::default()
 3582                },
 3583            ),
 3584        ]);
 3585    });
 3586
 3587    let toml_language = Arc::new(Language::new(
 3588        LanguageConfig {
 3589            name: "TOML".into(),
 3590            ..Default::default()
 3591        },
 3592        None,
 3593    ));
 3594    let rust_language = Arc::new(Language::new(
 3595        LanguageConfig {
 3596            name: "Rust".into(),
 3597            ..Default::default()
 3598        },
 3599        None,
 3600    ));
 3601
 3602    let toml_buffer =
 3603        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3604    let rust_buffer =
 3605        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3606    let multibuffer = cx.new(|cx| {
 3607        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3608        multibuffer.push_excerpts(
 3609            toml_buffer.clone(),
 3610            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3611            cx,
 3612        );
 3613        multibuffer.push_excerpts(
 3614            rust_buffer.clone(),
 3615            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3616            cx,
 3617        );
 3618        multibuffer
 3619    });
 3620
 3621    cx.add_window(|window, cx| {
 3622        let mut editor = build_editor(multibuffer, window, cx);
 3623
 3624        assert_eq!(
 3625            editor.text(cx),
 3626            indoc! {"
 3627                a = 1
 3628                b = 2
 3629
 3630                const c: usize = 3;
 3631            "}
 3632        );
 3633
 3634        select_ranges(
 3635            &mut editor,
 3636            indoc! {"
 3637                «aˇ» = 1
 3638                b = 2
 3639
 3640                «const c:ˇ» usize = 3;
 3641            "},
 3642            window,
 3643            cx,
 3644        );
 3645
 3646        editor.tab(&Tab, window, cx);
 3647        assert_text_with_selections(
 3648            &mut editor,
 3649            indoc! {"
 3650                  «aˇ» = 1
 3651                b = 2
 3652
 3653                    «const c:ˇ» usize = 3;
 3654            "},
 3655            cx,
 3656        );
 3657        editor.backtab(&Backtab, window, cx);
 3658        assert_text_with_selections(
 3659            &mut editor,
 3660            indoc! {"
 3661                «aˇ» = 1
 3662                b = 2
 3663
 3664                «const c:ˇ» usize = 3;
 3665            "},
 3666            cx,
 3667        );
 3668
 3669        editor
 3670    });
 3671}
 3672
 3673#[gpui::test]
 3674async fn test_backspace(cx: &mut TestAppContext) {
 3675    init_test(cx, |_| {});
 3676
 3677    let mut cx = EditorTestContext::new(cx).await;
 3678
 3679    // Basic backspace
 3680    cx.set_state(indoc! {"
 3681        onˇe two three
 3682        fou«rˇ» five six
 3683        seven «ˇeight nine
 3684        »ten
 3685    "});
 3686    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3687    cx.assert_editor_state(indoc! {"
 3688        oˇe two three
 3689        fouˇ five six
 3690        seven ˇten
 3691    "});
 3692
 3693    // Test backspace inside and around indents
 3694    cx.set_state(indoc! {"
 3695        zero
 3696            ˇone
 3697                ˇtwo
 3698            ˇ ˇ ˇ  three
 3699        ˇ  ˇ  four
 3700    "});
 3701    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3702    cx.assert_editor_state(indoc! {"
 3703        zero
 3704        ˇone
 3705            ˇtwo
 3706        ˇ  threeˇ  four
 3707    "});
 3708}
 3709
 3710#[gpui::test]
 3711async fn test_delete(cx: &mut TestAppContext) {
 3712    init_test(cx, |_| {});
 3713
 3714    let mut cx = EditorTestContext::new(cx).await;
 3715    cx.set_state(indoc! {"
 3716        onˇe two three
 3717        fou«rˇ» five six
 3718        seven «ˇeight nine
 3719        »ten
 3720    "});
 3721    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3722    cx.assert_editor_state(indoc! {"
 3723        onˇ two three
 3724        fouˇ five six
 3725        seven ˇten
 3726    "});
 3727}
 3728
 3729#[gpui::test]
 3730fn test_delete_line(cx: &mut TestAppContext) {
 3731    init_test(cx, |_| {});
 3732
 3733    let editor = cx.add_window(|window, cx| {
 3734        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3735        build_editor(buffer, window, cx)
 3736    });
 3737    _ = editor.update(cx, |editor, window, cx| {
 3738        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3739            s.select_display_ranges([
 3740                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3741                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3742                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3743            ])
 3744        });
 3745        editor.delete_line(&DeleteLine, window, cx);
 3746        assert_eq!(editor.display_text(cx), "ghi");
 3747        assert_eq!(
 3748            editor.selections.display_ranges(cx),
 3749            vec![
 3750                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3751                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3752            ]
 3753        );
 3754    });
 3755
 3756    let editor = cx.add_window(|window, cx| {
 3757        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3758        build_editor(buffer, window, cx)
 3759    });
 3760    _ = editor.update(cx, |editor, window, cx| {
 3761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3762            s.select_display_ranges([
 3763                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3764            ])
 3765        });
 3766        editor.delete_line(&DeleteLine, window, cx);
 3767        assert_eq!(editor.display_text(cx), "ghi\n");
 3768        assert_eq!(
 3769            editor.selections.display_ranges(cx),
 3770            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3771        );
 3772    });
 3773}
 3774
 3775#[gpui::test]
 3776fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3777    init_test(cx, |_| {});
 3778
 3779    cx.add_window(|window, cx| {
 3780        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3781        let mut editor = build_editor(buffer.clone(), window, cx);
 3782        let buffer = buffer.read(cx).as_singleton().unwrap();
 3783
 3784        assert_eq!(
 3785            editor.selections.ranges::<Point>(cx),
 3786            &[Point::new(0, 0)..Point::new(0, 0)]
 3787        );
 3788
 3789        // When on single line, replace newline at end by space
 3790        editor.join_lines(&JoinLines, window, cx);
 3791        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3792        assert_eq!(
 3793            editor.selections.ranges::<Point>(cx),
 3794            &[Point::new(0, 3)..Point::new(0, 3)]
 3795        );
 3796
 3797        // When multiple lines are selected, remove newlines that are spanned by the selection
 3798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3799            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3800        });
 3801        editor.join_lines(&JoinLines, window, cx);
 3802        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3803        assert_eq!(
 3804            editor.selections.ranges::<Point>(cx),
 3805            &[Point::new(0, 11)..Point::new(0, 11)]
 3806        );
 3807
 3808        // Undo should be transactional
 3809        editor.undo(&Undo, window, cx);
 3810        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3811        assert_eq!(
 3812            editor.selections.ranges::<Point>(cx),
 3813            &[Point::new(0, 5)..Point::new(2, 2)]
 3814        );
 3815
 3816        // When joining an empty line don't insert a space
 3817        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3818            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3819        });
 3820        editor.join_lines(&JoinLines, window, cx);
 3821        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3822        assert_eq!(
 3823            editor.selections.ranges::<Point>(cx),
 3824            [Point::new(2, 3)..Point::new(2, 3)]
 3825        );
 3826
 3827        // We can remove trailing newlines
 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        // We don't blow up on the last line
 3836        editor.join_lines(&JoinLines, window, cx);
 3837        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3838        assert_eq!(
 3839            editor.selections.ranges::<Point>(cx),
 3840            [Point::new(2, 3)..Point::new(2, 3)]
 3841        );
 3842
 3843        // reset to test indentation
 3844        editor.buffer.update(cx, |buffer, cx| {
 3845            buffer.edit(
 3846                [
 3847                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3848                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3849                ],
 3850                None,
 3851                cx,
 3852            )
 3853        });
 3854
 3855        // We remove any leading spaces
 3856        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3857        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3858            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3859        });
 3860        editor.join_lines(&JoinLines, window, cx);
 3861        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3862
 3863        // We don't insert a space for a line containing only spaces
 3864        editor.join_lines(&JoinLines, window, cx);
 3865        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3866
 3867        // We ignore any leading tabs
 3868        editor.join_lines(&JoinLines, window, cx);
 3869        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3870
 3871        editor
 3872    });
 3873}
 3874
 3875#[gpui::test]
 3876fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3877    init_test(cx, |_| {});
 3878
 3879    cx.add_window(|window, cx| {
 3880        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3881        let mut editor = build_editor(buffer.clone(), window, cx);
 3882        let buffer = buffer.read(cx).as_singleton().unwrap();
 3883
 3884        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3885            s.select_ranges([
 3886                Point::new(0, 2)..Point::new(1, 1),
 3887                Point::new(1, 2)..Point::new(1, 2),
 3888                Point::new(3, 1)..Point::new(3, 2),
 3889            ])
 3890        });
 3891
 3892        editor.join_lines(&JoinLines, window, cx);
 3893        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3894
 3895        assert_eq!(
 3896            editor.selections.ranges::<Point>(cx),
 3897            [
 3898                Point::new(0, 7)..Point::new(0, 7),
 3899                Point::new(1, 3)..Point::new(1, 3)
 3900            ]
 3901        );
 3902        editor
 3903    });
 3904}
 3905
 3906#[gpui::test]
 3907async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3908    init_test(cx, |_| {});
 3909
 3910    let mut cx = EditorTestContext::new(cx).await;
 3911
 3912    let diff_base = r#"
 3913        Line 0
 3914        Line 1
 3915        Line 2
 3916        Line 3
 3917        "#
 3918    .unindent();
 3919
 3920    cx.set_state(
 3921        &r#"
 3922        ˇLine 0
 3923        Line 1
 3924        Line 2
 3925        Line 3
 3926        "#
 3927        .unindent(),
 3928    );
 3929
 3930    cx.set_head_text(&diff_base);
 3931    executor.run_until_parked();
 3932
 3933    // Join lines
 3934    cx.update_editor(|editor, window, cx| {
 3935        editor.join_lines(&JoinLines, window, cx);
 3936    });
 3937    executor.run_until_parked();
 3938
 3939    cx.assert_editor_state(
 3940        &r#"
 3941        Line 0ˇ Line 1
 3942        Line 2
 3943        Line 3
 3944        "#
 3945        .unindent(),
 3946    );
 3947    // Join again
 3948    cx.update_editor(|editor, window, cx| {
 3949        editor.join_lines(&JoinLines, window, cx);
 3950    });
 3951    executor.run_until_parked();
 3952
 3953    cx.assert_editor_state(
 3954        &r#"
 3955        Line 0 Line 1ˇ Line 2
 3956        Line 3
 3957        "#
 3958        .unindent(),
 3959    );
 3960}
 3961
 3962#[gpui::test]
 3963async fn test_custom_newlines_cause_no_false_positive_diffs(
 3964    executor: BackgroundExecutor,
 3965    cx: &mut TestAppContext,
 3966) {
 3967    init_test(cx, |_| {});
 3968    let mut cx = EditorTestContext::new(cx).await;
 3969    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3970    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3971    executor.run_until_parked();
 3972
 3973    cx.update_editor(|editor, window, cx| {
 3974        let snapshot = editor.snapshot(window, cx);
 3975        assert_eq!(
 3976            snapshot
 3977                .buffer_snapshot
 3978                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3979                .collect::<Vec<_>>(),
 3980            Vec::new(),
 3981            "Should not have any diffs for files with custom newlines"
 3982        );
 3983    });
 3984}
 3985
 3986#[gpui::test]
 3987async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3988    init_test(cx, |_| {});
 3989
 3990    let mut cx = EditorTestContext::new(cx).await;
 3991
 3992    // Test sort_lines_case_insensitive()
 3993    cx.set_state(indoc! {"
 3994        «z
 3995        y
 3996        x
 3997        Z
 3998        Y
 3999        Xˇ»
 4000    "});
 4001    cx.update_editor(|e, window, cx| {
 4002        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4003    });
 4004    cx.assert_editor_state(indoc! {"
 4005        «x
 4006        X
 4007        y
 4008        Y
 4009        z
 4010        Zˇ»
 4011    "});
 4012
 4013    // Test reverse_lines()
 4014    cx.set_state(indoc! {"
 4015        «5
 4016        4
 4017        3
 4018        2
 4019        1ˇ»
 4020    "});
 4021    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4022    cx.assert_editor_state(indoc! {"
 4023        «1
 4024        2
 4025        3
 4026        4
 4027        5ˇ»
 4028    "});
 4029
 4030    // Skip testing shuffle_line()
 4031
 4032    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4033    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4034
 4035    // Don't manipulate when cursor is on single line, but expand the selection
 4036    cx.set_state(indoc! {"
 4037        ddˇdd
 4038        ccc
 4039        bb
 4040        a
 4041    "});
 4042    cx.update_editor(|e, window, cx| {
 4043        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4044    });
 4045    cx.assert_editor_state(indoc! {"
 4046        «ddddˇ»
 4047        ccc
 4048        bb
 4049        a
 4050    "});
 4051
 4052    // Basic manipulate case
 4053    // Start selection moves to column 0
 4054    // End of selection shrinks to fit shorter line
 4055    cx.set_state(indoc! {"
 4056        dd«d
 4057        ccc
 4058        bb
 4059        aaaaaˇ»
 4060    "});
 4061    cx.update_editor(|e, window, cx| {
 4062        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4063    });
 4064    cx.assert_editor_state(indoc! {"
 4065        «aaaaa
 4066        bb
 4067        ccc
 4068        dddˇ»
 4069    "});
 4070
 4071    // Manipulate case with newlines
 4072    cx.set_state(indoc! {"
 4073        dd«d
 4074        ccc
 4075
 4076        bb
 4077        aaaaa
 4078
 4079        ˇ»
 4080    "});
 4081    cx.update_editor(|e, window, cx| {
 4082        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4083    });
 4084    cx.assert_editor_state(indoc! {"
 4085        «
 4086
 4087        aaaaa
 4088        bb
 4089        ccc
 4090        dddˇ»
 4091
 4092    "});
 4093
 4094    // Adding new line
 4095    cx.set_state(indoc! {"
 4096        aa«a
 4097        bbˇ»b
 4098    "});
 4099    cx.update_editor(|e, window, cx| {
 4100        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4101    });
 4102    cx.assert_editor_state(indoc! {"
 4103        «aaa
 4104        bbb
 4105        added_lineˇ»
 4106    "});
 4107
 4108    // Removing line
 4109    cx.set_state(indoc! {"
 4110        aa«a
 4111        bbbˇ»
 4112    "});
 4113    cx.update_editor(|e, window, cx| {
 4114        e.manipulate_immutable_lines(window, cx, |lines| {
 4115            lines.pop();
 4116        })
 4117    });
 4118    cx.assert_editor_state(indoc! {"
 4119        «aaaˇ»
 4120    "});
 4121
 4122    // Removing all lines
 4123    cx.set_state(indoc! {"
 4124        aa«a
 4125        bbbˇ»
 4126    "});
 4127    cx.update_editor(|e, window, cx| {
 4128        e.manipulate_immutable_lines(window, cx, |lines| {
 4129            lines.drain(..);
 4130        })
 4131    });
 4132    cx.assert_editor_state(indoc! {"
 4133        ˇ
 4134    "});
 4135}
 4136
 4137#[gpui::test]
 4138async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4139    init_test(cx, |_| {});
 4140
 4141    let mut cx = EditorTestContext::new(cx).await;
 4142
 4143    // Consider continuous selection as single selection
 4144    cx.set_state(indoc! {"
 4145        Aaa«aa
 4146        cˇ»c«c
 4147        bb
 4148        aaaˇ»aa
 4149    "});
 4150    cx.update_editor(|e, window, cx| {
 4151        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4152    });
 4153    cx.assert_editor_state(indoc! {"
 4154        «Aaaaa
 4155        ccc
 4156        bb
 4157        aaaaaˇ»
 4158    "});
 4159
 4160    cx.set_state(indoc! {"
 4161        Aaa«aa
 4162        cˇ»c«c
 4163        bb
 4164        aaaˇ»aa
 4165    "});
 4166    cx.update_editor(|e, window, cx| {
 4167        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4168    });
 4169    cx.assert_editor_state(indoc! {"
 4170        «Aaaaa
 4171        ccc
 4172        bbˇ»
 4173    "});
 4174
 4175    // Consider non continuous selection as distinct dedup operations
 4176    cx.set_state(indoc! {"
 4177        «aaaaa
 4178        bb
 4179        aaaaa
 4180        aaaaaˇ»
 4181
 4182        aaa«aaˇ»
 4183    "});
 4184    cx.update_editor(|e, window, cx| {
 4185        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4186    });
 4187    cx.assert_editor_state(indoc! {"
 4188        «aaaaa
 4189        bbˇ»
 4190
 4191        «aaaaaˇ»
 4192    "});
 4193}
 4194
 4195#[gpui::test]
 4196async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4197    init_test(cx, |_| {});
 4198
 4199    let mut cx = EditorTestContext::new(cx).await;
 4200
 4201    cx.set_state(indoc! {"
 4202        «Aaa
 4203        aAa
 4204        Aaaˇ»
 4205    "});
 4206    cx.update_editor(|e, window, cx| {
 4207        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4208    });
 4209    cx.assert_editor_state(indoc! {"
 4210        «Aaa
 4211        aAaˇ»
 4212    "});
 4213
 4214    cx.set_state(indoc! {"
 4215        «Aaa
 4216        aAa
 4217        aaAˇ»
 4218    "});
 4219    cx.update_editor(|e, window, cx| {
 4220        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4221    });
 4222    cx.assert_editor_state(indoc! {"
 4223        «Aaaˇ»
 4224    "});
 4225}
 4226
 4227#[gpui::test]
 4228async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4229    init_test(cx, |_| {});
 4230
 4231    let mut cx = EditorTestContext::new(cx).await;
 4232
 4233    // Manipulate with multiple selections on a single line
 4234    cx.set_state(indoc! {"
 4235        dd«dd
 4236        cˇ»c«c
 4237        bb
 4238        aaaˇ»aa
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4242    });
 4243    cx.assert_editor_state(indoc! {"
 4244        «aaaaa
 4245        bb
 4246        ccc
 4247        ddddˇ»
 4248    "});
 4249
 4250    // Manipulate with multiple disjoin selections
 4251    cx.set_state(indoc! {"
 4252 4253        4
 4254        3
 4255        2
 4256        1ˇ»
 4257
 4258        dd«dd
 4259        ccc
 4260        bb
 4261        aaaˇ»aa
 4262    "});
 4263    cx.update_editor(|e, window, cx| {
 4264        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4265    });
 4266    cx.assert_editor_state(indoc! {"
 4267        «1
 4268        2
 4269        3
 4270        4
 4271        5ˇ»
 4272
 4273        «aaaaa
 4274        bb
 4275        ccc
 4276        ddddˇ»
 4277    "});
 4278
 4279    // Adding lines on each selection
 4280    cx.set_state(indoc! {"
 4281 4282        1ˇ»
 4283
 4284        bb«bb
 4285        aaaˇ»aa
 4286    "});
 4287    cx.update_editor(|e, window, cx| {
 4288        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4289    });
 4290    cx.assert_editor_state(indoc! {"
 4291        «2
 4292        1
 4293        added lineˇ»
 4294
 4295        «bbbb
 4296        aaaaa
 4297        added lineˇ»
 4298    "});
 4299
 4300    // Removing lines on each selection
 4301    cx.set_state(indoc! {"
 4302 4303        1ˇ»
 4304
 4305        bb«bb
 4306        aaaˇ»aa
 4307    "});
 4308    cx.update_editor(|e, window, cx| {
 4309        e.manipulate_immutable_lines(window, cx, |lines| {
 4310            lines.pop();
 4311        })
 4312    });
 4313    cx.assert_editor_state(indoc! {"
 4314        «2ˇ»
 4315
 4316        «bbbbˇ»
 4317    "});
 4318}
 4319
 4320#[gpui::test]
 4321async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4322    init_test(cx, |settings| {
 4323        settings.defaults.tab_size = NonZeroU32::new(3)
 4324    });
 4325
 4326    let mut cx = EditorTestContext::new(cx).await;
 4327
 4328    // MULTI SELECTION
 4329    // Ln.1 "«" tests empty lines
 4330    // Ln.9 tests just leading whitespace
 4331    cx.set_state(indoc! {"
 4332        «
 4333        abc                 // No indentationˇ»
 4334        «\tabc              // 1 tabˇ»
 4335        \t\tabc «      ˇ»   // 2 tabs
 4336        \t ab«c             // Tab followed by space
 4337         \tabc              // Space followed by tab (3 spaces should be the result)
 4338        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4339           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4340        \t
 4341        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4342    "});
 4343    cx.update_editor(|e, window, cx| {
 4344        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4345    });
 4346    cx.assert_editor_state(
 4347        indoc! {"
 4348            «
 4349            abc                 // No indentation
 4350               abc              // 1 tab
 4351                  abc          // 2 tabs
 4352                abc             // Tab followed by space
 4353               abc              // Space followed by tab (3 spaces should be the result)
 4354                           abc   // Mixed indentation (tab conversion depends on the column)
 4355               abc         // Already space indented
 4356               ·
 4357               abc\tdef          // Only the leading tab is manipulatedˇ»
 4358        "}
 4359        .replace("·", "")
 4360        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4361    );
 4362
 4363    // Test on just a few lines, the others should remain unchanged
 4364    // Only lines (3, 5, 10, 11) should change
 4365    cx.set_state(
 4366        indoc! {"
 4367            ·
 4368            abc                 // No indentation
 4369            \tabcˇ               // 1 tab
 4370            \t\tabc             // 2 tabs
 4371            \t abcˇ              // Tab followed by space
 4372             \tabc              // Space followed by tab (3 spaces should be the result)
 4373            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4374               abc              // Already space indented
 4375            «\t
 4376            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4377        "}
 4378        .replace("·", "")
 4379        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4380    );
 4381    cx.update_editor(|e, window, cx| {
 4382        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4383    });
 4384    cx.assert_editor_state(
 4385        indoc! {"
 4386            ·
 4387            abc                 // No indentation
 4388            «   abc               // 1 tabˇ»
 4389            \t\tabc             // 2 tabs
 4390            «    abc              // Tab followed by spaceˇ»
 4391             \tabc              // Space followed by tab (3 spaces should be the result)
 4392            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4393               abc              // Already space indented
 4394            «   ·
 4395               abc\tdef          // Only the leading tab is manipulatedˇ»
 4396        "}
 4397        .replace("·", "")
 4398        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4399    );
 4400
 4401    // SINGLE SELECTION
 4402    // Ln.1 "«" tests empty lines
 4403    // Ln.9 tests just leading whitespace
 4404    cx.set_state(indoc! {"
 4405        «
 4406        abc                 // No indentation
 4407        \tabc               // 1 tab
 4408        \t\tabc             // 2 tabs
 4409        \t abc              // Tab followed by space
 4410         \tabc              // Space followed by tab (3 spaces should be the result)
 4411        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4412           abc              // Already space indented
 4413        \t
 4414        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4415    "});
 4416    cx.update_editor(|e, window, cx| {
 4417        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4418    });
 4419    cx.assert_editor_state(
 4420        indoc! {"
 4421            «
 4422            abc                 // No indentation
 4423               abc               // 1 tab
 4424                  abc             // 2 tabs
 4425                abc              // Tab followed by space
 4426               abc              // Space followed by tab (3 spaces should be the result)
 4427                           abc   // Mixed indentation (tab conversion depends on the column)
 4428               abc              // Already space indented
 4429               ·
 4430               abc\tdef          // Only the leading tab is manipulatedˇ»
 4431        "}
 4432        .replace("·", "")
 4433        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4434    );
 4435}
 4436
 4437#[gpui::test]
 4438async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4439    init_test(cx, |settings| {
 4440        settings.defaults.tab_size = NonZeroU32::new(3)
 4441    });
 4442
 4443    let mut cx = EditorTestContext::new(cx).await;
 4444
 4445    // MULTI SELECTION
 4446    // Ln.1 "«" tests empty lines
 4447    // Ln.11 tests just leading whitespace
 4448    cx.set_state(indoc! {"
 4449        «
 4450        abˇ»ˇc                 // No indentation
 4451         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4452          abc  «             // 2 spaces (< 3 so dont convert)
 4453           abc              // 3 spaces (convert)
 4454             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4455        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4456        «\t abc              // Tab followed by space
 4457         \tabc              // Space followed by tab (should be consumed due to tab)
 4458        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4459           \tˇ»  «\t
 4460           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4461    "});
 4462    cx.update_editor(|e, window, cx| {
 4463        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4464    });
 4465    cx.assert_editor_state(indoc! {"
 4466        «
 4467        abc                 // No indentation
 4468         abc                // 1 space (< 3 so dont convert)
 4469          abc               // 2 spaces (< 3 so dont convert)
 4470        \tabc              // 3 spaces (convert)
 4471        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4472        \t\t\tabc           // Already tab indented
 4473        \t abc              // Tab followed by space
 4474        \tabc              // Space followed by tab (should be consumed due to tab)
 4475        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4476        \t\t\t
 4477        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4478    "});
 4479
 4480    // Test on just a few lines, the other should remain unchanged
 4481    // Only lines (4, 8, 11, 12) should change
 4482    cx.set_state(
 4483        indoc! {"
 4484            ·
 4485            abc                 // No indentation
 4486             abc                // 1 space (< 3 so dont convert)
 4487              abc               // 2 spaces (< 3 so dont convert)
 4488            «   abc              // 3 spaces (convert)ˇ»
 4489                 abc            // 5 spaces (1 tab + 2 spaces)
 4490            \t\t\tabc           // Already tab indented
 4491            \t abc              // Tab followed by space
 4492             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4493               \t\t  \tabc      // Mixed indentation
 4494            \t \t  \t   \tabc   // Mixed indentation
 4495               \t  \tˇ
 4496            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4497        "}
 4498        .replace("·", "")
 4499        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4500    );
 4501    cx.update_editor(|e, window, cx| {
 4502        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4503    });
 4504    cx.assert_editor_state(
 4505        indoc! {"
 4506            ·
 4507            abc                 // No indentation
 4508             abc                // 1 space (< 3 so dont convert)
 4509              abc               // 2 spaces (< 3 so dont convert)
 4510            «\tabc              // 3 spaces (convert)ˇ»
 4511                 abc            // 5 spaces (1 tab + 2 spaces)
 4512            \t\t\tabc           // Already tab indented
 4513            \t abc              // Tab followed by space
 4514            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4515               \t\t  \tabc      // Mixed indentation
 4516            \t \t  \t   \tabc   // Mixed indentation
 4517            «\t\t\t
 4518            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4519        "}
 4520        .replace("·", "")
 4521        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4522    );
 4523
 4524    // SINGLE SELECTION
 4525    // Ln.1 "«" tests empty lines
 4526    // Ln.11 tests just leading whitespace
 4527    cx.set_state(indoc! {"
 4528        «
 4529        abc                 // No indentation
 4530         abc                // 1 space (< 3 so dont convert)
 4531          abc               // 2 spaces (< 3 so dont convert)
 4532           abc              // 3 spaces (convert)
 4533             abc            // 5 spaces (1 tab + 2 spaces)
 4534        \t\t\tabc           // Already tab indented
 4535        \t abc              // Tab followed by space
 4536         \tabc              // Space followed by tab (should be consumed due to tab)
 4537        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4538           \t  \t
 4539           abc   \t         // Only the leading spaces should be convertedˇ»
 4540    "});
 4541    cx.update_editor(|e, window, cx| {
 4542        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4543    });
 4544    cx.assert_editor_state(indoc! {"
 4545        «
 4546        abc                 // No indentation
 4547         abc                // 1 space (< 3 so dont convert)
 4548          abc               // 2 spaces (< 3 so dont convert)
 4549        \tabc              // 3 spaces (convert)
 4550        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4551        \t\t\tabc           // Already tab indented
 4552        \t abc              // Tab followed by space
 4553        \tabc              // Space followed by tab (should be consumed due to tab)
 4554        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4555        \t\t\t
 4556        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4557    "});
 4558}
 4559
 4560#[gpui::test]
 4561async fn test_toggle_case(cx: &mut TestAppContext) {
 4562    init_test(cx, |_| {});
 4563
 4564    let mut cx = EditorTestContext::new(cx).await;
 4565
 4566    // If all lower case -> upper case
 4567    cx.set_state(indoc! {"
 4568        «hello worldˇ»
 4569    "});
 4570    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4571    cx.assert_editor_state(indoc! {"
 4572        «HELLO WORLDˇ»
 4573    "});
 4574
 4575    // If all upper case -> lower case
 4576    cx.set_state(indoc! {"
 4577        «HELLO WORLDˇ»
 4578    "});
 4579    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4580    cx.assert_editor_state(indoc! {"
 4581        «hello worldˇ»
 4582    "});
 4583
 4584    // If any upper case characters are identified -> lower case
 4585    // This matches JetBrains IDEs
 4586    cx.set_state(indoc! {"
 4587        «hEllo worldˇ»
 4588    "});
 4589    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4590    cx.assert_editor_state(indoc! {"
 4591        «hello worldˇ»
 4592    "});
 4593}
 4594
 4595#[gpui::test]
 4596async fn test_manipulate_text(cx: &mut TestAppContext) {
 4597    init_test(cx, |_| {});
 4598
 4599    let mut cx = EditorTestContext::new(cx).await;
 4600
 4601    // Test convert_to_upper_case()
 4602    cx.set_state(indoc! {"
 4603        «hello worldˇ»
 4604    "});
 4605    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4606    cx.assert_editor_state(indoc! {"
 4607        «HELLO WORLDˇ»
 4608    "});
 4609
 4610    // Test convert_to_lower_case()
 4611    cx.set_state(indoc! {"
 4612        «HELLO WORLDˇ»
 4613    "});
 4614    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4615    cx.assert_editor_state(indoc! {"
 4616        «hello worldˇ»
 4617    "});
 4618
 4619    // Test multiple line, single selection case
 4620    cx.set_state(indoc! {"
 4621        «The quick brown
 4622        fox jumps over
 4623        the lazy dogˇ»
 4624    "});
 4625    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4626    cx.assert_editor_state(indoc! {"
 4627        «The Quick Brown
 4628        Fox Jumps Over
 4629        The Lazy Dogˇ»
 4630    "});
 4631
 4632    // Test multiple line, single selection case
 4633    cx.set_state(indoc! {"
 4634        «The quick brown
 4635        fox jumps over
 4636        the lazy dogˇ»
 4637    "});
 4638    cx.update_editor(|e, window, cx| {
 4639        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4640    });
 4641    cx.assert_editor_state(indoc! {"
 4642        «TheQuickBrown
 4643        FoxJumpsOver
 4644        TheLazyDogˇ»
 4645    "});
 4646
 4647    // From here on out, test more complex cases of manipulate_text()
 4648
 4649    // Test no selection case - should affect words cursors are in
 4650    // Cursor at beginning, middle, and end of word
 4651    cx.set_state(indoc! {"
 4652        ˇhello big beauˇtiful worldˇ
 4653    "});
 4654    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4655    cx.assert_editor_state(indoc! {"
 4656        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4657    "});
 4658
 4659    // Test multiple selections on a single line and across multiple lines
 4660    cx.set_state(indoc! {"
 4661        «Theˇ» quick «brown
 4662        foxˇ» jumps «overˇ»
 4663        the «lazyˇ» dog
 4664    "});
 4665    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4666    cx.assert_editor_state(indoc! {"
 4667        «THEˇ» quick «BROWN
 4668        FOXˇ» jumps «OVERˇ»
 4669        the «LAZYˇ» dog
 4670    "});
 4671
 4672    // Test case where text length grows
 4673    cx.set_state(indoc! {"
 4674        «tschüߡ»
 4675    "});
 4676    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4677    cx.assert_editor_state(indoc! {"
 4678        «TSCHÜSSˇ»
 4679    "});
 4680
 4681    // Test to make sure we don't crash when text shrinks
 4682    cx.set_state(indoc! {"
 4683        aaa_bbbˇ
 4684    "});
 4685    cx.update_editor(|e, window, cx| {
 4686        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4687    });
 4688    cx.assert_editor_state(indoc! {"
 4689        «aaaBbbˇ»
 4690    "});
 4691
 4692    // Test to make sure we all aware of the fact that each word can grow and shrink
 4693    // Final selections should be aware of this fact
 4694    cx.set_state(indoc! {"
 4695        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4696    "});
 4697    cx.update_editor(|e, window, cx| {
 4698        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4699    });
 4700    cx.assert_editor_state(indoc! {"
 4701        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4702    "});
 4703
 4704    cx.set_state(indoc! {"
 4705        «hElLo, WoRld!ˇ»
 4706    "});
 4707    cx.update_editor(|e, window, cx| {
 4708        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4709    });
 4710    cx.assert_editor_state(indoc! {"
 4711        «HeLlO, wOrLD!ˇ»
 4712    "});
 4713}
 4714
 4715#[gpui::test]
 4716fn test_duplicate_line(cx: &mut TestAppContext) {
 4717    init_test(cx, |_| {});
 4718
 4719    let editor = cx.add_window(|window, cx| {
 4720        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4721        build_editor(buffer, window, cx)
 4722    });
 4723    _ = editor.update(cx, |editor, window, cx| {
 4724        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4725            s.select_display_ranges([
 4726                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4727                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4728                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4729                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4730            ])
 4731        });
 4732        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4733        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4734        assert_eq!(
 4735            editor.selections.display_ranges(cx),
 4736            vec![
 4737                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4738                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4739                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4740                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4741            ]
 4742        );
 4743    });
 4744
 4745    let editor = cx.add_window(|window, cx| {
 4746        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4747        build_editor(buffer, window, cx)
 4748    });
 4749    _ = editor.update(cx, |editor, window, cx| {
 4750        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4751            s.select_display_ranges([
 4752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4753                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4754            ])
 4755        });
 4756        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4757        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4758        assert_eq!(
 4759            editor.selections.display_ranges(cx),
 4760            vec![
 4761                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4762                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4763            ]
 4764        );
 4765    });
 4766
 4767    // With `move_upwards` the selections stay in place, except for
 4768    // the lines inserted above them
 4769    let editor = cx.add_window(|window, cx| {
 4770        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4771        build_editor(buffer, window, cx)
 4772    });
 4773    _ = editor.update(cx, |editor, window, cx| {
 4774        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4775            s.select_display_ranges([
 4776                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4777                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4778                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4779                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4780            ])
 4781        });
 4782        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4783        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4784        assert_eq!(
 4785            editor.selections.display_ranges(cx),
 4786            vec![
 4787                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4789                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4790                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4791            ]
 4792        );
 4793    });
 4794
 4795    let editor = cx.add_window(|window, cx| {
 4796        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4797        build_editor(buffer, window, cx)
 4798    });
 4799    _ = editor.update(cx, |editor, window, cx| {
 4800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4801            s.select_display_ranges([
 4802                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4803                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4804            ])
 4805        });
 4806        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4807        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4808        assert_eq!(
 4809            editor.selections.display_ranges(cx),
 4810            vec![
 4811                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4812                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4813            ]
 4814        );
 4815    });
 4816
 4817    let editor = cx.add_window(|window, cx| {
 4818        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4819        build_editor(buffer, window, cx)
 4820    });
 4821    _ = editor.update(cx, |editor, window, cx| {
 4822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4823            s.select_display_ranges([
 4824                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4825                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4826            ])
 4827        });
 4828        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4829        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4830        assert_eq!(
 4831            editor.selections.display_ranges(cx),
 4832            vec![
 4833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4834                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4835            ]
 4836        );
 4837    });
 4838}
 4839
 4840#[gpui::test]
 4841fn test_move_line_up_down(cx: &mut TestAppContext) {
 4842    init_test(cx, |_| {});
 4843
 4844    let editor = cx.add_window(|window, cx| {
 4845        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4846        build_editor(buffer, window, cx)
 4847    });
 4848    _ = editor.update(cx, |editor, window, cx| {
 4849        editor.fold_creases(
 4850            vec![
 4851                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4852                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4853                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4854            ],
 4855            true,
 4856            window,
 4857            cx,
 4858        );
 4859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4860            s.select_display_ranges([
 4861                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4862                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4863                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4864                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4865            ])
 4866        });
 4867        assert_eq!(
 4868            editor.display_text(cx),
 4869            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4870        );
 4871
 4872        editor.move_line_up(&MoveLineUp, window, cx);
 4873        assert_eq!(
 4874            editor.display_text(cx),
 4875            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4876        );
 4877        assert_eq!(
 4878            editor.selections.display_ranges(cx),
 4879            vec![
 4880                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4881                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4882                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4883                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4884            ]
 4885        );
 4886    });
 4887
 4888    _ = editor.update(cx, |editor, window, cx| {
 4889        editor.move_line_down(&MoveLineDown, window, cx);
 4890        assert_eq!(
 4891            editor.display_text(cx),
 4892            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4893        );
 4894        assert_eq!(
 4895            editor.selections.display_ranges(cx),
 4896            vec![
 4897                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4898                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4899                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4900                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4901            ]
 4902        );
 4903    });
 4904
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.move_line_down(&MoveLineDown, window, cx);
 4907        assert_eq!(
 4908            editor.display_text(cx),
 4909            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4910        );
 4911        assert_eq!(
 4912            editor.selections.display_ranges(cx),
 4913            vec![
 4914                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4915                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4916                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4917                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4918            ]
 4919        );
 4920    });
 4921
 4922    _ = editor.update(cx, |editor, window, cx| {
 4923        editor.move_line_up(&MoveLineUp, window, cx);
 4924        assert_eq!(
 4925            editor.display_text(cx),
 4926            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4927        );
 4928        assert_eq!(
 4929            editor.selections.display_ranges(cx),
 4930            vec![
 4931                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4932                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4933                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4934                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4935            ]
 4936        );
 4937    });
 4938}
 4939
 4940#[gpui::test]
 4941fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4942    init_test(cx, |_| {});
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4950        editor.insert_blocks(
 4951            [BlockProperties {
 4952                style: BlockStyle::Fixed,
 4953                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4954                height: Some(1),
 4955                render: Arc::new(|_| div().into_any()),
 4956                priority: 0,
 4957                render_in_minimap: true,
 4958            }],
 4959            Some(Autoscroll::fit()),
 4960            cx,
 4961        );
 4962        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4963            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4964        });
 4965        editor.move_line_down(&MoveLineDown, window, cx);
 4966    });
 4967}
 4968
 4969#[gpui::test]
 4970async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4971    init_test(cx, |_| {});
 4972
 4973    let mut cx = EditorTestContext::new(cx).await;
 4974    cx.set_state(
 4975        &"
 4976            ˇzero
 4977            one
 4978            two
 4979            three
 4980            four
 4981            five
 4982        "
 4983        .unindent(),
 4984    );
 4985
 4986    // Create a four-line block that replaces three lines of text.
 4987    cx.update_editor(|editor, window, cx| {
 4988        let snapshot = editor.snapshot(window, cx);
 4989        let snapshot = &snapshot.buffer_snapshot;
 4990        let placement = BlockPlacement::Replace(
 4991            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4992        );
 4993        editor.insert_blocks(
 4994            [BlockProperties {
 4995                placement,
 4996                height: Some(4),
 4997                style: BlockStyle::Sticky,
 4998                render: Arc::new(|_| gpui::div().into_any_element()),
 4999                priority: 0,
 5000                render_in_minimap: true,
 5001            }],
 5002            None,
 5003            cx,
 5004        );
 5005    });
 5006
 5007    // Move down so that the cursor touches the block.
 5008    cx.update_editor(|editor, window, cx| {
 5009        editor.move_down(&Default::default(), window, cx);
 5010    });
 5011    cx.assert_editor_state(
 5012        &"
 5013            zero
 5014            «one
 5015            two
 5016            threeˇ»
 5017            four
 5018            five
 5019        "
 5020        .unindent(),
 5021    );
 5022
 5023    // Move down past the block.
 5024    cx.update_editor(|editor, window, cx| {
 5025        editor.move_down(&Default::default(), window, cx);
 5026    });
 5027    cx.assert_editor_state(
 5028        &"
 5029            zero
 5030            one
 5031            two
 5032            three
 5033            ˇfour
 5034            five
 5035        "
 5036        .unindent(),
 5037    );
 5038}
 5039
 5040#[gpui::test]
 5041fn test_transpose(cx: &mut TestAppContext) {
 5042    init_test(cx, |_| {});
 5043
 5044    _ = cx.add_window(|window, cx| {
 5045        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5046        editor.set_style(EditorStyle::default(), window, cx);
 5047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5048            s.select_ranges([1..1])
 5049        });
 5050        editor.transpose(&Default::default(), window, cx);
 5051        assert_eq!(editor.text(cx), "bac");
 5052        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5053
 5054        editor.transpose(&Default::default(), window, cx);
 5055        assert_eq!(editor.text(cx), "bca");
 5056        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5057
 5058        editor.transpose(&Default::default(), window, cx);
 5059        assert_eq!(editor.text(cx), "bac");
 5060        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5061
 5062        editor
 5063    });
 5064
 5065    _ = cx.add_window(|window, cx| {
 5066        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5067        editor.set_style(EditorStyle::default(), window, cx);
 5068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5069            s.select_ranges([3..3])
 5070        });
 5071        editor.transpose(&Default::default(), window, cx);
 5072        assert_eq!(editor.text(cx), "acb\nde");
 5073        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5074
 5075        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5076            s.select_ranges([4..4])
 5077        });
 5078        editor.transpose(&Default::default(), window, cx);
 5079        assert_eq!(editor.text(cx), "acbd\ne");
 5080        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5081
 5082        editor.transpose(&Default::default(), window, cx);
 5083        assert_eq!(editor.text(cx), "acbde\n");
 5084        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5085
 5086        editor.transpose(&Default::default(), window, cx);
 5087        assert_eq!(editor.text(cx), "acbd\ne");
 5088        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5089
 5090        editor
 5091    });
 5092
 5093    _ = cx.add_window(|window, cx| {
 5094        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5095        editor.set_style(EditorStyle::default(), window, cx);
 5096        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5097            s.select_ranges([1..1, 2..2, 4..4])
 5098        });
 5099        editor.transpose(&Default::default(), window, cx);
 5100        assert_eq!(editor.text(cx), "bacd\ne");
 5101        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5102
 5103        editor.transpose(&Default::default(), window, cx);
 5104        assert_eq!(editor.text(cx), "bcade\n");
 5105        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5106
 5107        editor.transpose(&Default::default(), window, cx);
 5108        assert_eq!(editor.text(cx), "bcda\ne");
 5109        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5110
 5111        editor.transpose(&Default::default(), window, cx);
 5112        assert_eq!(editor.text(cx), "bcade\n");
 5113        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5114
 5115        editor.transpose(&Default::default(), window, cx);
 5116        assert_eq!(editor.text(cx), "bcaed\n");
 5117        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5118
 5119        editor
 5120    });
 5121
 5122    _ = cx.add_window(|window, cx| {
 5123        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5124        editor.set_style(EditorStyle::default(), window, cx);
 5125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5126            s.select_ranges([4..4])
 5127        });
 5128        editor.transpose(&Default::default(), window, cx);
 5129        assert_eq!(editor.text(cx), "🏀🍐✋");
 5130        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5131
 5132        editor.transpose(&Default::default(), window, cx);
 5133        assert_eq!(editor.text(cx), "🏀✋🍐");
 5134        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5135
 5136        editor.transpose(&Default::default(), window, cx);
 5137        assert_eq!(editor.text(cx), "🏀🍐✋");
 5138        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5139
 5140        editor
 5141    });
 5142}
 5143
 5144#[gpui::test]
 5145async fn test_rewrap(cx: &mut TestAppContext) {
 5146    init_test(cx, |settings| {
 5147        settings.languages.extend([
 5148            (
 5149                "Markdown".into(),
 5150                LanguageSettingsContent {
 5151                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5152                    ..Default::default()
 5153                },
 5154            ),
 5155            (
 5156                "Plain Text".into(),
 5157                LanguageSettingsContent {
 5158                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5159                    ..Default::default()
 5160                },
 5161            ),
 5162        ])
 5163    });
 5164
 5165    let mut cx = EditorTestContext::new(cx).await;
 5166
 5167    let language_with_c_comments = Arc::new(Language::new(
 5168        LanguageConfig {
 5169            line_comments: vec!["// ".into()],
 5170            ..LanguageConfig::default()
 5171        },
 5172        None,
 5173    ));
 5174    let language_with_pound_comments = Arc::new(Language::new(
 5175        LanguageConfig {
 5176            line_comments: vec!["# ".into()],
 5177            ..LanguageConfig::default()
 5178        },
 5179        None,
 5180    ));
 5181    let markdown_language = Arc::new(Language::new(
 5182        LanguageConfig {
 5183            name: "Markdown".into(),
 5184            ..LanguageConfig::default()
 5185        },
 5186        None,
 5187    ));
 5188    let language_with_doc_comments = Arc::new(Language::new(
 5189        LanguageConfig {
 5190            line_comments: vec!["// ".into(), "/// ".into()],
 5191            ..LanguageConfig::default()
 5192        },
 5193        Some(tree_sitter_rust::LANGUAGE.into()),
 5194    ));
 5195
 5196    let plaintext_language = Arc::new(Language::new(
 5197        LanguageConfig {
 5198            name: "Plain Text".into(),
 5199            ..LanguageConfig::default()
 5200        },
 5201        None,
 5202    ));
 5203
 5204    assert_rewrap(
 5205        indoc! {"
 5206            // ˇ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.
 5207        "},
 5208        indoc! {"
 5209            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5210            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5211            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5212            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5213            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5214            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5215            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5216            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5217            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5218            // porttitor id. Aliquam id accumsan eros.
 5219        "},
 5220        language_with_c_comments.clone(),
 5221        &mut cx,
 5222    );
 5223
 5224    // Test that rewrapping works inside of a selection
 5225    assert_rewrap(
 5226        indoc! {"
 5227            «// 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.ˇ»
 5228        "},
 5229        indoc! {"
 5230            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5231            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5232            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5233            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5234            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5235            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5236            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5237            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5238            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5239            // porttitor id. Aliquam id accumsan eros.ˇ»
 5240        "},
 5241        language_with_c_comments.clone(),
 5242        &mut cx,
 5243    );
 5244
 5245    // Test that cursors that expand to the same region are collapsed.
 5246    assert_rewrap(
 5247        indoc! {"
 5248            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5249            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5250            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5251            // ˇ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.
 5252        "},
 5253        indoc! {"
 5254            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5255            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5256            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 5257            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5258            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5259            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5260            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5261            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5262            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5263            // porttitor id. Aliquam id accumsan eros.
 5264        "},
 5265        language_with_c_comments.clone(),
 5266        &mut cx,
 5267    );
 5268
 5269    // Test that non-contiguous selections are treated separately.
 5270    assert_rewrap(
 5271        indoc! {"
 5272            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5273            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5274            //
 5275            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5276            // ˇ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.
 5277        "},
 5278        indoc! {"
 5279            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5280            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5281            // auctor, eu lacinia sapien scelerisque.
 5282            //
 5283            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5284            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5285            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5286            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5287            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5288            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5289            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5290        "},
 5291        language_with_c_comments.clone(),
 5292        &mut cx,
 5293    );
 5294
 5295    // Test that different comment prefixes are supported.
 5296    assert_rewrap(
 5297        indoc! {"
 5298            # ˇ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.
 5299        "},
 5300        indoc! {"
 5301            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5302            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5303            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5304            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5305            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5306            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5307            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5308            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5309            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5310            # accumsan eros.
 5311        "},
 5312        language_with_pound_comments.clone(),
 5313        &mut cx,
 5314    );
 5315
 5316    // Test that rewrapping is ignored outside of comments in most languages.
 5317    assert_rewrap(
 5318        indoc! {"
 5319            /// Adds two numbers.
 5320            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5321            fn add(a: u32, b: u32) -> u32 {
 5322                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ˇ
 5323            }
 5324        "},
 5325        indoc! {"
 5326            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5327            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5328            fn add(a: u32, b: u32) -> u32 {
 5329                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ˇ
 5330            }
 5331        "},
 5332        language_with_doc_comments.clone(),
 5333        &mut cx,
 5334    );
 5335
 5336    // Test that rewrapping works in Markdown and Plain Text languages.
 5337    assert_rewrap(
 5338        indoc! {"
 5339            # Hello
 5340
 5341            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.
 5342        "},
 5343        indoc! {"
 5344            # Hello
 5345
 5346            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5347            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5348            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5349            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5350            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5351            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5352            Integer sit amet scelerisque nisi.
 5353        "},
 5354        markdown_language,
 5355        &mut cx,
 5356    );
 5357
 5358    assert_rewrap(
 5359        indoc! {"
 5360            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.
 5361        "},
 5362        indoc! {"
 5363            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5364            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5365            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5366            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5367            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5368            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5369            Integer sit amet scelerisque nisi.
 5370        "},
 5371        plaintext_language.clone(),
 5372        &mut cx,
 5373    );
 5374
 5375    // Test rewrapping unaligned comments in a selection.
 5376    assert_rewrap(
 5377        indoc! {"
 5378            fn foo() {
 5379                if true {
 5380            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5381            // Praesent semper egestas tellus id dignissim.ˇ»
 5382                    do_something();
 5383                } else {
 5384                    //
 5385                }
 5386            }
 5387        "},
 5388        indoc! {"
 5389            fn foo() {
 5390                if true {
 5391            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5392                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5393                    // egestas tellus id dignissim.ˇ»
 5394                    do_something();
 5395                } else {
 5396                    //
 5397                }
 5398            }
 5399        "},
 5400        language_with_doc_comments.clone(),
 5401        &mut cx,
 5402    );
 5403
 5404    assert_rewrap(
 5405        indoc! {"
 5406            fn foo() {
 5407                if true {
 5408            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5409            // Praesent semper egestas tellus id dignissim.»
 5410                    do_something();
 5411                } else {
 5412                    //
 5413                }
 5414
 5415            }
 5416        "},
 5417        indoc! {"
 5418            fn foo() {
 5419                if true {
 5420            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5421                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5422                    // egestas tellus id dignissim.»
 5423                    do_something();
 5424                } else {
 5425                    //
 5426                }
 5427
 5428            }
 5429        "},
 5430        language_with_doc_comments.clone(),
 5431        &mut cx,
 5432    );
 5433
 5434    assert_rewrap(
 5435        indoc! {"
 5436            «ˇ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
 5437
 5438            two»
 5439
 5440            three
 5441
 5442            «ˇ\t
 5443
 5444            four four four four four four four four four four four four four four four four four four four four»
 5445
 5446            «ˇfive five five five five five five five five five five five five five five five five five five five
 5447            \t»
 5448            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
 5449        "},
 5450        indoc! {"
 5451            «ˇone one one one one one one one one one one one one one one one one one one one
 5452            one one one one one
 5453
 5454            two»
 5455
 5456            three
 5457
 5458            «ˇ\t
 5459
 5460            four four four four four four four four four four four four four four four four
 5461            four four four four»
 5462
 5463            «ˇfive five five five five five five five five five five five five five five five
 5464            five five five five
 5465            \t»
 5466            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
 5467        "},
 5468        plaintext_language.clone(),
 5469        &mut cx,
 5470    );
 5471
 5472    assert_rewrap(
 5473        indoc! {"
 5474            //ˇ 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
 5475            //ˇ
 5476            //ˇ 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
 5477            //ˇ short short short
 5478            int main(void) {
 5479                return 17;
 5480            }
 5481        "},
 5482        indoc! {"
 5483            //ˇ long long long long long long long long long long long long long long long
 5484            // long long long long long long long long long long long long long
 5485            //ˇ
 5486            //ˇ long long long long long long long long long long long long long long long
 5487            //ˇ long long long long long long long long long long long long long short short
 5488            // short
 5489            int main(void) {
 5490                return 17;
 5491            }
 5492        "},
 5493        language_with_c_comments,
 5494        &mut cx,
 5495    );
 5496
 5497    #[track_caller]
 5498    fn assert_rewrap(
 5499        unwrapped_text: &str,
 5500        wrapped_text: &str,
 5501        language: Arc<Language>,
 5502        cx: &mut EditorTestContext,
 5503    ) {
 5504        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5505        cx.set_state(unwrapped_text);
 5506        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5507        cx.assert_editor_state(wrapped_text);
 5508    }
 5509}
 5510
 5511#[gpui::test]
 5512async fn test_hard_wrap(cx: &mut TestAppContext) {
 5513    init_test(cx, |_| {});
 5514    let mut cx = EditorTestContext::new(cx).await;
 5515
 5516    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5517    cx.update_editor(|editor, _, cx| {
 5518        editor.set_hard_wrap(Some(14), cx);
 5519    });
 5520
 5521    cx.set_state(indoc!(
 5522        "
 5523        one two three ˇ
 5524        "
 5525    ));
 5526    cx.simulate_input("four");
 5527    cx.run_until_parked();
 5528
 5529    cx.assert_editor_state(indoc!(
 5530        "
 5531        one two three
 5532        fourˇ
 5533        "
 5534    ));
 5535
 5536    cx.update_editor(|editor, window, cx| {
 5537        editor.newline(&Default::default(), window, cx);
 5538    });
 5539    cx.run_until_parked();
 5540    cx.assert_editor_state(indoc!(
 5541        "
 5542        one two three
 5543        four
 5544        ˇ
 5545        "
 5546    ));
 5547
 5548    cx.simulate_input("five");
 5549    cx.run_until_parked();
 5550    cx.assert_editor_state(indoc!(
 5551        "
 5552        one two three
 5553        four
 5554        fiveˇ
 5555        "
 5556    ));
 5557
 5558    cx.update_editor(|editor, window, cx| {
 5559        editor.newline(&Default::default(), window, cx);
 5560    });
 5561    cx.run_until_parked();
 5562    cx.simulate_input("# ");
 5563    cx.run_until_parked();
 5564    cx.assert_editor_state(indoc!(
 5565        "
 5566        one two three
 5567        four
 5568        five
 5569        # ˇ
 5570        "
 5571    ));
 5572
 5573    cx.update_editor(|editor, window, cx| {
 5574        editor.newline(&Default::default(), window, cx);
 5575    });
 5576    cx.run_until_parked();
 5577    cx.assert_editor_state(indoc!(
 5578        "
 5579        one two three
 5580        four
 5581        five
 5582        #\x20
 5583 5584        "
 5585    ));
 5586
 5587    cx.simulate_input(" 6");
 5588    cx.run_until_parked();
 5589    cx.assert_editor_state(indoc!(
 5590        "
 5591        one two three
 5592        four
 5593        five
 5594        #
 5595        # 6ˇ
 5596        "
 5597    ));
 5598}
 5599
 5600#[gpui::test]
 5601async fn test_clipboard(cx: &mut TestAppContext) {
 5602    init_test(cx, |_| {});
 5603
 5604    let mut cx = EditorTestContext::new(cx).await;
 5605
 5606    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5607    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5608    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5609
 5610    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5611    cx.set_state("two ˇfour ˇsix ˇ");
 5612    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5613    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5614
 5615    // Paste again but with only two cursors. Since the number of cursors doesn't
 5616    // match the number of slices in the clipboard, the entire clipboard text
 5617    // is pasted at each cursor.
 5618    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5619    cx.update_editor(|e, window, cx| {
 5620        e.handle_input("( ", window, cx);
 5621        e.paste(&Paste, window, cx);
 5622        e.handle_input(") ", window, cx);
 5623    });
 5624    cx.assert_editor_state(
 5625        &([
 5626            "( one✅ ",
 5627            "three ",
 5628            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5629            "three ",
 5630            "five ) ˇ",
 5631        ]
 5632        .join("\n")),
 5633    );
 5634
 5635    // Cut with three selections, one of which is full-line.
 5636    cx.set_state(indoc! {"
 5637        1«2ˇ»3
 5638        4ˇ567
 5639        «8ˇ»9"});
 5640    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5641    cx.assert_editor_state(indoc! {"
 5642        1ˇ3
 5643        ˇ9"});
 5644
 5645    // Paste with three selections, noticing how the copied selection that was full-line
 5646    // gets inserted before the second cursor.
 5647    cx.set_state(indoc! {"
 5648        1ˇ3
 5649 5650        «oˇ»ne"});
 5651    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5652    cx.assert_editor_state(indoc! {"
 5653        12ˇ3
 5654        4567
 5655 5656        8ˇne"});
 5657
 5658    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5659    cx.set_state(indoc! {"
 5660        The quick brown
 5661        fox juˇmps over
 5662        the lazy dog"});
 5663    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5664    assert_eq!(
 5665        cx.read_from_clipboard()
 5666            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5667        Some("fox jumps over\n".to_string())
 5668    );
 5669
 5670    // Paste with three selections, noticing how the copied full-line selection is inserted
 5671    // before the empty selections but replaces the selection that is non-empty.
 5672    cx.set_state(indoc! {"
 5673        Tˇhe quick brown
 5674        «foˇ»x jumps over
 5675        tˇhe lazy dog"});
 5676    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5677    cx.assert_editor_state(indoc! {"
 5678        fox jumps over
 5679        Tˇhe quick brown
 5680        fox jumps over
 5681        ˇx jumps over
 5682        fox jumps over
 5683        tˇhe lazy dog"});
 5684}
 5685
 5686#[gpui::test]
 5687async fn test_copy_trim(cx: &mut TestAppContext) {
 5688    init_test(cx, |_| {});
 5689
 5690    let mut cx = EditorTestContext::new(cx).await;
 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"
 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 preserves all indentation selected",
 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 should strip all leading whitespaces, even if some of it was selected"
 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            "     for selection 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        "Regular copying for reverse selection works the same",
 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            "for selection in selections.iter() {
 5807let mut start = selection.start;
 5808let mut end = selection.end;
 5809let is_entire_line = selection.is_empty();
 5810if is_entire_line {
 5811    start = Point::new(start.row, 0);"
 5812                .to_string()
 5813        ),
 5814        "Copying with stripping for reverse selection works the same"
 5815    );
 5816
 5817    cx.set_state(
 5818        r#"            for selection «in selections.iter() {
 5819            let mut start = selection.start;
 5820            let mut end = selection.end;
 5821            let is_entire_line = selection.is_empty();
 5822            if is_entire_line {
 5823                start = Point::new(start.row, 0);ˇ»
 5824                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5825            }
 5826        "#,
 5827    );
 5828    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5829    assert_eq!(
 5830        cx.read_from_clipboard()
 5831            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5832        Some(
 5833            "in selections.iter() {
 5834            let mut start = selection.start;
 5835            let mut end = selection.end;
 5836            let is_entire_line = selection.is_empty();
 5837            if is_entire_line {
 5838                start = Point::new(start.row, 0);"
 5839                .to_string()
 5840        ),
 5841        "When selecting past the indent, the copying works as usual",
 5842    );
 5843    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5844    assert_eq!(
 5845        cx.read_from_clipboard()
 5846            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5847        Some(
 5848            "in selections.iter() {
 5849            let mut start = selection.start;
 5850            let mut end = selection.end;
 5851            let is_entire_line = selection.is_empty();
 5852            if is_entire_line {
 5853                start = Point::new(start.row, 0);"
 5854                .to_string()
 5855        ),
 5856        "When selecting past the indent, nothing is trimmed"
 5857    );
 5858
 5859    cx.set_state(
 5860        r#"            «for selection in selections.iter() {
 5861            let mut start = selection.start;
 5862
 5863            let mut end = selection.end;
 5864            let is_entire_line = selection.is_empty();
 5865            if is_entire_line {
 5866                start = Point::new(start.row, 0);
 5867ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5868            }
 5869        "#,
 5870    );
 5871    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5872    assert_eq!(
 5873        cx.read_from_clipboard()
 5874            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5875        Some(
 5876            "for selection in selections.iter() {
 5877let mut start = selection.start;
 5878
 5879let mut end = selection.end;
 5880let is_entire_line = selection.is_empty();
 5881if is_entire_line {
 5882    start = Point::new(start.row, 0);
 5883"
 5884            .to_string()
 5885        ),
 5886        "Copying with stripping should ignore empty lines"
 5887    );
 5888}
 5889
 5890#[gpui::test]
 5891async fn test_paste_multiline(cx: &mut TestAppContext) {
 5892    init_test(cx, |_| {});
 5893
 5894    let mut cx = EditorTestContext::new(cx).await;
 5895    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5896
 5897    // Cut an indented block, without the leading whitespace.
 5898    cx.set_state(indoc! {"
 5899        const a: B = (
 5900            c(),
 5901            «d(
 5902                e,
 5903                f
 5904            )ˇ»
 5905        );
 5906    "});
 5907    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5908    cx.assert_editor_state(indoc! {"
 5909        const a: B = (
 5910            c(),
 5911            ˇ
 5912        );
 5913    "});
 5914
 5915    // Paste it at the same position.
 5916    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5917    cx.assert_editor_state(indoc! {"
 5918        const a: B = (
 5919            c(),
 5920            d(
 5921                e,
 5922                f
 5923 5924        );
 5925    "});
 5926
 5927    // Paste it at a line with a lower indent level.
 5928    cx.set_state(indoc! {"
 5929        ˇ
 5930        const a: B = (
 5931            c(),
 5932        );
 5933    "});
 5934    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5935    cx.assert_editor_state(indoc! {"
 5936        d(
 5937            e,
 5938            f
 5939 5940        const a: B = (
 5941            c(),
 5942        );
 5943    "});
 5944
 5945    // Cut an indented block, with the leading whitespace.
 5946    cx.set_state(indoc! {"
 5947        const a: B = (
 5948            c(),
 5949        «    d(
 5950                e,
 5951                f
 5952            )
 5953        ˇ»);
 5954    "});
 5955    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5956    cx.assert_editor_state(indoc! {"
 5957        const a: B = (
 5958            c(),
 5959        ˇ);
 5960    "});
 5961
 5962    // Paste it at the same position.
 5963    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5964    cx.assert_editor_state(indoc! {"
 5965        const a: B = (
 5966            c(),
 5967            d(
 5968                e,
 5969                f
 5970            )
 5971        ˇ);
 5972    "});
 5973
 5974    // Paste it at a line with a higher indent level.
 5975    cx.set_state(indoc! {"
 5976        const a: B = (
 5977            c(),
 5978            d(
 5979                e,
 5980 5981            )
 5982        );
 5983    "});
 5984    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5985    cx.assert_editor_state(indoc! {"
 5986        const a: B = (
 5987            c(),
 5988            d(
 5989                e,
 5990                f    d(
 5991                    e,
 5992                    f
 5993                )
 5994        ˇ
 5995            )
 5996        );
 5997    "});
 5998
 5999    // Copy an indented block, starting mid-line
 6000    cx.set_state(indoc! {"
 6001        const a: B = (
 6002            c(),
 6003            somethin«g(
 6004                e,
 6005                f
 6006            )ˇ»
 6007        );
 6008    "});
 6009    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6010
 6011    // Paste it on a line with a lower indent level
 6012    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6013    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6014    cx.assert_editor_state(indoc! {"
 6015        const a: B = (
 6016            c(),
 6017            something(
 6018                e,
 6019                f
 6020            )
 6021        );
 6022        g(
 6023            e,
 6024            f
 6025"});
 6026}
 6027
 6028#[gpui::test]
 6029async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6030    init_test(cx, |_| {});
 6031
 6032    cx.write_to_clipboard(ClipboardItem::new_string(
 6033        "    d(\n        e\n    );\n".into(),
 6034    ));
 6035
 6036    let mut cx = EditorTestContext::new(cx).await;
 6037    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6038
 6039    cx.set_state(indoc! {"
 6040        fn a() {
 6041            b();
 6042            if c() {
 6043                ˇ
 6044            }
 6045        }
 6046    "});
 6047
 6048    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6049    cx.assert_editor_state(indoc! {"
 6050        fn a() {
 6051            b();
 6052            if c() {
 6053                d(
 6054                    e
 6055                );
 6056        ˇ
 6057            }
 6058        }
 6059    "});
 6060
 6061    cx.set_state(indoc! {"
 6062        fn a() {
 6063            b();
 6064            ˇ
 6065        }
 6066    "});
 6067
 6068    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6069    cx.assert_editor_state(indoc! {"
 6070        fn a() {
 6071            b();
 6072            d(
 6073                e
 6074            );
 6075        ˇ
 6076        }
 6077    "});
 6078}
 6079
 6080#[gpui::test]
 6081fn test_select_all(cx: &mut TestAppContext) {
 6082    init_test(cx, |_| {});
 6083
 6084    let editor = cx.add_window(|window, cx| {
 6085        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6086        build_editor(buffer, window, cx)
 6087    });
 6088    _ = editor.update(cx, |editor, window, cx| {
 6089        editor.select_all(&SelectAll, window, cx);
 6090        assert_eq!(
 6091            editor.selections.display_ranges(cx),
 6092            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6093        );
 6094    });
 6095}
 6096
 6097#[gpui::test]
 6098fn test_select_line(cx: &mut TestAppContext) {
 6099    init_test(cx, |_| {});
 6100
 6101    let editor = cx.add_window(|window, cx| {
 6102        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6103        build_editor(buffer, window, cx)
 6104    });
 6105    _ = editor.update(cx, |editor, window, cx| {
 6106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6107            s.select_display_ranges([
 6108                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6109                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6110                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6111                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6112            ])
 6113        });
 6114        editor.select_line(&SelectLine, window, cx);
 6115        assert_eq!(
 6116            editor.selections.display_ranges(cx),
 6117            vec![
 6118                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6119                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6120            ]
 6121        );
 6122    });
 6123
 6124    _ = editor.update(cx, |editor, window, cx| {
 6125        editor.select_line(&SelectLine, window, cx);
 6126        assert_eq!(
 6127            editor.selections.display_ranges(cx),
 6128            vec![
 6129                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6130                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6131            ]
 6132        );
 6133    });
 6134
 6135    _ = editor.update(cx, |editor, window, cx| {
 6136        editor.select_line(&SelectLine, window, cx);
 6137        assert_eq!(
 6138            editor.selections.display_ranges(cx),
 6139            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6140        );
 6141    });
 6142}
 6143
 6144#[gpui::test]
 6145async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6146    init_test(cx, |_| {});
 6147    let mut cx = EditorTestContext::new(cx).await;
 6148
 6149    #[track_caller]
 6150    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6151        cx.set_state(initial_state);
 6152        cx.update_editor(|e, window, cx| {
 6153            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6154        });
 6155        cx.assert_editor_state(expected_state);
 6156    }
 6157
 6158    // Selection starts and ends at the middle of lines, left-to-right
 6159    test(
 6160        &mut cx,
 6161        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6162        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6163    );
 6164    // Same thing, right-to-left
 6165    test(
 6166        &mut cx,
 6167        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6168        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6169    );
 6170
 6171    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6172    test(
 6173        &mut cx,
 6174        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6175        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6176    );
 6177    // Same thing, right-to-left
 6178    test(
 6179        &mut cx,
 6180        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6181        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6182    );
 6183
 6184    // Whole buffer, left-to-right, last line ends with newline
 6185    test(
 6186        &mut cx,
 6187        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6188        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6189    );
 6190    // Same thing, right-to-left
 6191    test(
 6192        &mut cx,
 6193        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6194        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6195    );
 6196
 6197    // Starts at the end of a line, ends at the start of another
 6198    test(
 6199        &mut cx,
 6200        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6201        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6202    );
 6203}
 6204
 6205#[gpui::test]
 6206async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6207    init_test(cx, |_| {});
 6208
 6209    let editor = cx.add_window(|window, cx| {
 6210        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6211        build_editor(buffer, window, cx)
 6212    });
 6213
 6214    // setup
 6215    _ = editor.update(cx, |editor, window, cx| {
 6216        editor.fold_creases(
 6217            vec![
 6218                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6219                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6220                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6221            ],
 6222            true,
 6223            window,
 6224            cx,
 6225        );
 6226        assert_eq!(
 6227            editor.display_text(cx),
 6228            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6229        );
 6230    });
 6231
 6232    _ = editor.update(cx, |editor, window, cx| {
 6233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6234            s.select_display_ranges([
 6235                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6236                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6237                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6238                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6239            ])
 6240        });
 6241        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6242        assert_eq!(
 6243            editor.display_text(cx),
 6244            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6245        );
 6246    });
 6247    EditorTestContext::for_editor(editor, cx)
 6248        .await
 6249        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6250
 6251    _ = editor.update(cx, |editor, window, cx| {
 6252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6253            s.select_display_ranges([
 6254                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6255            ])
 6256        });
 6257        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6258        assert_eq!(
 6259            editor.display_text(cx),
 6260            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6261        );
 6262        assert_eq!(
 6263            editor.selections.display_ranges(cx),
 6264            [
 6265                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6266                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6267                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6268                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6269                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6270                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6271                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6272            ]
 6273        );
 6274    });
 6275    EditorTestContext::for_editor(editor, cx)
 6276        .await
 6277        .assert_editor_state(
 6278            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6279        );
 6280}
 6281
 6282#[gpui::test]
 6283async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6284    init_test(cx, |_| {});
 6285
 6286    let mut cx = EditorTestContext::new(cx).await;
 6287
 6288    cx.set_state(indoc!(
 6289        r#"abc
 6290           defˇghi
 6291
 6292           jk
 6293           nlmo
 6294           "#
 6295    ));
 6296
 6297    cx.update_editor(|editor, window, cx| {
 6298        editor.add_selection_above(&Default::default(), window, cx);
 6299    });
 6300
 6301    cx.assert_editor_state(indoc!(
 6302        r#"abcˇ
 6303           defˇghi
 6304
 6305           jk
 6306           nlmo
 6307           "#
 6308    ));
 6309
 6310    cx.update_editor(|editor, window, cx| {
 6311        editor.add_selection_above(&Default::default(), window, cx);
 6312    });
 6313
 6314    cx.assert_editor_state(indoc!(
 6315        r#"abcˇ
 6316            defˇghi
 6317
 6318            jk
 6319            nlmo
 6320            "#
 6321    ));
 6322
 6323    cx.update_editor(|editor, window, cx| {
 6324        editor.add_selection_below(&Default::default(), window, cx);
 6325    });
 6326
 6327    cx.assert_editor_state(indoc!(
 6328        r#"abc
 6329           defˇghi
 6330
 6331           jk
 6332           nlmo
 6333           "#
 6334    ));
 6335
 6336    cx.update_editor(|editor, window, cx| {
 6337        editor.undo_selection(&Default::default(), window, cx);
 6338    });
 6339
 6340    cx.assert_editor_state(indoc!(
 6341        r#"abcˇ
 6342           defˇghi
 6343
 6344           jk
 6345           nlmo
 6346           "#
 6347    ));
 6348
 6349    cx.update_editor(|editor, window, cx| {
 6350        editor.redo_selection(&Default::default(), window, cx);
 6351    });
 6352
 6353    cx.assert_editor_state(indoc!(
 6354        r#"abc
 6355           defˇghi
 6356
 6357           jk
 6358           nlmo
 6359           "#
 6360    ));
 6361
 6362    cx.update_editor(|editor, window, cx| {
 6363        editor.add_selection_below(&Default::default(), window, cx);
 6364    });
 6365
 6366    cx.assert_editor_state(indoc!(
 6367        r#"abc
 6368           defˇghi
 6369           ˇ
 6370           jk
 6371           nlmo
 6372           "#
 6373    ));
 6374
 6375    cx.update_editor(|editor, window, cx| {
 6376        editor.add_selection_below(&Default::default(), window, cx);
 6377    });
 6378
 6379    cx.assert_editor_state(indoc!(
 6380        r#"abc
 6381           defˇghi
 6382           ˇ
 6383           jkˇ
 6384           nlmo
 6385           "#
 6386    ));
 6387
 6388    cx.update_editor(|editor, window, cx| {
 6389        editor.add_selection_below(&Default::default(), window, cx);
 6390    });
 6391
 6392    cx.assert_editor_state(indoc!(
 6393        r#"abc
 6394           defˇghi
 6395           ˇ
 6396           jkˇ
 6397           nlmˇo
 6398           "#
 6399    ));
 6400
 6401    cx.update_editor(|editor, window, cx| {
 6402        editor.add_selection_below(&Default::default(), window, cx);
 6403    });
 6404
 6405    cx.assert_editor_state(indoc!(
 6406        r#"abc
 6407           defˇghi
 6408           ˇ
 6409           jkˇ
 6410           nlmˇo
 6411           ˇ"#
 6412    ));
 6413
 6414    // change selections
 6415    cx.set_state(indoc!(
 6416        r#"abc
 6417           def«ˇg»hi
 6418
 6419           jk
 6420           nlmo
 6421           "#
 6422    ));
 6423
 6424    cx.update_editor(|editor, window, cx| {
 6425        editor.add_selection_below(&Default::default(), window, cx);
 6426    });
 6427
 6428    cx.assert_editor_state(indoc!(
 6429        r#"abc
 6430           def«ˇg»hi
 6431
 6432           jk
 6433           nlm«ˇo»
 6434           "#
 6435    ));
 6436
 6437    cx.update_editor(|editor, window, cx| {
 6438        editor.add_selection_below(&Default::default(), window, cx);
 6439    });
 6440
 6441    cx.assert_editor_state(indoc!(
 6442        r#"abc
 6443           def«ˇg»hi
 6444
 6445           jk
 6446           nlm«ˇo»
 6447           "#
 6448    ));
 6449
 6450    cx.update_editor(|editor, window, cx| {
 6451        editor.add_selection_above(&Default::default(), window, cx);
 6452    });
 6453
 6454    cx.assert_editor_state(indoc!(
 6455        r#"abc
 6456           def«ˇg»hi
 6457
 6458           jk
 6459           nlmo
 6460           "#
 6461    ));
 6462
 6463    cx.update_editor(|editor, window, cx| {
 6464        editor.add_selection_above(&Default::default(), window, cx);
 6465    });
 6466
 6467    cx.assert_editor_state(indoc!(
 6468        r#"abc
 6469           def«ˇg»hi
 6470
 6471           jk
 6472           nlmo
 6473           "#
 6474    ));
 6475
 6476    // Change selections again
 6477    cx.set_state(indoc!(
 6478        r#"a«bc
 6479           defgˇ»hi
 6480
 6481           jk
 6482           nlmo
 6483           "#
 6484    ));
 6485
 6486    cx.update_editor(|editor, window, cx| {
 6487        editor.add_selection_below(&Default::default(), window, cx);
 6488    });
 6489
 6490    cx.assert_editor_state(indoc!(
 6491        r#"a«bcˇ»
 6492           d«efgˇ»hi
 6493
 6494           j«kˇ»
 6495           nlmo
 6496           "#
 6497    ));
 6498
 6499    cx.update_editor(|editor, window, cx| {
 6500        editor.add_selection_below(&Default::default(), window, cx);
 6501    });
 6502    cx.assert_editor_state(indoc!(
 6503        r#"a«bcˇ»
 6504           d«efgˇ»hi
 6505
 6506           j«kˇ»
 6507           n«lmoˇ»
 6508           "#
 6509    ));
 6510    cx.update_editor(|editor, window, cx| {
 6511        editor.add_selection_above(&Default::default(), window, cx);
 6512    });
 6513
 6514    cx.assert_editor_state(indoc!(
 6515        r#"a«bcˇ»
 6516           d«efgˇ»hi
 6517
 6518           j«kˇ»
 6519           nlmo
 6520           "#
 6521    ));
 6522
 6523    // Change selections again
 6524    cx.set_state(indoc!(
 6525        r#"abc
 6526           d«ˇefghi
 6527
 6528           jk
 6529           nlm»o
 6530           "#
 6531    ));
 6532
 6533    cx.update_editor(|editor, window, cx| {
 6534        editor.add_selection_above(&Default::default(), window, cx);
 6535    });
 6536
 6537    cx.assert_editor_state(indoc!(
 6538        r#"a«ˇbc»
 6539           d«ˇef»ghi
 6540
 6541           j«ˇk»
 6542           n«ˇlm»o
 6543           "#
 6544    ));
 6545
 6546    cx.update_editor(|editor, window, cx| {
 6547        editor.add_selection_below(&Default::default(), window, cx);
 6548    });
 6549
 6550    cx.assert_editor_state(indoc!(
 6551        r#"abc
 6552           d«ˇef»ghi
 6553
 6554           j«ˇk»
 6555           n«ˇlm»o
 6556           "#
 6557    ));
 6558}
 6559
 6560#[gpui::test]
 6561async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6562    init_test(cx, |_| {});
 6563    let mut cx = EditorTestContext::new(cx).await;
 6564
 6565    cx.set_state(indoc!(
 6566        r#"line onˇe
 6567           liˇne two
 6568           line three
 6569           line four"#
 6570    ));
 6571
 6572    cx.update_editor(|editor, window, cx| {
 6573        editor.add_selection_below(&Default::default(), window, cx);
 6574    });
 6575
 6576    // test multiple cursors expand in the same direction
 6577    cx.assert_editor_state(indoc!(
 6578        r#"line onˇe
 6579           liˇne twˇo
 6580           liˇne three
 6581           line four"#
 6582    ));
 6583
 6584    cx.update_editor(|editor, window, cx| {
 6585        editor.add_selection_below(&Default::default(), window, cx);
 6586    });
 6587
 6588    cx.update_editor(|editor, window, cx| {
 6589        editor.add_selection_below(&Default::default(), window, cx);
 6590    });
 6591
 6592    // test multiple cursors expand below overflow
 6593    cx.assert_editor_state(indoc!(
 6594        r#"line onˇe
 6595           liˇne twˇo
 6596           liˇne thˇree
 6597           liˇne foˇur"#
 6598    ));
 6599
 6600    cx.update_editor(|editor, window, cx| {
 6601        editor.add_selection_above(&Default::default(), window, cx);
 6602    });
 6603
 6604    // test multiple cursors retrieves back correctly
 6605    cx.assert_editor_state(indoc!(
 6606        r#"line onˇe
 6607           liˇne twˇo
 6608           liˇne thˇree
 6609           line four"#
 6610    ));
 6611
 6612    cx.update_editor(|editor, window, cx| {
 6613        editor.add_selection_above(&Default::default(), window, cx);
 6614    });
 6615
 6616    cx.update_editor(|editor, window, cx| {
 6617        editor.add_selection_above(&Default::default(), window, cx);
 6618    });
 6619
 6620    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6621    cx.assert_editor_state(indoc!(
 6622        r#"liˇne onˇe
 6623           liˇne two
 6624           line three
 6625           line four"#
 6626    ));
 6627
 6628    cx.update_editor(|editor, window, cx| {
 6629        editor.undo_selection(&Default::default(), window, cx);
 6630    });
 6631
 6632    // test undo
 6633    cx.assert_editor_state(indoc!(
 6634        r#"line onˇe
 6635           liˇne twˇo
 6636           line three
 6637           line four"#
 6638    ));
 6639
 6640    cx.update_editor(|editor, window, cx| {
 6641        editor.redo_selection(&Default::default(), window, cx);
 6642    });
 6643
 6644    // test redo
 6645    cx.assert_editor_state(indoc!(
 6646        r#"liˇne onˇe
 6647           liˇne two
 6648           line three
 6649           line four"#
 6650    ));
 6651
 6652    cx.set_state(indoc!(
 6653        r#"abcd
 6654           ef«ghˇ»
 6655           ijkl
 6656           «mˇ»nop"#
 6657    ));
 6658
 6659    cx.update_editor(|editor, window, cx| {
 6660        editor.add_selection_above(&Default::default(), window, cx);
 6661    });
 6662
 6663    // test multiple selections expand in the same direction
 6664    cx.assert_editor_state(indoc!(
 6665        r#"ab«cdˇ»
 6666           ef«ghˇ»
 6667           «iˇ»jkl
 6668           «mˇ»nop"#
 6669    ));
 6670
 6671    cx.update_editor(|editor, window, cx| {
 6672        editor.add_selection_above(&Default::default(), window, cx);
 6673    });
 6674
 6675    // test multiple selection upward overflow
 6676    cx.assert_editor_state(indoc!(
 6677        r#"ab«cdˇ»
 6678           «eˇ»f«ghˇ»
 6679           «iˇ»jkl
 6680           «mˇ»nop"#
 6681    ));
 6682
 6683    cx.update_editor(|editor, window, cx| {
 6684        editor.add_selection_below(&Default::default(), window, cx);
 6685    });
 6686
 6687    // test multiple selection retrieves back correctly
 6688    cx.assert_editor_state(indoc!(
 6689        r#"abcd
 6690           ef«ghˇ»
 6691           «iˇ»jkl
 6692           «mˇ»nop"#
 6693    ));
 6694
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.add_selection_below(&Default::default(), window, cx);
 6697    });
 6698
 6699    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6700    cx.assert_editor_state(indoc!(
 6701        r#"abcd
 6702           ef«ghˇ»
 6703           ij«klˇ»
 6704           «mˇ»nop"#
 6705    ));
 6706
 6707    cx.update_editor(|editor, window, cx| {
 6708        editor.undo_selection(&Default::default(), window, cx);
 6709    });
 6710
 6711    // test undo
 6712    cx.assert_editor_state(indoc!(
 6713        r#"abcd
 6714           ef«ghˇ»
 6715           «iˇ»jkl
 6716           «mˇ»nop"#
 6717    ));
 6718
 6719    cx.update_editor(|editor, window, cx| {
 6720        editor.redo_selection(&Default::default(), window, cx);
 6721    });
 6722
 6723    // test redo
 6724    cx.assert_editor_state(indoc!(
 6725        r#"abcd
 6726           ef«ghˇ»
 6727           ij«klˇ»
 6728           «mˇ»nop"#
 6729    ));
 6730}
 6731
 6732#[gpui::test]
 6733async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6734    init_test(cx, |_| {});
 6735    let mut cx = EditorTestContext::new(cx).await;
 6736
 6737    cx.set_state(indoc!(
 6738        r#"line onˇe
 6739           liˇne two
 6740           line three
 6741           line four"#
 6742    ));
 6743
 6744    cx.update_editor(|editor, window, cx| {
 6745        editor.add_selection_below(&Default::default(), window, cx);
 6746        editor.add_selection_below(&Default::default(), window, cx);
 6747        editor.add_selection_below(&Default::default(), window, cx);
 6748    });
 6749
 6750    // initial state with two multi cursor groups
 6751    cx.assert_editor_state(indoc!(
 6752        r#"line onˇe
 6753           liˇne twˇo
 6754           liˇne thˇree
 6755           liˇne foˇur"#
 6756    ));
 6757
 6758    // add single cursor in middle - simulate opt click
 6759    cx.update_editor(|editor, window, cx| {
 6760        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6761        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6762        editor.end_selection(window, cx);
 6763    });
 6764
 6765    cx.assert_editor_state(indoc!(
 6766        r#"line onˇe
 6767           liˇne twˇo
 6768           liˇneˇ thˇree
 6769           liˇne foˇur"#
 6770    ));
 6771
 6772    cx.update_editor(|editor, window, cx| {
 6773        editor.add_selection_above(&Default::default(), window, cx);
 6774    });
 6775
 6776    // test new added selection expands above and existing selection shrinks
 6777    cx.assert_editor_state(indoc!(
 6778        r#"line onˇe
 6779           liˇneˇ twˇo
 6780           liˇneˇ thˇree
 6781           line four"#
 6782    ));
 6783
 6784    cx.update_editor(|editor, window, cx| {
 6785        editor.add_selection_above(&Default::default(), window, cx);
 6786    });
 6787
 6788    // test new added selection expands above and existing selection shrinks
 6789    cx.assert_editor_state(indoc!(
 6790        r#"lineˇ onˇe
 6791           liˇneˇ twˇo
 6792           lineˇ three
 6793           line four"#
 6794    ));
 6795
 6796    // intial state with two selection groups
 6797    cx.set_state(indoc!(
 6798        r#"abcd
 6799           ef«ghˇ»
 6800           ijkl
 6801           «mˇ»nop"#
 6802    ));
 6803
 6804    cx.update_editor(|editor, window, cx| {
 6805        editor.add_selection_above(&Default::default(), window, cx);
 6806        editor.add_selection_above(&Default::default(), window, cx);
 6807    });
 6808
 6809    cx.assert_editor_state(indoc!(
 6810        r#"ab«cdˇ»
 6811           «eˇ»f«ghˇ»
 6812           «iˇ»jkl
 6813           «mˇ»nop"#
 6814    ));
 6815
 6816    // add single selection in middle - simulate opt drag
 6817    cx.update_editor(|editor, window, cx| {
 6818        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6819        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6820        editor.update_selection(
 6821            DisplayPoint::new(DisplayRow(2), 4),
 6822            0,
 6823            gpui::Point::<f32>::default(),
 6824            window,
 6825            cx,
 6826        );
 6827        editor.end_selection(window, cx);
 6828    });
 6829
 6830    cx.assert_editor_state(indoc!(
 6831        r#"ab«cdˇ»
 6832           «eˇ»f«ghˇ»
 6833           «iˇ»jk«lˇ»
 6834           «mˇ»nop"#
 6835    ));
 6836
 6837    cx.update_editor(|editor, window, cx| {
 6838        editor.add_selection_below(&Default::default(), window, cx);
 6839    });
 6840
 6841    // test new added selection expands below, others shrinks from above
 6842    cx.assert_editor_state(indoc!(
 6843        r#"abcd
 6844           ef«ghˇ»
 6845           «iˇ»jk«lˇ»
 6846           «mˇ»no«pˇ»"#
 6847    ));
 6848}
 6849
 6850#[gpui::test]
 6851async fn test_select_next(cx: &mut TestAppContext) {
 6852    init_test(cx, |_| {});
 6853
 6854    let mut cx = EditorTestContext::new(cx).await;
 6855    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6856
 6857    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6858        .unwrap();
 6859    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6860
 6861    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6862        .unwrap();
 6863    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6864
 6865    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6866    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6867
 6868    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6869    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6870
 6871    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6872        .unwrap();
 6873    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6874
 6875    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6876        .unwrap();
 6877    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6878
 6879    // Test selection direction should be preserved
 6880    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6881
 6882    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6883        .unwrap();
 6884    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6885}
 6886
 6887#[gpui::test]
 6888async fn test_select_all_matches(cx: &mut TestAppContext) {
 6889    init_test(cx, |_| {});
 6890
 6891    let mut cx = EditorTestContext::new(cx).await;
 6892
 6893    // Test caret-only selections
 6894    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6895    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6896        .unwrap();
 6897    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6898
 6899    // Test left-to-right selections
 6900    cx.set_state("abc\n«abcˇ»\nabc");
 6901    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6902        .unwrap();
 6903    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6904
 6905    // Test right-to-left selections
 6906    cx.set_state("abc\n«ˇabc»\nabc");
 6907    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6908        .unwrap();
 6909    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6910
 6911    // Test selecting whitespace with caret selection
 6912    cx.set_state("abc\nˇ   abc\nabc");
 6913    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6914        .unwrap();
 6915    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6916
 6917    // Test selecting whitespace with left-to-right selection
 6918    cx.set_state("abc\n«ˇ  »abc\nabc");
 6919    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6920        .unwrap();
 6921    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6922
 6923    // Test no matches with right-to-left selection
 6924    cx.set_state("abc\n«  ˇ»abc\nabc");
 6925    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6926        .unwrap();
 6927    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6928
 6929    // Test with a single word and clip_at_line_ends=true (#29823)
 6930    cx.set_state("aˇbc");
 6931    cx.update_editor(|e, window, cx| {
 6932        e.set_clip_at_line_ends(true, cx);
 6933        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6934        e.set_clip_at_line_ends(false, cx);
 6935    });
 6936    cx.assert_editor_state("«abcˇ»");
 6937}
 6938
 6939#[gpui::test]
 6940async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6941    init_test(cx, |_| {});
 6942
 6943    let mut cx = EditorTestContext::new(cx).await;
 6944
 6945    let large_body_1 = "\nd".repeat(200);
 6946    let large_body_2 = "\ne".repeat(200);
 6947
 6948    cx.set_state(&format!(
 6949        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6950    ));
 6951    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6952        let scroll_position = editor.scroll_position(cx);
 6953        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6954        scroll_position
 6955    });
 6956
 6957    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6958        .unwrap();
 6959    cx.assert_editor_state(&format!(
 6960        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6961    ));
 6962    let scroll_position_after_selection =
 6963        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6964    assert_eq!(
 6965        initial_scroll_position, scroll_position_after_selection,
 6966        "Scroll position should not change after selecting all matches"
 6967    );
 6968}
 6969
 6970#[gpui::test]
 6971async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6972    init_test(cx, |_| {});
 6973
 6974    let mut cx = EditorLspTestContext::new_rust(
 6975        lsp::ServerCapabilities {
 6976            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6977            ..Default::default()
 6978        },
 6979        cx,
 6980    )
 6981    .await;
 6982
 6983    cx.set_state(indoc! {"
 6984        line 1
 6985        line 2
 6986        linˇe 3
 6987        line 4
 6988        line 5
 6989    "});
 6990
 6991    // Make an edit
 6992    cx.update_editor(|editor, window, cx| {
 6993        editor.handle_input("X", window, cx);
 6994    });
 6995
 6996    // Move cursor to a different position
 6997    cx.update_editor(|editor, window, cx| {
 6998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6999            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7000        });
 7001    });
 7002
 7003    cx.assert_editor_state(indoc! {"
 7004        line 1
 7005        line 2
 7006        linXe 3
 7007        line 4
 7008        liˇne 5
 7009    "});
 7010
 7011    cx.lsp
 7012        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7013            Ok(Some(vec![lsp::TextEdit::new(
 7014                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7015                "PREFIX ".to_string(),
 7016            )]))
 7017        });
 7018
 7019    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7020        .unwrap()
 7021        .await
 7022        .unwrap();
 7023
 7024    cx.assert_editor_state(indoc! {"
 7025        PREFIX line 1
 7026        line 2
 7027        linXe 3
 7028        line 4
 7029        liˇne 5
 7030    "});
 7031
 7032    // Undo formatting
 7033    cx.update_editor(|editor, window, cx| {
 7034        editor.undo(&Default::default(), window, cx);
 7035    });
 7036
 7037    // Verify cursor moved back to position after edit
 7038    cx.assert_editor_state(indoc! {"
 7039        line 1
 7040        line 2
 7041        linXˇe 3
 7042        line 4
 7043        line 5
 7044    "});
 7045}
 7046
 7047#[gpui::test]
 7048async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7049    init_test(cx, |_| {});
 7050
 7051    let mut cx = EditorTestContext::new(cx).await;
 7052
 7053    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7054    cx.update_editor(|editor, window, cx| {
 7055        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7056    });
 7057
 7058    cx.set_state(indoc! {"
 7059        line 1
 7060        line 2
 7061        linˇe 3
 7062        line 4
 7063        line 5
 7064        line 6
 7065        line 7
 7066        line 8
 7067        line 9
 7068        line 10
 7069    "});
 7070
 7071    let snapshot = cx.buffer_snapshot();
 7072    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7073
 7074    cx.update(|_, cx| {
 7075        provider.update(cx, |provider, _| {
 7076            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7077                id: None,
 7078                edits: vec![(edit_position..edit_position, "X".into())],
 7079                edit_preview: None,
 7080            }))
 7081        })
 7082    });
 7083
 7084    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7085    cx.update_editor(|editor, window, cx| {
 7086        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7087    });
 7088
 7089    cx.assert_editor_state(indoc! {"
 7090        line 1
 7091        line 2
 7092        lineXˇ 3
 7093        line 4
 7094        line 5
 7095        line 6
 7096        line 7
 7097        line 8
 7098        line 9
 7099        line 10
 7100    "});
 7101
 7102    cx.update_editor(|editor, window, cx| {
 7103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7104            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7105        });
 7106    });
 7107
 7108    cx.assert_editor_state(indoc! {"
 7109        line 1
 7110        line 2
 7111        lineX 3
 7112        line 4
 7113        line 5
 7114        line 6
 7115        line 7
 7116        line 8
 7117        line 9
 7118        liˇne 10
 7119    "});
 7120
 7121    cx.update_editor(|editor, window, cx| {
 7122        editor.undo(&Default::default(), window, cx);
 7123    });
 7124
 7125    cx.assert_editor_state(indoc! {"
 7126        line 1
 7127        line 2
 7128        lineˇ 3
 7129        line 4
 7130        line 5
 7131        line 6
 7132        line 7
 7133        line 8
 7134        line 9
 7135        line 10
 7136    "});
 7137}
 7138
 7139#[gpui::test]
 7140async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7141    init_test(cx, |_| {});
 7142
 7143    let mut cx = EditorTestContext::new(cx).await;
 7144    cx.set_state(
 7145        r#"let foo = 2;
 7146lˇet foo = 2;
 7147let fooˇ = 2;
 7148let foo = 2;
 7149let foo = ˇ2;"#,
 7150    );
 7151
 7152    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7153        .unwrap();
 7154    cx.assert_editor_state(
 7155        r#"let foo = 2;
 7156«letˇ» foo = 2;
 7157let «fooˇ» = 2;
 7158let foo = 2;
 7159let foo = «2ˇ»;"#,
 7160    );
 7161
 7162    // noop for multiple selections with different contents
 7163    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7164        .unwrap();
 7165    cx.assert_editor_state(
 7166        r#"let foo = 2;
 7167«letˇ» foo = 2;
 7168let «fooˇ» = 2;
 7169let foo = 2;
 7170let foo = «2ˇ»;"#,
 7171    );
 7172
 7173    // Test last selection direction should be preserved
 7174    cx.set_state(
 7175        r#"let foo = 2;
 7176let foo = 2;
 7177let «fooˇ» = 2;
 7178let «ˇfoo» = 2;
 7179let foo = 2;"#,
 7180    );
 7181
 7182    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7183        .unwrap();
 7184    cx.assert_editor_state(
 7185        r#"let foo = 2;
 7186let foo = 2;
 7187let «fooˇ» = 2;
 7188let «ˇfoo» = 2;
 7189let «ˇfoo» = 2;"#,
 7190    );
 7191}
 7192
 7193#[gpui::test]
 7194async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7195    init_test(cx, |_| {});
 7196
 7197    let mut cx =
 7198        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7199
 7200    cx.assert_editor_state(indoc! {"
 7201        ˇbbb
 7202        ccc
 7203
 7204        bbb
 7205        ccc
 7206        "});
 7207    cx.dispatch_action(SelectPrevious::default());
 7208    cx.assert_editor_state(indoc! {"
 7209                «bbbˇ»
 7210                ccc
 7211
 7212                bbb
 7213                ccc
 7214                "});
 7215    cx.dispatch_action(SelectPrevious::default());
 7216    cx.assert_editor_state(indoc! {"
 7217                «bbbˇ»
 7218                ccc
 7219
 7220                «bbbˇ»
 7221                ccc
 7222                "});
 7223}
 7224
 7225#[gpui::test]
 7226async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7227    init_test(cx, |_| {});
 7228
 7229    let mut cx = EditorTestContext::new(cx).await;
 7230    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7231
 7232    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7233        .unwrap();
 7234    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7235
 7236    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7237        .unwrap();
 7238    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7239
 7240    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7241    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7242
 7243    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7244    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7245
 7246    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7247        .unwrap();
 7248    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7249
 7250    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7251        .unwrap();
 7252    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7253}
 7254
 7255#[gpui::test]
 7256async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7257    init_test(cx, |_| {});
 7258
 7259    let mut cx = EditorTestContext::new(cx).await;
 7260    cx.set_state("");
 7261
 7262    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7263        .unwrap();
 7264    cx.assert_editor_state("«aˇ»");
 7265    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7266        .unwrap();
 7267    cx.assert_editor_state("«aˇ»");
 7268}
 7269
 7270#[gpui::test]
 7271async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7272    init_test(cx, |_| {});
 7273
 7274    let mut cx = EditorTestContext::new(cx).await;
 7275    cx.set_state(
 7276        r#"let foo = 2;
 7277lˇet foo = 2;
 7278let fooˇ = 2;
 7279let foo = 2;
 7280let foo = ˇ2;"#,
 7281    );
 7282
 7283    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7284        .unwrap();
 7285    cx.assert_editor_state(
 7286        r#"let foo = 2;
 7287«letˇ» foo = 2;
 7288let «fooˇ» = 2;
 7289let foo = 2;
 7290let foo = «2ˇ»;"#,
 7291    );
 7292
 7293    // noop for multiple selections with different contents
 7294    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7295        .unwrap();
 7296    cx.assert_editor_state(
 7297        r#"let foo = 2;
 7298«letˇ» foo = 2;
 7299let «fooˇ» = 2;
 7300let foo = 2;
 7301let foo = «2ˇ»;"#,
 7302    );
 7303}
 7304
 7305#[gpui::test]
 7306async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7307    init_test(cx, |_| {});
 7308
 7309    let mut cx = EditorTestContext::new(cx).await;
 7310    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7311
 7312    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7313        .unwrap();
 7314    // selection direction is preserved
 7315    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7316
 7317    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7318        .unwrap();
 7319    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7320
 7321    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7322    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7323
 7324    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7325    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7326
 7327    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7328        .unwrap();
 7329    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7330
 7331    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7332        .unwrap();
 7333    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7334}
 7335
 7336#[gpui::test]
 7337async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7338    init_test(cx, |_| {});
 7339
 7340    let language = Arc::new(Language::new(
 7341        LanguageConfig::default(),
 7342        Some(tree_sitter_rust::LANGUAGE.into()),
 7343    ));
 7344
 7345    let text = r#"
 7346        use mod1::mod2::{mod3, mod4};
 7347
 7348        fn fn_1(param1: bool, param2: &str) {
 7349            let var1 = "text";
 7350        }
 7351    "#
 7352    .unindent();
 7353
 7354    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7355    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7356    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7357
 7358    editor
 7359        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7360        .await;
 7361
 7362    editor.update_in(cx, |editor, window, cx| {
 7363        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7364            s.select_display_ranges([
 7365                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7366                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7367                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7368            ]);
 7369        });
 7370        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7371    });
 7372    editor.update(cx, |editor, cx| {
 7373        assert_text_with_selections(
 7374            editor,
 7375            indoc! {r#"
 7376                use mod1::mod2::{mod3, «mod4ˇ»};
 7377
 7378                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7379                    let var1 = "«ˇtext»";
 7380                }
 7381            "#},
 7382            cx,
 7383        );
 7384    });
 7385
 7386    editor.update_in(cx, |editor, window, cx| {
 7387        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7388    });
 7389    editor.update(cx, |editor, cx| {
 7390        assert_text_with_selections(
 7391            editor,
 7392            indoc! {r#"
 7393                use mod1::mod2::«{mod3, mod4}ˇ»;
 7394
 7395                «ˇfn fn_1(param1: bool, param2: &str) {
 7396                    let var1 = "text";
 7397 7398            "#},
 7399            cx,
 7400        );
 7401    });
 7402
 7403    editor.update_in(cx, |editor, window, cx| {
 7404        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7405    });
 7406    assert_eq!(
 7407        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7408        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7409    );
 7410
 7411    // Trying to expand the selected syntax node one more time has no effect.
 7412    editor.update_in(cx, |editor, window, cx| {
 7413        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7414    });
 7415    assert_eq!(
 7416        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7417        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7418    );
 7419
 7420    editor.update_in(cx, |editor, window, cx| {
 7421        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7422    });
 7423    editor.update(cx, |editor, cx| {
 7424        assert_text_with_selections(
 7425            editor,
 7426            indoc! {r#"
 7427                use mod1::mod2::«{mod3, mod4}ˇ»;
 7428
 7429                «ˇfn fn_1(param1: bool, param2: &str) {
 7430                    let var1 = "text";
 7431 7432            "#},
 7433            cx,
 7434        );
 7435    });
 7436
 7437    editor.update_in(cx, |editor, window, cx| {
 7438        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7439    });
 7440    editor.update(cx, |editor, cx| {
 7441        assert_text_with_selections(
 7442            editor,
 7443            indoc! {r#"
 7444                use mod1::mod2::{mod3, «mod4ˇ»};
 7445
 7446                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7447                    let var1 = "«ˇtext»";
 7448                }
 7449            "#},
 7450            cx,
 7451        );
 7452    });
 7453
 7454    editor.update_in(cx, |editor, window, cx| {
 7455        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7456    });
 7457    editor.update(cx, |editor, cx| {
 7458        assert_text_with_selections(
 7459            editor,
 7460            indoc! {r#"
 7461                use mod1::mod2::{mod3, mo«ˇ»d4};
 7462
 7463                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7464                    let var1 = "te«ˇ»xt";
 7465                }
 7466            "#},
 7467            cx,
 7468        );
 7469    });
 7470
 7471    // Trying to shrink the selected syntax node one more time has no effect.
 7472    editor.update_in(cx, |editor, window, cx| {
 7473        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7474    });
 7475    editor.update_in(cx, |editor, _, cx| {
 7476        assert_text_with_selections(
 7477            editor,
 7478            indoc! {r#"
 7479                use mod1::mod2::{mod3, mo«ˇ»d4};
 7480
 7481                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7482                    let var1 = "te«ˇ»xt";
 7483                }
 7484            "#},
 7485            cx,
 7486        );
 7487    });
 7488
 7489    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7490    // a fold.
 7491    editor.update_in(cx, |editor, window, cx| {
 7492        editor.fold_creases(
 7493            vec![
 7494                Crease::simple(
 7495                    Point::new(0, 21)..Point::new(0, 24),
 7496                    FoldPlaceholder::test(),
 7497                ),
 7498                Crease::simple(
 7499                    Point::new(3, 20)..Point::new(3, 22),
 7500                    FoldPlaceholder::test(),
 7501                ),
 7502            ],
 7503            true,
 7504            window,
 7505            cx,
 7506        );
 7507        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7508    });
 7509    editor.update(cx, |editor, cx| {
 7510        assert_text_with_selections(
 7511            editor,
 7512            indoc! {r#"
 7513                use mod1::mod2::«{mod3, mod4}ˇ»;
 7514
 7515                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7516                    let var1 = "«ˇtext»";
 7517                }
 7518            "#},
 7519            cx,
 7520        );
 7521    });
 7522}
 7523
 7524#[gpui::test]
 7525async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7526    init_test(cx, |_| {});
 7527
 7528    let language = Arc::new(Language::new(
 7529        LanguageConfig::default(),
 7530        Some(tree_sitter_rust::LANGUAGE.into()),
 7531    ));
 7532
 7533    let text = "let a = 2;";
 7534
 7535    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7536    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7537    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7538
 7539    editor
 7540        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7541        .await;
 7542
 7543    // Test case 1: Cursor at end of word
 7544    editor.update_in(cx, |editor, window, cx| {
 7545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7546            s.select_display_ranges([
 7547                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7548            ]);
 7549        });
 7550    });
 7551    editor.update(cx, |editor, cx| {
 7552        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7553    });
 7554    editor.update_in(cx, |editor, window, cx| {
 7555        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7556    });
 7557    editor.update(cx, |editor, cx| {
 7558        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7559    });
 7560    editor.update_in(cx, |editor, window, cx| {
 7561        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7562    });
 7563    editor.update(cx, |editor, cx| {
 7564        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7565    });
 7566
 7567    // Test case 2: Cursor at end of statement
 7568    editor.update_in(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7572            ]);
 7573        });
 7574    });
 7575    editor.update(cx, |editor, cx| {
 7576        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7577    });
 7578    editor.update_in(cx, |editor, window, cx| {
 7579        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7580    });
 7581    editor.update(cx, |editor, cx| {
 7582        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7583    });
 7584}
 7585
 7586#[gpui::test]
 7587async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7588    init_test(cx, |_| {});
 7589
 7590    let language = Arc::new(Language::new(
 7591        LanguageConfig::default(),
 7592        Some(tree_sitter_rust::LANGUAGE.into()),
 7593    ));
 7594
 7595    let text = r#"
 7596        use mod1::mod2::{mod3, mod4};
 7597
 7598        fn fn_1(param1: bool, param2: &str) {
 7599            let var1 = "hello world";
 7600        }
 7601    "#
 7602    .unindent();
 7603
 7604    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7605    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7606    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7607
 7608    editor
 7609        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7610        .await;
 7611
 7612    // Test 1: Cursor on a letter of a string word
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7615            s.select_display_ranges([
 7616                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7617            ]);
 7618        });
 7619    });
 7620    editor.update_in(cx, |editor, window, cx| {
 7621        assert_text_with_selections(
 7622            editor,
 7623            indoc! {r#"
 7624                use mod1::mod2::{mod3, mod4};
 7625
 7626                fn fn_1(param1: bool, param2: &str) {
 7627                    let var1 = "hˇello world";
 7628                }
 7629            "#},
 7630            cx,
 7631        );
 7632        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7633        assert_text_with_selections(
 7634            editor,
 7635            indoc! {r#"
 7636                use mod1::mod2::{mod3, mod4};
 7637
 7638                fn fn_1(param1: bool, param2: &str) {
 7639                    let var1 = "«ˇhello» world";
 7640                }
 7641            "#},
 7642            cx,
 7643        );
 7644    });
 7645
 7646    // Test 2: Partial selection within a word
 7647    editor.update_in(cx, |editor, window, cx| {
 7648        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7649            s.select_display_ranges([
 7650                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7651            ]);
 7652        });
 7653    });
 7654    editor.update_in(cx, |editor, window, cx| {
 7655        assert_text_with_selections(
 7656            editor,
 7657            indoc! {r#"
 7658                use mod1::mod2::{mod3, mod4};
 7659
 7660                fn fn_1(param1: bool, param2: &str) {
 7661                    let var1 = "h«elˇ»lo world";
 7662                }
 7663            "#},
 7664            cx,
 7665        );
 7666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7667        assert_text_with_selections(
 7668            editor,
 7669            indoc! {r#"
 7670                use mod1::mod2::{mod3, mod4};
 7671
 7672                fn fn_1(param1: bool, param2: &str) {
 7673                    let var1 = "«ˇhello» world";
 7674                }
 7675            "#},
 7676            cx,
 7677        );
 7678    });
 7679
 7680    // Test 3: Complete word already selected
 7681    editor.update_in(cx, |editor, window, cx| {
 7682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7683            s.select_display_ranges([
 7684                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7685            ]);
 7686        });
 7687    });
 7688    editor.update_in(cx, |editor, window, cx| {
 7689        assert_text_with_selections(
 7690            editor,
 7691            indoc! {r#"
 7692                use mod1::mod2::{mod3, mod4};
 7693
 7694                fn fn_1(param1: bool, param2: &str) {
 7695                    let var1 = "«helloˇ» world";
 7696                }
 7697            "#},
 7698            cx,
 7699        );
 7700        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7701        assert_text_with_selections(
 7702            editor,
 7703            indoc! {r#"
 7704                use mod1::mod2::{mod3, mod4};
 7705
 7706                fn fn_1(param1: bool, param2: &str) {
 7707                    let var1 = "«hello worldˇ»";
 7708                }
 7709            "#},
 7710            cx,
 7711        );
 7712    });
 7713
 7714    // Test 4: Selection spanning across words
 7715    editor.update_in(cx, |editor, window, cx| {
 7716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7717            s.select_display_ranges([
 7718                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7719            ]);
 7720        });
 7721    });
 7722    editor.update_in(cx, |editor, window, cx| {
 7723        assert_text_with_selections(
 7724            editor,
 7725            indoc! {r#"
 7726                use mod1::mod2::{mod3, mod4};
 7727
 7728                fn fn_1(param1: bool, param2: &str) {
 7729                    let var1 = "hel«lo woˇ»rld";
 7730                }
 7731            "#},
 7732            cx,
 7733        );
 7734        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7735        assert_text_with_selections(
 7736            editor,
 7737            indoc! {r#"
 7738                use mod1::mod2::{mod3, mod4};
 7739
 7740                fn fn_1(param1: bool, param2: &str) {
 7741                    let var1 = "«ˇhello world»";
 7742                }
 7743            "#},
 7744            cx,
 7745        );
 7746    });
 7747
 7748    // Test 5: Expansion beyond string
 7749    editor.update_in(cx, |editor, window, cx| {
 7750        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7752        assert_text_with_selections(
 7753            editor,
 7754            indoc! {r#"
 7755                use mod1::mod2::{mod3, mod4};
 7756
 7757                fn fn_1(param1: bool, param2: &str) {
 7758                    «ˇlet var1 = "hello world";»
 7759                }
 7760            "#},
 7761            cx,
 7762        );
 7763    });
 7764}
 7765
 7766#[gpui::test]
 7767async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7768    init_test(cx, |_| {});
 7769
 7770    let base_text = r#"
 7771        impl A {
 7772            // this is an uncommitted comment
 7773
 7774            fn b() {
 7775                c();
 7776            }
 7777
 7778            // this is another uncommitted comment
 7779
 7780            fn d() {
 7781                // e
 7782                // f
 7783            }
 7784        }
 7785
 7786        fn g() {
 7787            // h
 7788        }
 7789    "#
 7790    .unindent();
 7791
 7792    let text = r#"
 7793        ˇimpl A {
 7794
 7795            fn b() {
 7796                c();
 7797            }
 7798
 7799            fn d() {
 7800                // e
 7801                // f
 7802            }
 7803        }
 7804
 7805        fn g() {
 7806            // h
 7807        }
 7808    "#
 7809    .unindent();
 7810
 7811    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7812    cx.set_state(&text);
 7813    cx.set_head_text(&base_text);
 7814    cx.update_editor(|editor, window, cx| {
 7815        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7816    });
 7817
 7818    cx.assert_state_with_diff(
 7819        "
 7820        ˇimpl A {
 7821      -     // this is an uncommitted comment
 7822
 7823            fn b() {
 7824                c();
 7825            }
 7826
 7827      -     // this is another uncommitted comment
 7828      -
 7829            fn d() {
 7830                // e
 7831                // f
 7832            }
 7833        }
 7834
 7835        fn g() {
 7836            // h
 7837        }
 7838    "
 7839        .unindent(),
 7840    );
 7841
 7842    let expected_display_text = "
 7843        impl A {
 7844            // this is an uncommitted comment
 7845
 7846            fn b() {
 7847 7848            }
 7849
 7850            // this is another uncommitted comment
 7851
 7852            fn d() {
 7853 7854            }
 7855        }
 7856
 7857        fn g() {
 7858 7859        }
 7860        "
 7861    .unindent();
 7862
 7863    cx.update_editor(|editor, window, cx| {
 7864        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7865        assert_eq!(editor.display_text(cx), expected_display_text);
 7866    });
 7867}
 7868
 7869#[gpui::test]
 7870async fn test_autoindent(cx: &mut TestAppContext) {
 7871    init_test(cx, |_| {});
 7872
 7873    let language = Arc::new(
 7874        Language::new(
 7875            LanguageConfig {
 7876                brackets: BracketPairConfig {
 7877                    pairs: vec![
 7878                        BracketPair {
 7879                            start: "{".to_string(),
 7880                            end: "}".to_string(),
 7881                            close: false,
 7882                            surround: false,
 7883                            newline: true,
 7884                        },
 7885                        BracketPair {
 7886                            start: "(".to_string(),
 7887                            end: ")".to_string(),
 7888                            close: false,
 7889                            surround: false,
 7890                            newline: true,
 7891                        },
 7892                    ],
 7893                    ..Default::default()
 7894                },
 7895                ..Default::default()
 7896            },
 7897            Some(tree_sitter_rust::LANGUAGE.into()),
 7898        )
 7899        .with_indents_query(
 7900            r#"
 7901                (_ "(" ")" @end) @indent
 7902                (_ "{" "}" @end) @indent
 7903            "#,
 7904        )
 7905        .unwrap(),
 7906    );
 7907
 7908    let text = "fn a() {}";
 7909
 7910    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7911    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7912    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7913    editor
 7914        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7915        .await;
 7916
 7917    editor.update_in(cx, |editor, window, cx| {
 7918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7919            s.select_ranges([5..5, 8..8, 9..9])
 7920        });
 7921        editor.newline(&Newline, window, cx);
 7922        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7923        assert_eq!(
 7924            editor.selections.ranges(cx),
 7925            &[
 7926                Point::new(1, 4)..Point::new(1, 4),
 7927                Point::new(3, 4)..Point::new(3, 4),
 7928                Point::new(5, 0)..Point::new(5, 0)
 7929            ]
 7930        );
 7931    });
 7932}
 7933
 7934#[gpui::test]
 7935async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7936    init_test(cx, |_| {});
 7937
 7938    {
 7939        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7940        cx.set_state(indoc! {"
 7941            impl A {
 7942
 7943                fn b() {}
 7944
 7945            «fn c() {
 7946
 7947            }ˇ»
 7948            }
 7949        "});
 7950
 7951        cx.update_editor(|editor, window, cx| {
 7952            editor.autoindent(&Default::default(), window, cx);
 7953        });
 7954
 7955        cx.assert_editor_state(indoc! {"
 7956            impl A {
 7957
 7958                fn b() {}
 7959
 7960                «fn c() {
 7961
 7962                }ˇ»
 7963            }
 7964        "});
 7965    }
 7966
 7967    {
 7968        let mut cx = EditorTestContext::new_multibuffer(
 7969            cx,
 7970            [indoc! { "
 7971                impl A {
 7972                «
 7973                // a
 7974                fn b(){}
 7975                »
 7976                «
 7977                    }
 7978                    fn c(){}
 7979                »
 7980            "}],
 7981        );
 7982
 7983        let buffer = cx.update_editor(|editor, _, cx| {
 7984            let buffer = editor.buffer().update(cx, |buffer, _| {
 7985                buffer.all_buffers().iter().next().unwrap().clone()
 7986            });
 7987            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7988            buffer
 7989        });
 7990
 7991        cx.run_until_parked();
 7992        cx.update_editor(|editor, window, cx| {
 7993            editor.select_all(&Default::default(), window, cx);
 7994            editor.autoindent(&Default::default(), window, cx)
 7995        });
 7996        cx.run_until_parked();
 7997
 7998        cx.update(|_, cx| {
 7999            assert_eq!(
 8000                buffer.read(cx).text(),
 8001                indoc! { "
 8002                    impl A {
 8003
 8004                        // a
 8005                        fn b(){}
 8006
 8007
 8008                    }
 8009                    fn c(){}
 8010
 8011                " }
 8012            )
 8013        });
 8014    }
 8015}
 8016
 8017#[gpui::test]
 8018async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8019    init_test(cx, |_| {});
 8020
 8021    let mut cx = EditorTestContext::new(cx).await;
 8022
 8023    let language = Arc::new(Language::new(
 8024        LanguageConfig {
 8025            brackets: BracketPairConfig {
 8026                pairs: vec![
 8027                    BracketPair {
 8028                        start: "{".to_string(),
 8029                        end: "}".to_string(),
 8030                        close: true,
 8031                        surround: true,
 8032                        newline: true,
 8033                    },
 8034                    BracketPair {
 8035                        start: "(".to_string(),
 8036                        end: ")".to_string(),
 8037                        close: true,
 8038                        surround: true,
 8039                        newline: true,
 8040                    },
 8041                    BracketPair {
 8042                        start: "/*".to_string(),
 8043                        end: " */".to_string(),
 8044                        close: true,
 8045                        surround: true,
 8046                        newline: true,
 8047                    },
 8048                    BracketPair {
 8049                        start: "[".to_string(),
 8050                        end: "]".to_string(),
 8051                        close: false,
 8052                        surround: false,
 8053                        newline: true,
 8054                    },
 8055                    BracketPair {
 8056                        start: "\"".to_string(),
 8057                        end: "\"".to_string(),
 8058                        close: true,
 8059                        surround: true,
 8060                        newline: false,
 8061                    },
 8062                    BracketPair {
 8063                        start: "<".to_string(),
 8064                        end: ">".to_string(),
 8065                        close: false,
 8066                        surround: true,
 8067                        newline: true,
 8068                    },
 8069                ],
 8070                ..Default::default()
 8071            },
 8072            autoclose_before: "})]".to_string(),
 8073            ..Default::default()
 8074        },
 8075        Some(tree_sitter_rust::LANGUAGE.into()),
 8076    ));
 8077
 8078    cx.language_registry().add(language.clone());
 8079    cx.update_buffer(|buffer, cx| {
 8080        buffer.set_language(Some(language), cx);
 8081    });
 8082
 8083    cx.set_state(
 8084        &r#"
 8085            🏀ˇ
 8086            εˇ
 8087            ❤️ˇ
 8088        "#
 8089        .unindent(),
 8090    );
 8091
 8092    // autoclose multiple nested brackets at multiple cursors
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.handle_input("{", window, cx);
 8095        editor.handle_input("{", window, cx);
 8096        editor.handle_input("{", window, cx);
 8097    });
 8098    cx.assert_editor_state(
 8099        &"
 8100            🏀{{{ˇ}}}
 8101            ε{{{ˇ}}}
 8102            ❤️{{{ˇ}}}
 8103        "
 8104        .unindent(),
 8105    );
 8106
 8107    // insert a different closing bracket
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.handle_input(")", window, cx);
 8110    });
 8111    cx.assert_editor_state(
 8112        &"
 8113            🏀{{{)ˇ}}}
 8114            ε{{{)ˇ}}}
 8115            ❤️{{{)ˇ}}}
 8116        "
 8117        .unindent(),
 8118    );
 8119
 8120    // skip over the auto-closed brackets when typing a closing bracket
 8121    cx.update_editor(|editor, window, cx| {
 8122        editor.move_right(&MoveRight, window, cx);
 8123        editor.handle_input("}", window, cx);
 8124        editor.handle_input("}", window, cx);
 8125        editor.handle_input("}", window, cx);
 8126    });
 8127    cx.assert_editor_state(
 8128        &"
 8129            🏀{{{)}}}}ˇ
 8130            ε{{{)}}}}ˇ
 8131            ❤️{{{)}}}}ˇ
 8132        "
 8133        .unindent(),
 8134    );
 8135
 8136    // autoclose multi-character pairs
 8137    cx.set_state(
 8138        &"
 8139            ˇ
 8140            ˇ
 8141        "
 8142        .unindent(),
 8143    );
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.handle_input("/", window, cx);
 8146        editor.handle_input("*", window, cx);
 8147    });
 8148    cx.assert_editor_state(
 8149        &"
 8150            /*ˇ */
 8151            /*ˇ */
 8152        "
 8153        .unindent(),
 8154    );
 8155
 8156    // one cursor autocloses a multi-character pair, one cursor
 8157    // does not autoclose.
 8158    cx.set_state(
 8159        &"
 8160 8161            ˇ
 8162        "
 8163        .unindent(),
 8164    );
 8165    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8166    cx.assert_editor_state(
 8167        &"
 8168            /*ˇ */
 8169 8170        "
 8171        .unindent(),
 8172    );
 8173
 8174    // Don't autoclose if the next character isn't whitespace and isn't
 8175    // listed in the language's "autoclose_before" section.
 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    // Don't autoclose if `close` is false for the bracket pair
 8181    cx.set_state("ˇ");
 8182    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8183    cx.assert_editor_state("");
 8184
 8185    // Surround with brackets if text is selected
 8186    cx.set_state("«aˇ» b");
 8187    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8188    cx.assert_editor_state("{«aˇ»} b");
 8189
 8190    // Autoclose when not immediately after a word character
 8191    cx.set_state("a ˇ");
 8192    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8193    cx.assert_editor_state("a \"ˇ\"");
 8194
 8195    // Autoclose pair where the start and end characters are the same
 8196    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8197    cx.assert_editor_state("a \"\"ˇ");
 8198
 8199    // Don't autoclose when immediately after a word character
 8200    cx.set_state("");
 8201    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8202    cx.assert_editor_state("a\"ˇ");
 8203
 8204    // Do autoclose when after a non-word character
 8205    cx.set_state("");
 8206    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8207    cx.assert_editor_state("{\"ˇ\"");
 8208
 8209    // Non identical pairs autoclose regardless of preceding character
 8210    cx.set_state("");
 8211    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8212    cx.assert_editor_state("a{ˇ}");
 8213
 8214    // Don't autoclose pair if autoclose is disabled
 8215    cx.set_state("ˇ");
 8216    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8217    cx.assert_editor_state("");
 8218
 8219    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8220    cx.set_state("«aˇ» b");
 8221    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8222    cx.assert_editor_state("<«aˇ»> b");
 8223}
 8224
 8225#[gpui::test]
 8226async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8227    init_test(cx, |settings| {
 8228        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8229    });
 8230
 8231    let mut cx = EditorTestContext::new(cx).await;
 8232
 8233    let language = Arc::new(Language::new(
 8234        LanguageConfig {
 8235            brackets: BracketPairConfig {
 8236                pairs: vec![
 8237                    BracketPair {
 8238                        start: "{".to_string(),
 8239                        end: "}".to_string(),
 8240                        close: true,
 8241                        surround: true,
 8242                        newline: true,
 8243                    },
 8244                    BracketPair {
 8245                        start: "(".to_string(),
 8246                        end: ")".to_string(),
 8247                        close: true,
 8248                        surround: true,
 8249                        newline: true,
 8250                    },
 8251                    BracketPair {
 8252                        start: "[".to_string(),
 8253                        end: "]".to_string(),
 8254                        close: false,
 8255                        surround: false,
 8256                        newline: true,
 8257                    },
 8258                ],
 8259                ..Default::default()
 8260            },
 8261            autoclose_before: "})]".to_string(),
 8262            ..Default::default()
 8263        },
 8264        Some(tree_sitter_rust::LANGUAGE.into()),
 8265    ));
 8266
 8267    cx.language_registry().add(language.clone());
 8268    cx.update_buffer(|buffer, cx| {
 8269        buffer.set_language(Some(language), cx);
 8270    });
 8271
 8272    cx.set_state(
 8273        &"
 8274            ˇ
 8275            ˇ
 8276            ˇ
 8277        "
 8278        .unindent(),
 8279    );
 8280
 8281    // ensure only matching closing brackets are skipped over
 8282    cx.update_editor(|editor, window, cx| {
 8283        editor.handle_input("}", window, cx);
 8284        editor.move_left(&MoveLeft, window, cx);
 8285        editor.handle_input(")", window, cx);
 8286        editor.move_left(&MoveLeft, window, cx);
 8287    });
 8288    cx.assert_editor_state(
 8289        &"
 8290            ˇ)}
 8291            ˇ)}
 8292            ˇ)}
 8293        "
 8294        .unindent(),
 8295    );
 8296
 8297    // skip-over closing brackets at multiple cursors
 8298    cx.update_editor(|editor, window, cx| {
 8299        editor.handle_input(")", window, cx);
 8300        editor.handle_input("}", window, cx);
 8301    });
 8302    cx.assert_editor_state(
 8303        &"
 8304            )}ˇ
 8305            )}ˇ
 8306            )}ˇ
 8307        "
 8308        .unindent(),
 8309    );
 8310
 8311    // ignore non-close brackets
 8312    cx.update_editor(|editor, window, cx| {
 8313        editor.handle_input("]", window, cx);
 8314        editor.move_left(&MoveLeft, window, cx);
 8315        editor.handle_input("]", window, cx);
 8316    });
 8317    cx.assert_editor_state(
 8318        &"
 8319            )}]ˇ]
 8320            )}]ˇ]
 8321            )}]ˇ]
 8322        "
 8323        .unindent(),
 8324    );
 8325}
 8326
 8327#[gpui::test]
 8328async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8329    init_test(cx, |_| {});
 8330
 8331    let mut cx = EditorTestContext::new(cx).await;
 8332
 8333    let html_language = Arc::new(
 8334        Language::new(
 8335            LanguageConfig {
 8336                name: "HTML".into(),
 8337                brackets: BracketPairConfig {
 8338                    pairs: vec![
 8339                        BracketPair {
 8340                            start: "<".into(),
 8341                            end: ">".into(),
 8342                            close: true,
 8343                            ..Default::default()
 8344                        },
 8345                        BracketPair {
 8346                            start: "{".into(),
 8347                            end: "}".into(),
 8348                            close: true,
 8349                            ..Default::default()
 8350                        },
 8351                        BracketPair {
 8352                            start: "(".into(),
 8353                            end: ")".into(),
 8354                            close: true,
 8355                            ..Default::default()
 8356                        },
 8357                    ],
 8358                    ..Default::default()
 8359                },
 8360                autoclose_before: "})]>".into(),
 8361                ..Default::default()
 8362            },
 8363            Some(tree_sitter_html::LANGUAGE.into()),
 8364        )
 8365        .with_injection_query(
 8366            r#"
 8367            (script_element
 8368                (raw_text) @injection.content
 8369                (#set! injection.language "javascript"))
 8370            "#,
 8371        )
 8372        .unwrap(),
 8373    );
 8374
 8375    let javascript_language = Arc::new(Language::new(
 8376        LanguageConfig {
 8377            name: "JavaScript".into(),
 8378            brackets: BracketPairConfig {
 8379                pairs: vec![
 8380                    BracketPair {
 8381                        start: "/*".into(),
 8382                        end: " */".into(),
 8383                        close: true,
 8384                        ..Default::default()
 8385                    },
 8386                    BracketPair {
 8387                        start: "{".into(),
 8388                        end: "}".into(),
 8389                        close: true,
 8390                        ..Default::default()
 8391                    },
 8392                    BracketPair {
 8393                        start: "(".into(),
 8394                        end: ")".into(),
 8395                        close: true,
 8396                        ..Default::default()
 8397                    },
 8398                ],
 8399                ..Default::default()
 8400            },
 8401            autoclose_before: "})]>".into(),
 8402            ..Default::default()
 8403        },
 8404        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8405    ));
 8406
 8407    cx.language_registry().add(html_language.clone());
 8408    cx.language_registry().add(javascript_language.clone());
 8409
 8410    cx.update_buffer(|buffer, cx| {
 8411        buffer.set_language(Some(html_language), cx);
 8412    });
 8413
 8414    cx.set_state(
 8415        &r#"
 8416            <body>ˇ
 8417                <script>
 8418                    var x = 1;ˇ
 8419                </script>
 8420            </body>ˇ
 8421        "#
 8422        .unindent(),
 8423    );
 8424
 8425    // Precondition: different languages are active at different locations.
 8426    cx.update_editor(|editor, window, cx| {
 8427        let snapshot = editor.snapshot(window, cx);
 8428        let cursors = editor.selections.ranges::<usize>(cx);
 8429        let languages = cursors
 8430            .iter()
 8431            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8432            .collect::<Vec<_>>();
 8433        assert_eq!(
 8434            languages,
 8435            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8436        );
 8437    });
 8438
 8439    // Angle brackets autoclose in HTML, but not JavaScript.
 8440    cx.update_editor(|editor, window, cx| {
 8441        editor.handle_input("<", window, cx);
 8442        editor.handle_input("a", window, cx);
 8443    });
 8444    cx.assert_editor_state(
 8445        &r#"
 8446            <body><aˇ>
 8447                <script>
 8448                    var x = 1;<aˇ
 8449                </script>
 8450            </body><aˇ>
 8451        "#
 8452        .unindent(),
 8453    );
 8454
 8455    // Curly braces and parens autoclose in both HTML and JavaScript.
 8456    cx.update_editor(|editor, window, cx| {
 8457        editor.handle_input(" b=", window, cx);
 8458        editor.handle_input("{", window, cx);
 8459        editor.handle_input("c", window, cx);
 8460        editor.handle_input("(", window, cx);
 8461    });
 8462    cx.assert_editor_state(
 8463        &r#"
 8464            <body><a b={c(ˇ)}>
 8465                <script>
 8466                    var x = 1;<a b={c(ˇ)}
 8467                </script>
 8468            </body><a b={c(ˇ)}>
 8469        "#
 8470        .unindent(),
 8471    );
 8472
 8473    // Brackets that were already autoclosed are skipped.
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.handle_input(")", window, cx);
 8476        editor.handle_input("d", window, cx);
 8477        editor.handle_input("}", window, cx);
 8478    });
 8479    cx.assert_editor_state(
 8480        &r#"
 8481            <body><a b={c()d}ˇ>
 8482                <script>
 8483                    var x = 1;<a b={c()d}ˇ
 8484                </script>
 8485            </body><a b={c()d}ˇ>
 8486        "#
 8487        .unindent(),
 8488    );
 8489    cx.update_editor(|editor, window, cx| {
 8490        editor.handle_input(">", window, cx);
 8491    });
 8492    cx.assert_editor_state(
 8493        &r#"
 8494            <body><a b={c()d}>ˇ
 8495                <script>
 8496                    var x = 1;<a b={c()d}>ˇ
 8497                </script>
 8498            </body><a b={c()d}>ˇ
 8499        "#
 8500        .unindent(),
 8501    );
 8502
 8503    // Reset
 8504    cx.set_state(
 8505        &r#"
 8506            <body>ˇ
 8507                <script>
 8508                    var x = 1;ˇ
 8509                </script>
 8510            </body>ˇ
 8511        "#
 8512        .unindent(),
 8513    );
 8514
 8515    cx.update_editor(|editor, window, cx| {
 8516        editor.handle_input("<", window, cx);
 8517    });
 8518    cx.assert_editor_state(
 8519        &r#"
 8520            <body><ˇ>
 8521                <script>
 8522                    var x = 1;<ˇ
 8523                </script>
 8524            </body><ˇ>
 8525        "#
 8526        .unindent(),
 8527    );
 8528
 8529    // When backspacing, the closing angle brackets are removed.
 8530    cx.update_editor(|editor, window, cx| {
 8531        editor.backspace(&Backspace, window, cx);
 8532    });
 8533    cx.assert_editor_state(
 8534        &r#"
 8535            <body>ˇ
 8536                <script>
 8537                    var x = 1;ˇ
 8538                </script>
 8539            </body>ˇ
 8540        "#
 8541        .unindent(),
 8542    );
 8543
 8544    // Block comments autoclose in JavaScript, but not HTML.
 8545    cx.update_editor(|editor, window, cx| {
 8546        editor.handle_input("/", window, cx);
 8547        editor.handle_input("*", window, cx);
 8548    });
 8549    cx.assert_editor_state(
 8550        &r#"
 8551            <body>/*ˇ
 8552                <script>
 8553                    var x = 1;/*ˇ */
 8554                </script>
 8555            </body>/*ˇ
 8556        "#
 8557        .unindent(),
 8558    );
 8559}
 8560
 8561#[gpui::test]
 8562async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8563    init_test(cx, |_| {});
 8564
 8565    let mut cx = EditorTestContext::new(cx).await;
 8566
 8567    let rust_language = Arc::new(
 8568        Language::new(
 8569            LanguageConfig {
 8570                name: "Rust".into(),
 8571                brackets: serde_json::from_value(json!([
 8572                    { "start": "{", "end": "}", "close": true, "newline": true },
 8573                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8574                ]))
 8575                .unwrap(),
 8576                autoclose_before: "})]>".into(),
 8577                ..Default::default()
 8578            },
 8579            Some(tree_sitter_rust::LANGUAGE.into()),
 8580        )
 8581        .with_override_query("(string_literal) @string")
 8582        .unwrap(),
 8583    );
 8584
 8585    cx.language_registry().add(rust_language.clone());
 8586    cx.update_buffer(|buffer, cx| {
 8587        buffer.set_language(Some(rust_language), cx);
 8588    });
 8589
 8590    cx.set_state(
 8591        &r#"
 8592            let x = ˇ
 8593        "#
 8594        .unindent(),
 8595    );
 8596
 8597    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8598    cx.update_editor(|editor, window, cx| {
 8599        editor.handle_input("\"", window, cx);
 8600    });
 8601    cx.assert_editor_state(
 8602        &r#"
 8603            let x = "ˇ"
 8604        "#
 8605        .unindent(),
 8606    );
 8607
 8608    // Inserting another quotation mark. The cursor moves across the existing
 8609    // automatically-inserted quotation mark.
 8610    cx.update_editor(|editor, window, cx| {
 8611        editor.handle_input("\"", window, cx);
 8612    });
 8613    cx.assert_editor_state(
 8614        &r#"
 8615            let x = ""ˇ
 8616        "#
 8617        .unindent(),
 8618    );
 8619
 8620    // Reset
 8621    cx.set_state(
 8622        &r#"
 8623            let x = ˇ
 8624        "#
 8625        .unindent(),
 8626    );
 8627
 8628    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8629    cx.update_editor(|editor, window, cx| {
 8630        editor.handle_input("\"", window, cx);
 8631        editor.handle_input(" ", window, cx);
 8632        editor.move_left(&Default::default(), window, cx);
 8633        editor.handle_input("\\", window, cx);
 8634        editor.handle_input("\"", window, cx);
 8635    });
 8636    cx.assert_editor_state(
 8637        &r#"
 8638            let x = "\"ˇ "
 8639        "#
 8640        .unindent(),
 8641    );
 8642
 8643    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8644    // mark. Nothing is inserted.
 8645    cx.update_editor(|editor, window, cx| {
 8646        editor.move_right(&Default::default(), window, cx);
 8647        editor.handle_input("\"", window, cx);
 8648    });
 8649    cx.assert_editor_state(
 8650        &r#"
 8651            let x = "\" "ˇ
 8652        "#
 8653        .unindent(),
 8654    );
 8655}
 8656
 8657#[gpui::test]
 8658async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8659    init_test(cx, |_| {});
 8660
 8661    let language = Arc::new(Language::new(
 8662        LanguageConfig {
 8663            brackets: BracketPairConfig {
 8664                pairs: vec![
 8665                    BracketPair {
 8666                        start: "{".to_string(),
 8667                        end: "}".to_string(),
 8668                        close: true,
 8669                        surround: true,
 8670                        newline: true,
 8671                    },
 8672                    BracketPair {
 8673                        start: "/* ".to_string(),
 8674                        end: "*/".to_string(),
 8675                        close: true,
 8676                        surround: true,
 8677                        ..Default::default()
 8678                    },
 8679                ],
 8680                ..Default::default()
 8681            },
 8682            ..Default::default()
 8683        },
 8684        Some(tree_sitter_rust::LANGUAGE.into()),
 8685    ));
 8686
 8687    let text = r#"
 8688        a
 8689        b
 8690        c
 8691    "#
 8692    .unindent();
 8693
 8694    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8695    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8697    editor
 8698        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8699        .await;
 8700
 8701    editor.update_in(cx, |editor, window, cx| {
 8702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8703            s.select_display_ranges([
 8704                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8706                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8707            ])
 8708        });
 8709
 8710        editor.handle_input("{", window, cx);
 8711        editor.handle_input("{", window, cx);
 8712        editor.handle_input("{", window, cx);
 8713        assert_eq!(
 8714            editor.text(cx),
 8715            "
 8716                {{{a}}}
 8717                {{{b}}}
 8718                {{{c}}}
 8719            "
 8720            .unindent()
 8721        );
 8722        assert_eq!(
 8723            editor.selections.display_ranges(cx),
 8724            [
 8725                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8726                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8727                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8728            ]
 8729        );
 8730
 8731        editor.undo(&Undo, window, cx);
 8732        editor.undo(&Undo, window, cx);
 8733        editor.undo(&Undo, window, cx);
 8734        assert_eq!(
 8735            editor.text(cx),
 8736            "
 8737                a
 8738                b
 8739                c
 8740            "
 8741            .unindent()
 8742        );
 8743        assert_eq!(
 8744            editor.selections.display_ranges(cx),
 8745            [
 8746                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8747                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8748                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8749            ]
 8750        );
 8751
 8752        // Ensure inserting the first character of a multi-byte bracket pair
 8753        // doesn't surround the selections with the bracket.
 8754        editor.handle_input("/", window, cx);
 8755        assert_eq!(
 8756            editor.text(cx),
 8757            "
 8758                /
 8759                /
 8760                /
 8761            "
 8762            .unindent()
 8763        );
 8764        assert_eq!(
 8765            editor.selections.display_ranges(cx),
 8766            [
 8767                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8768                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8769                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8770            ]
 8771        );
 8772
 8773        editor.undo(&Undo, window, cx);
 8774        assert_eq!(
 8775            editor.text(cx),
 8776            "
 8777                a
 8778                b
 8779                c
 8780            "
 8781            .unindent()
 8782        );
 8783        assert_eq!(
 8784            editor.selections.display_ranges(cx),
 8785            [
 8786                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8787                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8788                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8789            ]
 8790        );
 8791
 8792        // Ensure inserting the last character of a multi-byte bracket pair
 8793        // doesn't surround the selections with the bracket.
 8794        editor.handle_input("*", window, cx);
 8795        assert_eq!(
 8796            editor.text(cx),
 8797            "
 8798                *
 8799                *
 8800                *
 8801            "
 8802            .unindent()
 8803        );
 8804        assert_eq!(
 8805            editor.selections.display_ranges(cx),
 8806            [
 8807                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8808                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8809                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8810            ]
 8811        );
 8812    });
 8813}
 8814
 8815#[gpui::test]
 8816async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8817    init_test(cx, |_| {});
 8818
 8819    let language = Arc::new(Language::new(
 8820        LanguageConfig {
 8821            brackets: BracketPairConfig {
 8822                pairs: vec![BracketPair {
 8823                    start: "{".to_string(),
 8824                    end: "}".to_string(),
 8825                    close: true,
 8826                    surround: true,
 8827                    newline: true,
 8828                }],
 8829                ..Default::default()
 8830            },
 8831            autoclose_before: "}".to_string(),
 8832            ..Default::default()
 8833        },
 8834        Some(tree_sitter_rust::LANGUAGE.into()),
 8835    ));
 8836
 8837    let text = r#"
 8838        a
 8839        b
 8840        c
 8841    "#
 8842    .unindent();
 8843
 8844    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8845    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8846    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8847    editor
 8848        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8849        .await;
 8850
 8851    editor.update_in(cx, |editor, window, cx| {
 8852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8853            s.select_ranges([
 8854                Point::new(0, 1)..Point::new(0, 1),
 8855                Point::new(1, 1)..Point::new(1, 1),
 8856                Point::new(2, 1)..Point::new(2, 1),
 8857            ])
 8858        });
 8859
 8860        editor.handle_input("{", window, cx);
 8861        editor.handle_input("{", window, cx);
 8862        editor.handle_input("_", window, cx);
 8863        assert_eq!(
 8864            editor.text(cx),
 8865            "
 8866                a{{_}}
 8867                b{{_}}
 8868                c{{_}}
 8869            "
 8870            .unindent()
 8871        );
 8872        assert_eq!(
 8873            editor.selections.ranges::<Point>(cx),
 8874            [
 8875                Point::new(0, 4)..Point::new(0, 4),
 8876                Point::new(1, 4)..Point::new(1, 4),
 8877                Point::new(2, 4)..Point::new(2, 4)
 8878            ]
 8879        );
 8880
 8881        editor.backspace(&Default::default(), window, cx);
 8882        editor.backspace(&Default::default(), window, cx);
 8883        assert_eq!(
 8884            editor.text(cx),
 8885            "
 8886                a{}
 8887                b{}
 8888                c{}
 8889            "
 8890            .unindent()
 8891        );
 8892        assert_eq!(
 8893            editor.selections.ranges::<Point>(cx),
 8894            [
 8895                Point::new(0, 2)..Point::new(0, 2),
 8896                Point::new(1, 2)..Point::new(1, 2),
 8897                Point::new(2, 2)..Point::new(2, 2)
 8898            ]
 8899        );
 8900
 8901        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8902        assert_eq!(
 8903            editor.text(cx),
 8904            "
 8905                a
 8906                b
 8907                c
 8908            "
 8909            .unindent()
 8910        );
 8911        assert_eq!(
 8912            editor.selections.ranges::<Point>(cx),
 8913            [
 8914                Point::new(0, 1)..Point::new(0, 1),
 8915                Point::new(1, 1)..Point::new(1, 1),
 8916                Point::new(2, 1)..Point::new(2, 1)
 8917            ]
 8918        );
 8919    });
 8920}
 8921
 8922#[gpui::test]
 8923async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8924    init_test(cx, |settings| {
 8925        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8926    });
 8927
 8928    let mut cx = EditorTestContext::new(cx).await;
 8929
 8930    let language = Arc::new(Language::new(
 8931        LanguageConfig {
 8932            brackets: BracketPairConfig {
 8933                pairs: vec![
 8934                    BracketPair {
 8935                        start: "{".to_string(),
 8936                        end: "}".to_string(),
 8937                        close: true,
 8938                        surround: true,
 8939                        newline: true,
 8940                    },
 8941                    BracketPair {
 8942                        start: "(".to_string(),
 8943                        end: ")".to_string(),
 8944                        close: true,
 8945                        surround: true,
 8946                        newline: true,
 8947                    },
 8948                    BracketPair {
 8949                        start: "[".to_string(),
 8950                        end: "]".to_string(),
 8951                        close: false,
 8952                        surround: true,
 8953                        newline: true,
 8954                    },
 8955                ],
 8956                ..Default::default()
 8957            },
 8958            autoclose_before: "})]".to_string(),
 8959            ..Default::default()
 8960        },
 8961        Some(tree_sitter_rust::LANGUAGE.into()),
 8962    ));
 8963
 8964    cx.language_registry().add(language.clone());
 8965    cx.update_buffer(|buffer, cx| {
 8966        buffer.set_language(Some(language), cx);
 8967    });
 8968
 8969    cx.set_state(
 8970        &"
 8971            {(ˇ)}
 8972            [[ˇ]]
 8973            {(ˇ)}
 8974        "
 8975        .unindent(),
 8976    );
 8977
 8978    cx.update_editor(|editor, window, cx| {
 8979        editor.backspace(&Default::default(), window, cx);
 8980        editor.backspace(&Default::default(), window, cx);
 8981    });
 8982
 8983    cx.assert_editor_state(
 8984        &"
 8985            ˇ
 8986            ˇ]]
 8987            ˇ
 8988        "
 8989        .unindent(),
 8990    );
 8991
 8992    cx.update_editor(|editor, window, cx| {
 8993        editor.handle_input("{", window, cx);
 8994        editor.handle_input("{", window, cx);
 8995        editor.move_right(&MoveRight, window, cx);
 8996        editor.move_right(&MoveRight, window, cx);
 8997        editor.move_left(&MoveLeft, window, cx);
 8998        editor.move_left(&MoveLeft, window, cx);
 8999        editor.backspace(&Default::default(), window, cx);
 9000    });
 9001
 9002    cx.assert_editor_state(
 9003        &"
 9004            {ˇ}
 9005            {ˇ}]]
 9006            {ˇ}
 9007        "
 9008        .unindent(),
 9009    );
 9010
 9011    cx.update_editor(|editor, window, cx| {
 9012        editor.backspace(&Default::default(), window, cx);
 9013    });
 9014
 9015    cx.assert_editor_state(
 9016        &"
 9017            ˇ
 9018            ˇ]]
 9019            ˇ
 9020        "
 9021        .unindent(),
 9022    );
 9023}
 9024
 9025#[gpui::test]
 9026async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9027    init_test(cx, |_| {});
 9028
 9029    let language = Arc::new(Language::new(
 9030        LanguageConfig::default(),
 9031        Some(tree_sitter_rust::LANGUAGE.into()),
 9032    ));
 9033
 9034    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9035    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9036    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9037    editor
 9038        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9039        .await;
 9040
 9041    editor.update_in(cx, |editor, window, cx| {
 9042        editor.set_auto_replace_emoji_shortcode(true);
 9043
 9044        editor.handle_input("Hello ", window, cx);
 9045        editor.handle_input(":wave", window, cx);
 9046        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9047
 9048        editor.handle_input(":", window, cx);
 9049        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9050
 9051        editor.handle_input(" :smile", window, cx);
 9052        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9053
 9054        editor.handle_input(":", window, cx);
 9055        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9056
 9057        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9058        editor.handle_input(":wave", window, cx);
 9059        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9060
 9061        editor.handle_input(":", window, cx);
 9062        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9063
 9064        editor.handle_input(":1", window, cx);
 9065        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9066
 9067        editor.handle_input(":", window, cx);
 9068        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9069
 9070        // Ensure shortcode does not get replaced when it is part of a word
 9071        editor.handle_input(" Test:wave", window, cx);
 9072        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9073
 9074        editor.handle_input(":", window, cx);
 9075        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9076
 9077        editor.set_auto_replace_emoji_shortcode(false);
 9078
 9079        // Ensure shortcode does not get replaced when auto replace is off
 9080        editor.handle_input(" :wave", window, cx);
 9081        assert_eq!(
 9082            editor.text(cx),
 9083            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9084        );
 9085
 9086        editor.handle_input(":", window, cx);
 9087        assert_eq!(
 9088            editor.text(cx),
 9089            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9090        );
 9091    });
 9092}
 9093
 9094#[gpui::test]
 9095async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9096    init_test(cx, |_| {});
 9097
 9098    let (text, insertion_ranges) = marked_text_ranges(
 9099        indoc! {"
 9100            ˇ
 9101        "},
 9102        false,
 9103    );
 9104
 9105    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9106    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9107
 9108    _ = editor.update_in(cx, |editor, window, cx| {
 9109        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9110
 9111        editor
 9112            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9113            .unwrap();
 9114
 9115        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9116            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9117            assert_eq!(editor.text(cx), expected_text);
 9118            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9119        }
 9120
 9121        assert(
 9122            editor,
 9123            cx,
 9124            indoc! {"
 9125            type «» =•
 9126            "},
 9127        );
 9128
 9129        assert!(editor.context_menu_visible(), "There should be a matches");
 9130    });
 9131}
 9132
 9133#[gpui::test]
 9134async fn test_snippets(cx: &mut TestAppContext) {
 9135    init_test(cx, |_| {});
 9136
 9137    let mut cx = EditorTestContext::new(cx).await;
 9138
 9139    cx.set_state(indoc! {"
 9140        a.ˇ b
 9141        a.ˇ b
 9142        a.ˇ b
 9143    "});
 9144
 9145    cx.update_editor(|editor, window, cx| {
 9146        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9147        let insertion_ranges = editor
 9148            .selections
 9149            .all(cx)
 9150            .iter()
 9151            .map(|s| s.range().clone())
 9152            .collect::<Vec<_>>();
 9153        editor
 9154            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9155            .unwrap();
 9156    });
 9157
 9158    cx.assert_editor_state(indoc! {"
 9159        a.f(«oneˇ», two, «threeˇ») b
 9160        a.f(«oneˇ», two, «threeˇ») b
 9161        a.f(«oneˇ», two, «threeˇ») b
 9162    "});
 9163
 9164    // Can't move earlier than the first tab stop
 9165    cx.update_editor(|editor, window, cx| {
 9166        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9167    });
 9168    cx.assert_editor_state(indoc! {"
 9169        a.f(«oneˇ», two, «threeˇ») b
 9170        a.f(«oneˇ», two, «threeˇ») b
 9171        a.f(«oneˇ», two, «threeˇ») b
 9172    "});
 9173
 9174    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9175    cx.assert_editor_state(indoc! {"
 9176        a.f(one, «twoˇ», three) b
 9177        a.f(one, «twoˇ», three) b
 9178        a.f(one, «twoˇ», three) b
 9179    "});
 9180
 9181    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9182    cx.assert_editor_state(indoc! {"
 9183        a.f(«oneˇ», two, «threeˇ») b
 9184        a.f(«oneˇ», two, «threeˇ») b
 9185        a.f(«oneˇ», two, «threeˇ») b
 9186    "});
 9187
 9188    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9189    cx.assert_editor_state(indoc! {"
 9190        a.f(one, «twoˇ», three) b
 9191        a.f(one, «twoˇ», three) b
 9192        a.f(one, «twoˇ», three) b
 9193    "});
 9194    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9195    cx.assert_editor_state(indoc! {"
 9196        a.f(one, two, three)ˇ b
 9197        a.f(one, two, three)ˇ b
 9198        a.f(one, two, three)ˇ b
 9199    "});
 9200
 9201    // As soon as the last tab stop is reached, snippet state is gone
 9202    cx.update_editor(|editor, window, cx| {
 9203        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9204    });
 9205    cx.assert_editor_state(indoc! {"
 9206        a.f(one, two, three)ˇ b
 9207        a.f(one, two, three)ˇ b
 9208        a.f(one, two, three)ˇ b
 9209    "});
 9210}
 9211
 9212#[gpui::test]
 9213async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9214    init_test(cx, |_| {});
 9215
 9216    let mut cx = EditorTestContext::new(cx).await;
 9217
 9218    cx.update_editor(|editor, window, cx| {
 9219        let snippet = Snippet::parse(indoc! {"
 9220            /*
 9221             * Multiline comment with leading indentation
 9222             *
 9223             * $1
 9224             */
 9225            $0"})
 9226        .unwrap();
 9227        let insertion_ranges = editor
 9228            .selections
 9229            .all(cx)
 9230            .iter()
 9231            .map(|s| s.range().clone())
 9232            .collect::<Vec<_>>();
 9233        editor
 9234            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9235            .unwrap();
 9236    });
 9237
 9238    cx.assert_editor_state(indoc! {"
 9239        /*
 9240         * Multiline comment with leading indentation
 9241         *
 9242         * ˇ
 9243         */
 9244    "});
 9245
 9246    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9247    cx.assert_editor_state(indoc! {"
 9248        /*
 9249         * Multiline comment with leading indentation
 9250         *
 9251         *•
 9252         */
 9253        ˇ"});
 9254}
 9255
 9256#[gpui::test]
 9257async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9258    init_test(cx, |_| {});
 9259
 9260    let fs = FakeFs::new(cx.executor());
 9261    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9262
 9263    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9264
 9265    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9266    language_registry.add(rust_lang());
 9267    let mut fake_servers = language_registry.register_fake_lsp(
 9268        "Rust",
 9269        FakeLspAdapter {
 9270            capabilities: lsp::ServerCapabilities {
 9271                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9272                ..Default::default()
 9273            },
 9274            ..Default::default()
 9275        },
 9276    );
 9277
 9278    let buffer = project
 9279        .update(cx, |project, cx| {
 9280            project.open_local_buffer(path!("/file.rs"), cx)
 9281        })
 9282        .await
 9283        .unwrap();
 9284
 9285    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9286    let (editor, cx) = cx.add_window_view(|window, cx| {
 9287        build_editor_with_project(project.clone(), buffer, window, cx)
 9288    });
 9289    editor.update_in(cx, |editor, window, cx| {
 9290        editor.set_text("one\ntwo\nthree\n", window, cx)
 9291    });
 9292    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9293
 9294    cx.executor().start_waiting();
 9295    let fake_server = fake_servers.next().await.unwrap();
 9296
 9297    {
 9298        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9299            move |params, _| async move {
 9300                assert_eq!(
 9301                    params.text_document.uri,
 9302                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9303                );
 9304                assert_eq!(params.options.tab_size, 4);
 9305                Ok(Some(vec![lsp::TextEdit::new(
 9306                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9307                    ", ".to_string(),
 9308                )]))
 9309            },
 9310        );
 9311        let save = editor
 9312            .update_in(cx, |editor, window, cx| {
 9313                editor.save(
 9314                    SaveOptions {
 9315                        format: true,
 9316                        autosave: false,
 9317                    },
 9318                    project.clone(),
 9319                    window,
 9320                    cx,
 9321                )
 9322            })
 9323            .unwrap();
 9324        cx.executor().start_waiting();
 9325        save.await;
 9326
 9327        assert_eq!(
 9328            editor.update(cx, |editor, cx| editor.text(cx)),
 9329            "one, two\nthree\n"
 9330        );
 9331        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9332    }
 9333
 9334    {
 9335        editor.update_in(cx, |editor, window, cx| {
 9336            editor.set_text("one\ntwo\nthree\n", window, cx)
 9337        });
 9338        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9339
 9340        // Ensure we can still save even if formatting hangs.
 9341        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9342            move |params, _| async move {
 9343                assert_eq!(
 9344                    params.text_document.uri,
 9345                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9346                );
 9347                futures::future::pending::<()>().await;
 9348                unreachable!()
 9349            },
 9350        );
 9351        let save = editor
 9352            .update_in(cx, |editor, window, cx| {
 9353                editor.save(
 9354                    SaveOptions {
 9355                        format: true,
 9356                        autosave: false,
 9357                    },
 9358                    project.clone(),
 9359                    window,
 9360                    cx,
 9361                )
 9362            })
 9363            .unwrap();
 9364        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9365        cx.executor().start_waiting();
 9366        save.await;
 9367        assert_eq!(
 9368            editor.update(cx, |editor, cx| editor.text(cx)),
 9369            "one\ntwo\nthree\n"
 9370        );
 9371    }
 9372
 9373    // Set rust language override and assert overridden tabsize is sent to language server
 9374    update_test_language_settings(cx, |settings| {
 9375        settings.languages.insert(
 9376            "Rust".into(),
 9377            LanguageSettingsContent {
 9378                tab_size: NonZeroU32::new(8),
 9379                ..Default::default()
 9380            },
 9381        );
 9382    });
 9383
 9384    {
 9385        editor.update_in(cx, |editor, window, cx| {
 9386            editor.set_text("somehting_new\n", window, cx)
 9387        });
 9388        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9389        let _formatting_request_signal = fake_server
 9390            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9391                assert_eq!(
 9392                    params.text_document.uri,
 9393                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9394                );
 9395                assert_eq!(params.options.tab_size, 8);
 9396                Ok(Some(vec![]))
 9397            });
 9398        let save = editor
 9399            .update_in(cx, |editor, window, cx| {
 9400                editor.save(
 9401                    SaveOptions {
 9402                        format: true,
 9403                        autosave: false,
 9404                    },
 9405                    project.clone(),
 9406                    window,
 9407                    cx,
 9408                )
 9409            })
 9410            .unwrap();
 9411        cx.executor().start_waiting();
 9412        save.await;
 9413    }
 9414}
 9415
 9416#[gpui::test]
 9417async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9418    init_test(cx, |_| {});
 9419
 9420    let cols = 4;
 9421    let rows = 10;
 9422    let sample_text_1 = sample_text(rows, cols, 'a');
 9423    assert_eq!(
 9424        sample_text_1,
 9425        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9426    );
 9427    let sample_text_2 = sample_text(rows, cols, 'l');
 9428    assert_eq!(
 9429        sample_text_2,
 9430        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9431    );
 9432    let sample_text_3 = sample_text(rows, cols, 'v');
 9433    assert_eq!(
 9434        sample_text_3,
 9435        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9436    );
 9437
 9438    let fs = FakeFs::new(cx.executor());
 9439    fs.insert_tree(
 9440        path!("/a"),
 9441        json!({
 9442            "main.rs": sample_text_1,
 9443            "other.rs": sample_text_2,
 9444            "lib.rs": sample_text_3,
 9445        }),
 9446    )
 9447    .await;
 9448
 9449    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9450    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9451    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9452
 9453    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9454    language_registry.add(rust_lang());
 9455    let mut fake_servers = language_registry.register_fake_lsp(
 9456        "Rust",
 9457        FakeLspAdapter {
 9458            capabilities: lsp::ServerCapabilities {
 9459                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9460                ..Default::default()
 9461            },
 9462            ..Default::default()
 9463        },
 9464    );
 9465
 9466    let worktree = project.update(cx, |project, cx| {
 9467        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9468        assert_eq!(worktrees.len(), 1);
 9469        worktrees.pop().unwrap()
 9470    });
 9471    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9472
 9473    let buffer_1 = project
 9474        .update(cx, |project, cx| {
 9475            project.open_buffer((worktree_id, "main.rs"), cx)
 9476        })
 9477        .await
 9478        .unwrap();
 9479    let buffer_2 = project
 9480        .update(cx, |project, cx| {
 9481            project.open_buffer((worktree_id, "other.rs"), cx)
 9482        })
 9483        .await
 9484        .unwrap();
 9485    let buffer_3 = project
 9486        .update(cx, |project, cx| {
 9487            project.open_buffer((worktree_id, "lib.rs"), cx)
 9488        })
 9489        .await
 9490        .unwrap();
 9491
 9492    let multi_buffer = cx.new(|cx| {
 9493        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9494        multi_buffer.push_excerpts(
 9495            buffer_1.clone(),
 9496            [
 9497                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9498                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9499                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9500            ],
 9501            cx,
 9502        );
 9503        multi_buffer.push_excerpts(
 9504            buffer_2.clone(),
 9505            [
 9506                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9507                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9508                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9509            ],
 9510            cx,
 9511        );
 9512        multi_buffer.push_excerpts(
 9513            buffer_3.clone(),
 9514            [
 9515                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9516                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9517                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9518            ],
 9519            cx,
 9520        );
 9521        multi_buffer
 9522    });
 9523    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9524        Editor::new(
 9525            EditorMode::full(),
 9526            multi_buffer,
 9527            Some(project.clone()),
 9528            window,
 9529            cx,
 9530        )
 9531    });
 9532
 9533    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9534        editor.change_selections(
 9535            SelectionEffects::scroll(Autoscroll::Next),
 9536            window,
 9537            cx,
 9538            |s| s.select_ranges(Some(1..2)),
 9539        );
 9540        editor.insert("|one|two|three|", window, cx);
 9541    });
 9542    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9543    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9544        editor.change_selections(
 9545            SelectionEffects::scroll(Autoscroll::Next),
 9546            window,
 9547            cx,
 9548            |s| s.select_ranges(Some(60..70)),
 9549        );
 9550        editor.insert("|four|five|six|", window, cx);
 9551    });
 9552    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9553
 9554    // First two buffers should be edited, but not the third one.
 9555    assert_eq!(
 9556        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9557        "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}",
 9558    );
 9559    buffer_1.update(cx, |buffer, _| {
 9560        assert!(buffer.is_dirty());
 9561        assert_eq!(
 9562            buffer.text(),
 9563            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9564        )
 9565    });
 9566    buffer_2.update(cx, |buffer, _| {
 9567        assert!(buffer.is_dirty());
 9568        assert_eq!(
 9569            buffer.text(),
 9570            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9571        )
 9572    });
 9573    buffer_3.update(cx, |buffer, _| {
 9574        assert!(!buffer.is_dirty());
 9575        assert_eq!(buffer.text(), sample_text_3,)
 9576    });
 9577    cx.executor().run_until_parked();
 9578
 9579    cx.executor().start_waiting();
 9580    let save = multi_buffer_editor
 9581        .update_in(cx, |editor, window, cx| {
 9582            editor.save(
 9583                SaveOptions {
 9584                    format: true,
 9585                    autosave: false,
 9586                },
 9587                project.clone(),
 9588                window,
 9589                cx,
 9590            )
 9591        })
 9592        .unwrap();
 9593
 9594    let fake_server = fake_servers.next().await.unwrap();
 9595    fake_server
 9596        .server
 9597        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9598            Ok(Some(vec![lsp::TextEdit::new(
 9599                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9600                format!("[{} formatted]", params.text_document.uri),
 9601            )]))
 9602        })
 9603        .detach();
 9604    save.await;
 9605
 9606    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9607    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9608    assert_eq!(
 9609        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9610        uri!(
 9611            "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}"
 9612        ),
 9613    );
 9614    buffer_1.update(cx, |buffer, _| {
 9615        assert!(!buffer.is_dirty());
 9616        assert_eq!(
 9617            buffer.text(),
 9618            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9619        )
 9620    });
 9621    buffer_2.update(cx, |buffer, _| {
 9622        assert!(!buffer.is_dirty());
 9623        assert_eq!(
 9624            buffer.text(),
 9625            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9626        )
 9627    });
 9628    buffer_3.update(cx, |buffer, _| {
 9629        assert!(!buffer.is_dirty());
 9630        assert_eq!(buffer.text(), sample_text_3,)
 9631    });
 9632}
 9633
 9634#[gpui::test]
 9635async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9636    init_test(cx, |_| {});
 9637
 9638    let fs = FakeFs::new(cx.executor());
 9639    fs.insert_tree(
 9640        path!("/dir"),
 9641        json!({
 9642            "file1.rs": "fn main() { println!(\"hello\"); }",
 9643            "file2.rs": "fn test() { println!(\"test\"); }",
 9644            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9645        }),
 9646    )
 9647    .await;
 9648
 9649    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9650    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9651    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9652
 9653    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9654    language_registry.add(rust_lang());
 9655
 9656    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9657    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9658
 9659    // Open three buffers
 9660    let buffer_1 = project
 9661        .update(cx, |project, cx| {
 9662            project.open_buffer((worktree_id, "file1.rs"), cx)
 9663        })
 9664        .await
 9665        .unwrap();
 9666    let buffer_2 = project
 9667        .update(cx, |project, cx| {
 9668            project.open_buffer((worktree_id, "file2.rs"), cx)
 9669        })
 9670        .await
 9671        .unwrap();
 9672    let buffer_3 = project
 9673        .update(cx, |project, cx| {
 9674            project.open_buffer((worktree_id, "file3.rs"), cx)
 9675        })
 9676        .await
 9677        .unwrap();
 9678
 9679    // Create a multi-buffer with all three buffers
 9680    let multi_buffer = cx.new(|cx| {
 9681        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9682        multi_buffer.push_excerpts(
 9683            buffer_1.clone(),
 9684            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9685            cx,
 9686        );
 9687        multi_buffer.push_excerpts(
 9688            buffer_2.clone(),
 9689            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9690            cx,
 9691        );
 9692        multi_buffer.push_excerpts(
 9693            buffer_3.clone(),
 9694            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9695            cx,
 9696        );
 9697        multi_buffer
 9698    });
 9699
 9700    let editor = cx.new_window_entity(|window, cx| {
 9701        Editor::new(
 9702            EditorMode::full(),
 9703            multi_buffer,
 9704            Some(project.clone()),
 9705            window,
 9706            cx,
 9707        )
 9708    });
 9709
 9710    // Edit only the first buffer
 9711    editor.update_in(cx, |editor, window, cx| {
 9712        editor.change_selections(
 9713            SelectionEffects::scroll(Autoscroll::Next),
 9714            window,
 9715            cx,
 9716            |s| s.select_ranges(Some(10..10)),
 9717        );
 9718        editor.insert("// edited", window, cx);
 9719    });
 9720
 9721    // Verify that only buffer 1 is dirty
 9722    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9723    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9724    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9725
 9726    // Get write counts after file creation (files were created with initial content)
 9727    // We expect each file to have been written once during creation
 9728    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9729    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9730    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9731
 9732    // Perform autosave
 9733    let save_task = editor.update_in(cx, |editor, window, cx| {
 9734        editor.save(
 9735            SaveOptions {
 9736                format: true,
 9737                autosave: true,
 9738            },
 9739            project.clone(),
 9740            window,
 9741            cx,
 9742        )
 9743    });
 9744    save_task.await.unwrap();
 9745
 9746    // Only the dirty buffer should have been saved
 9747    assert_eq!(
 9748        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9749        1,
 9750        "Buffer 1 was dirty, so it should have been written once during autosave"
 9751    );
 9752    assert_eq!(
 9753        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9754        0,
 9755        "Buffer 2 was clean, so it should not have been written during autosave"
 9756    );
 9757    assert_eq!(
 9758        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9759        0,
 9760        "Buffer 3 was clean, so it should not have been written during autosave"
 9761    );
 9762
 9763    // Verify buffer states after autosave
 9764    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9765    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9766    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9767
 9768    // Now perform a manual save (format = true)
 9769    let save_task = editor.update_in(cx, |editor, window, cx| {
 9770        editor.save(
 9771            SaveOptions {
 9772                format: true,
 9773                autosave: false,
 9774            },
 9775            project.clone(),
 9776            window,
 9777            cx,
 9778        )
 9779    });
 9780    save_task.await.unwrap();
 9781
 9782    // During manual save, clean buffers don't get written to disk
 9783    // They just get did_save called for language server notifications
 9784    assert_eq!(
 9785        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9786        1,
 9787        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9788    );
 9789    assert_eq!(
 9790        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9791        0,
 9792        "Buffer 2 should not have been written at all"
 9793    );
 9794    assert_eq!(
 9795        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9796        0,
 9797        "Buffer 3 should not have been written at all"
 9798    );
 9799}
 9800
 9801#[gpui::test]
 9802async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9803    init_test(cx, |_| {});
 9804
 9805    let fs = FakeFs::new(cx.executor());
 9806    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9807
 9808    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9809
 9810    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9811    language_registry.add(rust_lang());
 9812    let mut fake_servers = language_registry.register_fake_lsp(
 9813        "Rust",
 9814        FakeLspAdapter {
 9815            capabilities: lsp::ServerCapabilities {
 9816                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9817                ..Default::default()
 9818            },
 9819            ..Default::default()
 9820        },
 9821    );
 9822
 9823    let buffer = project
 9824        .update(cx, |project, cx| {
 9825            project.open_local_buffer(path!("/file.rs"), cx)
 9826        })
 9827        .await
 9828        .unwrap();
 9829
 9830    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9831    let (editor, cx) = cx.add_window_view(|window, cx| {
 9832        build_editor_with_project(project.clone(), buffer, window, cx)
 9833    });
 9834    editor.update_in(cx, |editor, window, cx| {
 9835        editor.set_text("one\ntwo\nthree\n", window, cx)
 9836    });
 9837    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9838
 9839    cx.executor().start_waiting();
 9840    let fake_server = fake_servers.next().await.unwrap();
 9841
 9842    let save = editor
 9843        .update_in(cx, |editor, window, cx| {
 9844            editor.save(
 9845                SaveOptions {
 9846                    format: true,
 9847                    autosave: false,
 9848                },
 9849                project.clone(),
 9850                window,
 9851                cx,
 9852            )
 9853        })
 9854        .unwrap();
 9855    fake_server
 9856        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9857            assert_eq!(
 9858                params.text_document.uri,
 9859                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9860            );
 9861            assert_eq!(params.options.tab_size, 4);
 9862            Ok(Some(vec![lsp::TextEdit::new(
 9863                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9864                ", ".to_string(),
 9865            )]))
 9866        })
 9867        .next()
 9868        .await;
 9869    cx.executor().start_waiting();
 9870    save.await;
 9871    assert_eq!(
 9872        editor.update(cx, |editor, cx| editor.text(cx)),
 9873        "one, two\nthree\n"
 9874    );
 9875    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9876
 9877    editor.update_in(cx, |editor, window, cx| {
 9878        editor.set_text("one\ntwo\nthree\n", window, cx)
 9879    });
 9880    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9881
 9882    // Ensure we can still save even if formatting hangs.
 9883    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9884        move |params, _| async move {
 9885            assert_eq!(
 9886                params.text_document.uri,
 9887                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9888            );
 9889            futures::future::pending::<()>().await;
 9890            unreachable!()
 9891        },
 9892    );
 9893    let save = editor
 9894        .update_in(cx, |editor, window, cx| {
 9895            editor.save(
 9896                SaveOptions {
 9897                    format: true,
 9898                    autosave: false,
 9899                },
 9900                project.clone(),
 9901                window,
 9902                cx,
 9903            )
 9904        })
 9905        .unwrap();
 9906    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9907    cx.executor().start_waiting();
 9908    save.await;
 9909    assert_eq!(
 9910        editor.update(cx, |editor, cx| editor.text(cx)),
 9911        "one\ntwo\nthree\n"
 9912    );
 9913    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9914
 9915    // For non-dirty buffer, no formatting request should be sent
 9916    let save = editor
 9917        .update_in(cx, |editor, window, cx| {
 9918            editor.save(
 9919                SaveOptions {
 9920                    format: false,
 9921                    autosave: false,
 9922                },
 9923                project.clone(),
 9924                window,
 9925                cx,
 9926            )
 9927        })
 9928        .unwrap();
 9929    let _pending_format_request = fake_server
 9930        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9931            panic!("Should not be invoked");
 9932        })
 9933        .next();
 9934    cx.executor().start_waiting();
 9935    save.await;
 9936
 9937    // Set Rust language override and assert overridden tabsize is sent to language server
 9938    update_test_language_settings(cx, |settings| {
 9939        settings.languages.insert(
 9940            "Rust".into(),
 9941            LanguageSettingsContent {
 9942                tab_size: NonZeroU32::new(8),
 9943                ..Default::default()
 9944            },
 9945        );
 9946    });
 9947
 9948    editor.update_in(cx, |editor, window, cx| {
 9949        editor.set_text("somehting_new\n", window, cx)
 9950    });
 9951    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9952    let save = editor
 9953        .update_in(cx, |editor, window, cx| {
 9954            editor.save(
 9955                SaveOptions {
 9956                    format: true,
 9957                    autosave: false,
 9958                },
 9959                project.clone(),
 9960                window,
 9961                cx,
 9962            )
 9963        })
 9964        .unwrap();
 9965    fake_server
 9966        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9967            assert_eq!(
 9968                params.text_document.uri,
 9969                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9970            );
 9971            assert_eq!(params.options.tab_size, 8);
 9972            Ok(Some(Vec::new()))
 9973        })
 9974        .next()
 9975        .await;
 9976    save.await;
 9977}
 9978
 9979#[gpui::test]
 9980async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9981    init_test(cx, |settings| {
 9982        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9983            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9984        ))
 9985    });
 9986
 9987    let fs = FakeFs::new(cx.executor());
 9988    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9989
 9990    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9991
 9992    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9993    language_registry.add(Arc::new(Language::new(
 9994        LanguageConfig {
 9995            name: "Rust".into(),
 9996            matcher: LanguageMatcher {
 9997                path_suffixes: vec!["rs".to_string()],
 9998                ..Default::default()
 9999            },
10000            ..LanguageConfig::default()
10001        },
10002        Some(tree_sitter_rust::LANGUAGE.into()),
10003    )));
10004    update_test_language_settings(cx, |settings| {
10005        // Enable Prettier formatting for the same buffer, and ensure
10006        // LSP is called instead of Prettier.
10007        settings.defaults.prettier = Some(PrettierSettings {
10008            allowed: true,
10009            ..PrettierSettings::default()
10010        });
10011    });
10012    let mut fake_servers = language_registry.register_fake_lsp(
10013        "Rust",
10014        FakeLspAdapter {
10015            capabilities: lsp::ServerCapabilities {
10016                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10017                ..Default::default()
10018            },
10019            ..Default::default()
10020        },
10021    );
10022
10023    let buffer = project
10024        .update(cx, |project, cx| {
10025            project.open_local_buffer(path!("/file.rs"), cx)
10026        })
10027        .await
10028        .unwrap();
10029
10030    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10031    let (editor, cx) = cx.add_window_view(|window, cx| {
10032        build_editor_with_project(project.clone(), buffer, window, cx)
10033    });
10034    editor.update_in(cx, |editor, window, cx| {
10035        editor.set_text("one\ntwo\nthree\n", window, cx)
10036    });
10037
10038    cx.executor().start_waiting();
10039    let fake_server = fake_servers.next().await.unwrap();
10040
10041    let format = editor
10042        .update_in(cx, |editor, window, cx| {
10043            editor.perform_format(
10044                project.clone(),
10045                FormatTrigger::Manual,
10046                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10047                window,
10048                cx,
10049            )
10050        })
10051        .unwrap();
10052    fake_server
10053        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10054            assert_eq!(
10055                params.text_document.uri,
10056                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10057            );
10058            assert_eq!(params.options.tab_size, 4);
10059            Ok(Some(vec![lsp::TextEdit::new(
10060                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10061                ", ".to_string(),
10062            )]))
10063        })
10064        .next()
10065        .await;
10066    cx.executor().start_waiting();
10067    format.await;
10068    assert_eq!(
10069        editor.update(cx, |editor, cx| editor.text(cx)),
10070        "one, two\nthree\n"
10071    );
10072
10073    editor.update_in(cx, |editor, window, cx| {
10074        editor.set_text("one\ntwo\nthree\n", window, cx)
10075    });
10076    // Ensure we don't lock if formatting hangs.
10077    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10078        move |params, _| async move {
10079            assert_eq!(
10080                params.text_document.uri,
10081                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10082            );
10083            futures::future::pending::<()>().await;
10084            unreachable!()
10085        },
10086    );
10087    let format = editor
10088        .update_in(cx, |editor, window, cx| {
10089            editor.perform_format(
10090                project,
10091                FormatTrigger::Manual,
10092                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10093                window,
10094                cx,
10095            )
10096        })
10097        .unwrap();
10098    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10099    cx.executor().start_waiting();
10100    format.await;
10101    assert_eq!(
10102        editor.update(cx, |editor, cx| editor.text(cx)),
10103        "one\ntwo\nthree\n"
10104    );
10105}
10106
10107#[gpui::test]
10108async fn test_multiple_formatters(cx: &mut TestAppContext) {
10109    init_test(cx, |settings| {
10110        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10111        settings.defaults.formatter =
10112            Some(language_settings::SelectedFormatter::List(FormatterList(
10113                vec![
10114                    Formatter::LanguageServer { name: None },
10115                    Formatter::CodeActions(
10116                        [
10117                            ("code-action-1".into(), true),
10118                            ("code-action-2".into(), true),
10119                        ]
10120                        .into_iter()
10121                        .collect(),
10122                    ),
10123                ]
10124                .into(),
10125            )))
10126    });
10127
10128    let fs = FakeFs::new(cx.executor());
10129    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10130        .await;
10131
10132    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10133    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10134    language_registry.add(rust_lang());
10135
10136    let mut fake_servers = language_registry.register_fake_lsp(
10137        "Rust",
10138        FakeLspAdapter {
10139            capabilities: lsp::ServerCapabilities {
10140                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10141                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10142                    commands: vec!["the-command-for-code-action-1".into()],
10143                    ..Default::default()
10144                }),
10145                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10146                ..Default::default()
10147            },
10148            ..Default::default()
10149        },
10150    );
10151
10152    let buffer = project
10153        .update(cx, |project, cx| {
10154            project.open_local_buffer(path!("/file.rs"), cx)
10155        })
10156        .await
10157        .unwrap();
10158
10159    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10160    let (editor, cx) = cx.add_window_view(|window, cx| {
10161        build_editor_with_project(project.clone(), buffer, window, cx)
10162    });
10163
10164    cx.executor().start_waiting();
10165
10166    let fake_server = fake_servers.next().await.unwrap();
10167    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10168        move |_params, _| async move {
10169            Ok(Some(vec![lsp::TextEdit::new(
10170                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10171                "applied-formatting\n".to_string(),
10172            )]))
10173        },
10174    );
10175    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10176        move |params, _| async move {
10177            assert_eq!(
10178                params.context.only,
10179                Some(vec!["code-action-1".into(), "code-action-2".into()])
10180            );
10181            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10182            Ok(Some(vec![
10183                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10184                    kind: Some("code-action-1".into()),
10185                    edit: Some(lsp::WorkspaceEdit::new(
10186                        [(
10187                            uri.clone(),
10188                            vec![lsp::TextEdit::new(
10189                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10190                                "applied-code-action-1-edit\n".to_string(),
10191                            )],
10192                        )]
10193                        .into_iter()
10194                        .collect(),
10195                    )),
10196                    command: Some(lsp::Command {
10197                        command: "the-command-for-code-action-1".into(),
10198                        ..Default::default()
10199                    }),
10200                    ..Default::default()
10201                }),
10202                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10203                    kind: Some("code-action-2".into()),
10204                    edit: Some(lsp::WorkspaceEdit::new(
10205                        [(
10206                            uri.clone(),
10207                            vec![lsp::TextEdit::new(
10208                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10209                                "applied-code-action-2-edit\n".to_string(),
10210                            )],
10211                        )]
10212                        .into_iter()
10213                        .collect(),
10214                    )),
10215                    ..Default::default()
10216                }),
10217            ]))
10218        },
10219    );
10220
10221    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10222        move |params, _| async move { Ok(params) }
10223    });
10224
10225    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10226    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10227        let fake = fake_server.clone();
10228        let lock = command_lock.clone();
10229        move |params, _| {
10230            assert_eq!(params.command, "the-command-for-code-action-1");
10231            let fake = fake.clone();
10232            let lock = lock.clone();
10233            async move {
10234                lock.lock().await;
10235                fake.server
10236                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10237                        label: None,
10238                        edit: lsp::WorkspaceEdit {
10239                            changes: Some(
10240                                [(
10241                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10242                                    vec![lsp::TextEdit {
10243                                        range: lsp::Range::new(
10244                                            lsp::Position::new(0, 0),
10245                                            lsp::Position::new(0, 0),
10246                                        ),
10247                                        new_text: "applied-code-action-1-command\n".into(),
10248                                    }],
10249                                )]
10250                                .into_iter()
10251                                .collect(),
10252                            ),
10253                            ..Default::default()
10254                        },
10255                    })
10256                    .await
10257                    .into_response()
10258                    .unwrap();
10259                Ok(Some(json!(null)))
10260            }
10261        }
10262    });
10263
10264    cx.executor().start_waiting();
10265    editor
10266        .update_in(cx, |editor, window, cx| {
10267            editor.perform_format(
10268                project.clone(),
10269                FormatTrigger::Manual,
10270                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10271                window,
10272                cx,
10273            )
10274        })
10275        .unwrap()
10276        .await;
10277    editor.update(cx, |editor, cx| {
10278        assert_eq!(
10279            editor.text(cx),
10280            r#"
10281                applied-code-action-2-edit
10282                applied-code-action-1-command
10283                applied-code-action-1-edit
10284                applied-formatting
10285                one
10286                two
10287                three
10288            "#
10289            .unindent()
10290        );
10291    });
10292
10293    editor.update_in(cx, |editor, window, cx| {
10294        editor.undo(&Default::default(), window, cx);
10295        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10296    });
10297
10298    // Perform a manual edit while waiting for an LSP command
10299    // that's being run as part of a formatting code action.
10300    let lock_guard = command_lock.lock().await;
10301    let format = editor
10302        .update_in(cx, |editor, window, cx| {
10303            editor.perform_format(
10304                project.clone(),
10305                FormatTrigger::Manual,
10306                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10307                window,
10308                cx,
10309            )
10310        })
10311        .unwrap();
10312    cx.run_until_parked();
10313    editor.update(cx, |editor, cx| {
10314        assert_eq!(
10315            editor.text(cx),
10316            r#"
10317                applied-code-action-1-edit
10318                applied-formatting
10319                one
10320                two
10321                three
10322            "#
10323            .unindent()
10324        );
10325
10326        editor.buffer.update(cx, |buffer, cx| {
10327            let ix = buffer.len(cx);
10328            buffer.edit([(ix..ix, "edited\n")], None, cx);
10329        });
10330    });
10331
10332    // Allow the LSP command to proceed. Because the buffer was edited,
10333    // the second code action will not be run.
10334    drop(lock_guard);
10335    format.await;
10336    editor.update_in(cx, |editor, window, cx| {
10337        assert_eq!(
10338            editor.text(cx),
10339            r#"
10340                applied-code-action-1-command
10341                applied-code-action-1-edit
10342                applied-formatting
10343                one
10344                two
10345                three
10346                edited
10347            "#
10348            .unindent()
10349        );
10350
10351        // The manual edit is undone first, because it is the last thing the user did
10352        // (even though the command completed afterwards).
10353        editor.undo(&Default::default(), window, cx);
10354        assert_eq!(
10355            editor.text(cx),
10356            r#"
10357                applied-code-action-1-command
10358                applied-code-action-1-edit
10359                applied-formatting
10360                one
10361                two
10362                three
10363            "#
10364            .unindent()
10365        );
10366
10367        // All the formatting (including the command, which completed after the manual edit)
10368        // is undone together.
10369        editor.undo(&Default::default(), window, cx);
10370        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10371    });
10372}
10373
10374#[gpui::test]
10375async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10376    init_test(cx, |settings| {
10377        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10378            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10379        ))
10380    });
10381
10382    let fs = FakeFs::new(cx.executor());
10383    fs.insert_file(path!("/file.ts"), Default::default()).await;
10384
10385    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10386
10387    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10388    language_registry.add(Arc::new(Language::new(
10389        LanguageConfig {
10390            name: "TypeScript".into(),
10391            matcher: LanguageMatcher {
10392                path_suffixes: vec!["ts".to_string()],
10393                ..Default::default()
10394            },
10395            ..LanguageConfig::default()
10396        },
10397        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10398    )));
10399    update_test_language_settings(cx, |settings| {
10400        settings.defaults.prettier = Some(PrettierSettings {
10401            allowed: true,
10402            ..PrettierSettings::default()
10403        });
10404    });
10405    let mut fake_servers = language_registry.register_fake_lsp(
10406        "TypeScript",
10407        FakeLspAdapter {
10408            capabilities: lsp::ServerCapabilities {
10409                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10410                ..Default::default()
10411            },
10412            ..Default::default()
10413        },
10414    );
10415
10416    let buffer = project
10417        .update(cx, |project, cx| {
10418            project.open_local_buffer(path!("/file.ts"), cx)
10419        })
10420        .await
10421        .unwrap();
10422
10423    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10424    let (editor, cx) = cx.add_window_view(|window, cx| {
10425        build_editor_with_project(project.clone(), buffer, window, cx)
10426    });
10427    editor.update_in(cx, |editor, window, cx| {
10428        editor.set_text(
10429            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10430            window,
10431            cx,
10432        )
10433    });
10434
10435    cx.executor().start_waiting();
10436    let fake_server = fake_servers.next().await.unwrap();
10437
10438    let format = editor
10439        .update_in(cx, |editor, window, cx| {
10440            editor.perform_code_action_kind(
10441                project.clone(),
10442                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10443                window,
10444                cx,
10445            )
10446        })
10447        .unwrap();
10448    fake_server
10449        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10450            assert_eq!(
10451                params.text_document.uri,
10452                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10453            );
10454            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10455                lsp::CodeAction {
10456                    title: "Organize Imports".to_string(),
10457                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10458                    edit: Some(lsp::WorkspaceEdit {
10459                        changes: Some(
10460                            [(
10461                                params.text_document.uri.clone(),
10462                                vec![lsp::TextEdit::new(
10463                                    lsp::Range::new(
10464                                        lsp::Position::new(1, 0),
10465                                        lsp::Position::new(2, 0),
10466                                    ),
10467                                    "".to_string(),
10468                                )],
10469                            )]
10470                            .into_iter()
10471                            .collect(),
10472                        ),
10473                        ..Default::default()
10474                    }),
10475                    ..Default::default()
10476                },
10477            )]))
10478        })
10479        .next()
10480        .await;
10481    cx.executor().start_waiting();
10482    format.await;
10483    assert_eq!(
10484        editor.update(cx, |editor, cx| editor.text(cx)),
10485        "import { a } from 'module';\n\nconst x = a;\n"
10486    );
10487
10488    editor.update_in(cx, |editor, window, cx| {
10489        editor.set_text(
10490            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10491            window,
10492            cx,
10493        )
10494    });
10495    // Ensure we don't lock if code action hangs.
10496    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10497        move |params, _| async move {
10498            assert_eq!(
10499                params.text_document.uri,
10500                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10501            );
10502            futures::future::pending::<()>().await;
10503            unreachable!()
10504        },
10505    );
10506    let format = editor
10507        .update_in(cx, |editor, window, cx| {
10508            editor.perform_code_action_kind(
10509                project,
10510                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10511                window,
10512                cx,
10513            )
10514        })
10515        .unwrap();
10516    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10517    cx.executor().start_waiting();
10518    format.await;
10519    assert_eq!(
10520        editor.update(cx, |editor, cx| editor.text(cx)),
10521        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10522    );
10523}
10524
10525#[gpui::test]
10526async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10527    init_test(cx, |_| {});
10528
10529    let mut cx = EditorLspTestContext::new_rust(
10530        lsp::ServerCapabilities {
10531            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10532            ..Default::default()
10533        },
10534        cx,
10535    )
10536    .await;
10537
10538    cx.set_state(indoc! {"
10539        one.twoˇ
10540    "});
10541
10542    // The format request takes a long time. When it completes, it inserts
10543    // a newline and an indent before the `.`
10544    cx.lsp
10545        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10546            let executor = cx.background_executor().clone();
10547            async move {
10548                executor.timer(Duration::from_millis(100)).await;
10549                Ok(Some(vec![lsp::TextEdit {
10550                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10551                    new_text: "\n    ".into(),
10552                }]))
10553            }
10554        });
10555
10556    // Submit a format request.
10557    let format_1 = cx
10558        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10559        .unwrap();
10560    cx.executor().run_until_parked();
10561
10562    // Submit a second format request.
10563    let format_2 = cx
10564        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10565        .unwrap();
10566    cx.executor().run_until_parked();
10567
10568    // Wait for both format requests to complete
10569    cx.executor().advance_clock(Duration::from_millis(200));
10570    cx.executor().start_waiting();
10571    format_1.await.unwrap();
10572    cx.executor().start_waiting();
10573    format_2.await.unwrap();
10574
10575    // The formatting edits only happens once.
10576    cx.assert_editor_state(indoc! {"
10577        one
10578            .twoˇ
10579    "});
10580}
10581
10582#[gpui::test]
10583async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10584    init_test(cx, |settings| {
10585        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10586    });
10587
10588    let mut cx = EditorLspTestContext::new_rust(
10589        lsp::ServerCapabilities {
10590            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10591            ..Default::default()
10592        },
10593        cx,
10594    )
10595    .await;
10596
10597    // Set up a buffer white some trailing whitespace and no trailing newline.
10598    cx.set_state(
10599        &[
10600            "one ",   //
10601            "twoˇ",   //
10602            "three ", //
10603            "four",   //
10604        ]
10605        .join("\n"),
10606    );
10607
10608    // Submit a format request.
10609    let format = cx
10610        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10611        .unwrap();
10612
10613    // Record which buffer changes have been sent to the language server
10614    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10615    cx.lsp
10616        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10617            let buffer_changes = buffer_changes.clone();
10618            move |params, _| {
10619                buffer_changes.lock().extend(
10620                    params
10621                        .content_changes
10622                        .into_iter()
10623                        .map(|e| (e.range.unwrap(), e.text)),
10624                );
10625            }
10626        });
10627
10628    // Handle formatting requests to the language server.
10629    cx.lsp
10630        .set_request_handler::<lsp::request::Formatting, _, _>({
10631            let buffer_changes = buffer_changes.clone();
10632            move |_, _| {
10633                // When formatting is requested, trailing whitespace has already been stripped,
10634                // and the trailing newline has already been added.
10635                assert_eq!(
10636                    &buffer_changes.lock()[1..],
10637                    &[
10638                        (
10639                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10640                            "".into()
10641                        ),
10642                        (
10643                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10644                            "".into()
10645                        ),
10646                        (
10647                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10648                            "\n".into()
10649                        ),
10650                    ]
10651                );
10652
10653                // Insert blank lines between each line of the buffer.
10654                async move {
10655                    Ok(Some(vec![
10656                        lsp::TextEdit {
10657                            range: lsp::Range::new(
10658                                lsp::Position::new(1, 0),
10659                                lsp::Position::new(1, 0),
10660                            ),
10661                            new_text: "\n".into(),
10662                        },
10663                        lsp::TextEdit {
10664                            range: lsp::Range::new(
10665                                lsp::Position::new(2, 0),
10666                                lsp::Position::new(2, 0),
10667                            ),
10668                            new_text: "\n".into(),
10669                        },
10670                    ]))
10671                }
10672            }
10673        });
10674
10675    // After formatting the buffer, the trailing whitespace is stripped,
10676    // a newline is appended, and the edits provided by the language server
10677    // have been applied.
10678    format.await.unwrap();
10679    cx.assert_editor_state(
10680        &[
10681            "one",   //
10682            "",      //
10683            "twoˇ",  //
10684            "",      //
10685            "three", //
10686            "four",  //
10687            "",      //
10688        ]
10689        .join("\n"),
10690    );
10691
10692    // Undoing the formatting undoes the trailing whitespace removal, the
10693    // trailing newline, and the LSP edits.
10694    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10695    cx.assert_editor_state(
10696        &[
10697            "one ",   //
10698            "twoˇ",   //
10699            "three ", //
10700            "four",   //
10701        ]
10702        .join("\n"),
10703    );
10704}
10705
10706#[gpui::test]
10707async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10708    cx: &mut TestAppContext,
10709) {
10710    init_test(cx, |_| {});
10711
10712    cx.update(|cx| {
10713        cx.update_global::<SettingsStore, _>(|settings, cx| {
10714            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10715                settings.auto_signature_help = Some(true);
10716            });
10717        });
10718    });
10719
10720    let mut cx = EditorLspTestContext::new_rust(
10721        lsp::ServerCapabilities {
10722            signature_help_provider: Some(lsp::SignatureHelpOptions {
10723                ..Default::default()
10724            }),
10725            ..Default::default()
10726        },
10727        cx,
10728    )
10729    .await;
10730
10731    let language = Language::new(
10732        LanguageConfig {
10733            name: "Rust".into(),
10734            brackets: BracketPairConfig {
10735                pairs: vec![
10736                    BracketPair {
10737                        start: "{".to_string(),
10738                        end: "}".to_string(),
10739                        close: true,
10740                        surround: true,
10741                        newline: true,
10742                    },
10743                    BracketPair {
10744                        start: "(".to_string(),
10745                        end: ")".to_string(),
10746                        close: true,
10747                        surround: true,
10748                        newline: true,
10749                    },
10750                    BracketPair {
10751                        start: "/*".to_string(),
10752                        end: " */".to_string(),
10753                        close: true,
10754                        surround: true,
10755                        newline: true,
10756                    },
10757                    BracketPair {
10758                        start: "[".to_string(),
10759                        end: "]".to_string(),
10760                        close: false,
10761                        surround: false,
10762                        newline: true,
10763                    },
10764                    BracketPair {
10765                        start: "\"".to_string(),
10766                        end: "\"".to_string(),
10767                        close: true,
10768                        surround: true,
10769                        newline: false,
10770                    },
10771                    BracketPair {
10772                        start: "<".to_string(),
10773                        end: ">".to_string(),
10774                        close: false,
10775                        surround: true,
10776                        newline: true,
10777                    },
10778                ],
10779                ..Default::default()
10780            },
10781            autoclose_before: "})]".to_string(),
10782            ..Default::default()
10783        },
10784        Some(tree_sitter_rust::LANGUAGE.into()),
10785    );
10786    let language = Arc::new(language);
10787
10788    cx.language_registry().add(language.clone());
10789    cx.update_buffer(|buffer, cx| {
10790        buffer.set_language(Some(language), cx);
10791    });
10792
10793    cx.set_state(
10794        &r#"
10795            fn main() {
10796                sampleˇ
10797            }
10798        "#
10799        .unindent(),
10800    );
10801
10802    cx.update_editor(|editor, window, cx| {
10803        editor.handle_input("(", window, cx);
10804    });
10805    cx.assert_editor_state(
10806        &"
10807            fn main() {
10808                sample(ˇ)
10809            }
10810        "
10811        .unindent(),
10812    );
10813
10814    let mocked_response = lsp::SignatureHelp {
10815        signatures: vec![lsp::SignatureInformation {
10816            label: "fn sample(param1: u8, param2: u8)".to_string(),
10817            documentation: None,
10818            parameters: Some(vec![
10819                lsp::ParameterInformation {
10820                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10821                    documentation: None,
10822                },
10823                lsp::ParameterInformation {
10824                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10825                    documentation: None,
10826                },
10827            ]),
10828            active_parameter: None,
10829        }],
10830        active_signature: Some(0),
10831        active_parameter: Some(0),
10832    };
10833    handle_signature_help_request(&mut cx, mocked_response).await;
10834
10835    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10836        .await;
10837
10838    cx.editor(|editor, _, _| {
10839        let signature_help_state = editor.signature_help_state.popover().cloned();
10840        assert_eq!(
10841            signature_help_state.unwrap().label,
10842            "param1: u8, param2: u8"
10843        );
10844    });
10845}
10846
10847#[gpui::test]
10848async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10849    init_test(cx, |_| {});
10850
10851    cx.update(|cx| {
10852        cx.update_global::<SettingsStore, _>(|settings, cx| {
10853            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10854                settings.auto_signature_help = Some(false);
10855                settings.show_signature_help_after_edits = Some(false);
10856            });
10857        });
10858    });
10859
10860    let mut cx = EditorLspTestContext::new_rust(
10861        lsp::ServerCapabilities {
10862            signature_help_provider: Some(lsp::SignatureHelpOptions {
10863                ..Default::default()
10864            }),
10865            ..Default::default()
10866        },
10867        cx,
10868    )
10869    .await;
10870
10871    let language = Language::new(
10872        LanguageConfig {
10873            name: "Rust".into(),
10874            brackets: BracketPairConfig {
10875                pairs: vec![
10876                    BracketPair {
10877                        start: "{".to_string(),
10878                        end: "}".to_string(),
10879                        close: true,
10880                        surround: true,
10881                        newline: true,
10882                    },
10883                    BracketPair {
10884                        start: "(".to_string(),
10885                        end: ")".to_string(),
10886                        close: true,
10887                        surround: true,
10888                        newline: true,
10889                    },
10890                    BracketPair {
10891                        start: "/*".to_string(),
10892                        end: " */".to_string(),
10893                        close: true,
10894                        surround: true,
10895                        newline: true,
10896                    },
10897                    BracketPair {
10898                        start: "[".to_string(),
10899                        end: "]".to_string(),
10900                        close: false,
10901                        surround: false,
10902                        newline: true,
10903                    },
10904                    BracketPair {
10905                        start: "\"".to_string(),
10906                        end: "\"".to_string(),
10907                        close: true,
10908                        surround: true,
10909                        newline: false,
10910                    },
10911                    BracketPair {
10912                        start: "<".to_string(),
10913                        end: ">".to_string(),
10914                        close: false,
10915                        surround: true,
10916                        newline: true,
10917                    },
10918                ],
10919                ..Default::default()
10920            },
10921            autoclose_before: "})]".to_string(),
10922            ..Default::default()
10923        },
10924        Some(tree_sitter_rust::LANGUAGE.into()),
10925    );
10926    let language = Arc::new(language);
10927
10928    cx.language_registry().add(language.clone());
10929    cx.update_buffer(|buffer, cx| {
10930        buffer.set_language(Some(language), cx);
10931    });
10932
10933    // Ensure that signature_help is not called when no signature help is enabled.
10934    cx.set_state(
10935        &r#"
10936            fn main() {
10937                sampleˇ
10938            }
10939        "#
10940        .unindent(),
10941    );
10942    cx.update_editor(|editor, window, cx| {
10943        editor.handle_input("(", window, cx);
10944    });
10945    cx.assert_editor_state(
10946        &"
10947            fn main() {
10948                sample(ˇ)
10949            }
10950        "
10951        .unindent(),
10952    );
10953    cx.editor(|editor, _, _| {
10954        assert!(editor.signature_help_state.task().is_none());
10955    });
10956
10957    let mocked_response = lsp::SignatureHelp {
10958        signatures: vec![lsp::SignatureInformation {
10959            label: "fn sample(param1: u8, param2: u8)".to_string(),
10960            documentation: None,
10961            parameters: Some(vec![
10962                lsp::ParameterInformation {
10963                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10964                    documentation: None,
10965                },
10966                lsp::ParameterInformation {
10967                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10968                    documentation: None,
10969                },
10970            ]),
10971            active_parameter: None,
10972        }],
10973        active_signature: Some(0),
10974        active_parameter: Some(0),
10975    };
10976
10977    // Ensure that signature_help is called when enabled afte edits
10978    cx.update(|_, cx| {
10979        cx.update_global::<SettingsStore, _>(|settings, cx| {
10980            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10981                settings.auto_signature_help = Some(false);
10982                settings.show_signature_help_after_edits = Some(true);
10983            });
10984        });
10985    });
10986    cx.set_state(
10987        &r#"
10988            fn main() {
10989                sampleˇ
10990            }
10991        "#
10992        .unindent(),
10993    );
10994    cx.update_editor(|editor, window, cx| {
10995        editor.handle_input("(", window, cx);
10996    });
10997    cx.assert_editor_state(
10998        &"
10999            fn main() {
11000                sample(ˇ)
11001            }
11002        "
11003        .unindent(),
11004    );
11005    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11006    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11007        .await;
11008    cx.update_editor(|editor, _, _| {
11009        let signature_help_state = editor.signature_help_state.popover().cloned();
11010        assert!(signature_help_state.is_some());
11011        assert_eq!(
11012            signature_help_state.unwrap().label,
11013            "param1: u8, param2: u8"
11014        );
11015        editor.signature_help_state = SignatureHelpState::default();
11016    });
11017
11018    // Ensure that signature_help is called when auto signature help override is enabled
11019    cx.update(|_, cx| {
11020        cx.update_global::<SettingsStore, _>(|settings, cx| {
11021            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11022                settings.auto_signature_help = Some(true);
11023                settings.show_signature_help_after_edits = Some(false);
11024            });
11025        });
11026    });
11027    cx.set_state(
11028        &r#"
11029            fn main() {
11030                sampleˇ
11031            }
11032        "#
11033        .unindent(),
11034    );
11035    cx.update_editor(|editor, window, cx| {
11036        editor.handle_input("(", window, cx);
11037    });
11038    cx.assert_editor_state(
11039        &"
11040            fn main() {
11041                sample(ˇ)
11042            }
11043        "
11044        .unindent(),
11045    );
11046    handle_signature_help_request(&mut cx, mocked_response).await;
11047    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11048        .await;
11049    cx.editor(|editor, _, _| {
11050        let signature_help_state = editor.signature_help_state.popover().cloned();
11051        assert!(signature_help_state.is_some());
11052        assert_eq!(
11053            signature_help_state.unwrap().label,
11054            "param1: u8, param2: u8"
11055        );
11056    });
11057}
11058
11059#[gpui::test]
11060async fn test_signature_help(cx: &mut TestAppContext) {
11061    init_test(cx, |_| {});
11062    cx.update(|cx| {
11063        cx.update_global::<SettingsStore, _>(|settings, cx| {
11064            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11065                settings.auto_signature_help = Some(true);
11066            });
11067        });
11068    });
11069
11070    let mut cx = EditorLspTestContext::new_rust(
11071        lsp::ServerCapabilities {
11072            signature_help_provider: Some(lsp::SignatureHelpOptions {
11073                ..Default::default()
11074            }),
11075            ..Default::default()
11076        },
11077        cx,
11078    )
11079    .await;
11080
11081    // A test that directly calls `show_signature_help`
11082    cx.update_editor(|editor, window, cx| {
11083        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11084    });
11085
11086    let mocked_response = lsp::SignatureHelp {
11087        signatures: vec![lsp::SignatureInformation {
11088            label: "fn sample(param1: u8, param2: u8)".to_string(),
11089            documentation: None,
11090            parameters: Some(vec![
11091                lsp::ParameterInformation {
11092                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11093                    documentation: None,
11094                },
11095                lsp::ParameterInformation {
11096                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11097                    documentation: None,
11098                },
11099            ]),
11100            active_parameter: None,
11101        }],
11102        active_signature: Some(0),
11103        active_parameter: Some(0),
11104    };
11105    handle_signature_help_request(&mut cx, mocked_response).await;
11106
11107    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11108        .await;
11109
11110    cx.editor(|editor, _, _| {
11111        let signature_help_state = editor.signature_help_state.popover().cloned();
11112        assert!(signature_help_state.is_some());
11113        assert_eq!(
11114            signature_help_state.unwrap().label,
11115            "param1: u8, param2: u8"
11116        );
11117    });
11118
11119    // When exiting outside from inside the brackets, `signature_help` is closed.
11120    cx.set_state(indoc! {"
11121        fn main() {
11122            sample(ˇ);
11123        }
11124
11125        fn sample(param1: u8, param2: u8) {}
11126    "});
11127
11128    cx.update_editor(|editor, window, cx| {
11129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11130            s.select_ranges([0..0])
11131        });
11132    });
11133
11134    let mocked_response = lsp::SignatureHelp {
11135        signatures: Vec::new(),
11136        active_signature: None,
11137        active_parameter: None,
11138    };
11139    handle_signature_help_request(&mut cx, mocked_response).await;
11140
11141    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11142        .await;
11143
11144    cx.editor(|editor, _, _| {
11145        assert!(!editor.signature_help_state.is_shown());
11146    });
11147
11148    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11149    cx.set_state(indoc! {"
11150        fn main() {
11151            sample(ˇ);
11152        }
11153
11154        fn sample(param1: u8, param2: u8) {}
11155    "});
11156
11157    let mocked_response = lsp::SignatureHelp {
11158        signatures: vec![lsp::SignatureInformation {
11159            label: "fn sample(param1: u8, param2: u8)".to_string(),
11160            documentation: None,
11161            parameters: Some(vec![
11162                lsp::ParameterInformation {
11163                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11164                    documentation: None,
11165                },
11166                lsp::ParameterInformation {
11167                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11168                    documentation: None,
11169                },
11170            ]),
11171            active_parameter: None,
11172        }],
11173        active_signature: Some(0),
11174        active_parameter: Some(0),
11175    };
11176    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11177    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11178        .await;
11179    cx.editor(|editor, _, _| {
11180        assert!(editor.signature_help_state.is_shown());
11181    });
11182
11183    // Restore the popover with more parameter input
11184    cx.set_state(indoc! {"
11185        fn main() {
11186            sample(param1, param2ˇ);
11187        }
11188
11189        fn sample(param1: u8, param2: u8) {}
11190    "});
11191
11192    let mocked_response = lsp::SignatureHelp {
11193        signatures: vec![lsp::SignatureInformation {
11194            label: "fn sample(param1: u8, param2: u8)".to_string(),
11195            documentation: None,
11196            parameters: Some(vec![
11197                lsp::ParameterInformation {
11198                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11199                    documentation: None,
11200                },
11201                lsp::ParameterInformation {
11202                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11203                    documentation: None,
11204                },
11205            ]),
11206            active_parameter: None,
11207        }],
11208        active_signature: Some(0),
11209        active_parameter: Some(1),
11210    };
11211    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11212    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11213        .await;
11214
11215    // When selecting a range, the popover is gone.
11216    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11217    cx.update_editor(|editor, window, cx| {
11218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11219            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11220        })
11221    });
11222    cx.assert_editor_state(indoc! {"
11223        fn main() {
11224            sample(param1, «ˇparam2»);
11225        }
11226
11227        fn sample(param1: u8, param2: u8) {}
11228    "});
11229    cx.editor(|editor, _, _| {
11230        assert!(!editor.signature_help_state.is_shown());
11231    });
11232
11233    // When unselecting again, the popover is back if within the brackets.
11234    cx.update_editor(|editor, window, cx| {
11235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11236            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11237        })
11238    });
11239    cx.assert_editor_state(indoc! {"
11240        fn main() {
11241            sample(param1, ˇparam2);
11242        }
11243
11244        fn sample(param1: u8, param2: u8) {}
11245    "});
11246    handle_signature_help_request(&mut cx, mocked_response).await;
11247    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11248        .await;
11249    cx.editor(|editor, _, _| {
11250        assert!(editor.signature_help_state.is_shown());
11251    });
11252
11253    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11254    cx.update_editor(|editor, window, cx| {
11255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11256            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11257            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11258        })
11259    });
11260    cx.assert_editor_state(indoc! {"
11261        fn main() {
11262            sample(param1, ˇparam2);
11263        }
11264
11265        fn sample(param1: u8, param2: u8) {}
11266    "});
11267
11268    let mocked_response = lsp::SignatureHelp {
11269        signatures: vec![lsp::SignatureInformation {
11270            label: "fn sample(param1: u8, param2: u8)".to_string(),
11271            documentation: None,
11272            parameters: Some(vec![
11273                lsp::ParameterInformation {
11274                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11275                    documentation: None,
11276                },
11277                lsp::ParameterInformation {
11278                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11279                    documentation: None,
11280                },
11281            ]),
11282            active_parameter: None,
11283        }],
11284        active_signature: Some(0),
11285        active_parameter: Some(1),
11286    };
11287    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11288    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11289        .await;
11290    cx.update_editor(|editor, _, cx| {
11291        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11292    });
11293    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11294        .await;
11295    cx.update_editor(|editor, window, cx| {
11296        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11297            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11298        })
11299    });
11300    cx.assert_editor_state(indoc! {"
11301        fn main() {
11302            sample(param1, «ˇparam2»);
11303        }
11304
11305        fn sample(param1: u8, param2: u8) {}
11306    "});
11307    cx.update_editor(|editor, window, cx| {
11308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11309            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11310        })
11311    });
11312    cx.assert_editor_state(indoc! {"
11313        fn main() {
11314            sample(param1, ˇparam2);
11315        }
11316
11317        fn sample(param1: u8, param2: u8) {}
11318    "});
11319    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11320        .await;
11321}
11322
11323#[gpui::test]
11324async fn test_completion_mode(cx: &mut TestAppContext) {
11325    init_test(cx, |_| {});
11326    let mut cx = EditorLspTestContext::new_rust(
11327        lsp::ServerCapabilities {
11328            completion_provider: Some(lsp::CompletionOptions {
11329                resolve_provider: Some(true),
11330                ..Default::default()
11331            }),
11332            ..Default::default()
11333        },
11334        cx,
11335    )
11336    .await;
11337
11338    struct Run {
11339        run_description: &'static str,
11340        initial_state: String,
11341        buffer_marked_text: String,
11342        completion_label: &'static str,
11343        completion_text: &'static str,
11344        expected_with_insert_mode: String,
11345        expected_with_replace_mode: String,
11346        expected_with_replace_subsequence_mode: String,
11347        expected_with_replace_suffix_mode: String,
11348    }
11349
11350    let runs = [
11351        Run {
11352            run_description: "Start of word matches completion text",
11353            initial_state: "before ediˇ after".into(),
11354            buffer_marked_text: "before <edi|> after".into(),
11355            completion_label: "editor",
11356            completion_text: "editor",
11357            expected_with_insert_mode: "before editorˇ after".into(),
11358            expected_with_replace_mode: "before editorˇ after".into(),
11359            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11360            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11361        },
11362        Run {
11363            run_description: "Accept same text at the middle of the word",
11364            initial_state: "before ediˇtor after".into(),
11365            buffer_marked_text: "before <edi|tor> after".into(),
11366            completion_label: "editor",
11367            completion_text: "editor",
11368            expected_with_insert_mode: "before editorˇtor after".into(),
11369            expected_with_replace_mode: "before editorˇ after".into(),
11370            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11371            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11372        },
11373        Run {
11374            run_description: "End of word matches completion text -- cursor at end",
11375            initial_state: "before torˇ after".into(),
11376            buffer_marked_text: "before <tor|> after".into(),
11377            completion_label: "editor",
11378            completion_text: "editor",
11379            expected_with_insert_mode: "before editorˇ after".into(),
11380            expected_with_replace_mode: "before editorˇ after".into(),
11381            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11382            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11383        },
11384        Run {
11385            run_description: "End of word matches completion text -- cursor at start",
11386            initial_state: "before ˇtor after".into(),
11387            buffer_marked_text: "before <|tor> after".into(),
11388            completion_label: "editor",
11389            completion_text: "editor",
11390            expected_with_insert_mode: "before editorˇtor after".into(),
11391            expected_with_replace_mode: "before editorˇ after".into(),
11392            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11393            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11394        },
11395        Run {
11396            run_description: "Prepend text containing whitespace",
11397            initial_state: "pˇfield: bool".into(),
11398            buffer_marked_text: "<p|field>: bool".into(),
11399            completion_label: "pub ",
11400            completion_text: "pub ",
11401            expected_with_insert_mode: "pub ˇfield: bool".into(),
11402            expected_with_replace_mode: "pub ˇ: bool".into(),
11403            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11404            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11405        },
11406        Run {
11407            run_description: "Add element to start of list",
11408            initial_state: "[element_ˇelement_2]".into(),
11409            buffer_marked_text: "[<element_|element_2>]".into(),
11410            completion_label: "element_1",
11411            completion_text: "element_1",
11412            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11413            expected_with_replace_mode: "[element_1ˇ]".into(),
11414            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11415            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11416        },
11417        Run {
11418            run_description: "Add element to start of list -- first and second elements are equal",
11419            initial_state: "[elˇelement]".into(),
11420            buffer_marked_text: "[<el|element>]".into(),
11421            completion_label: "element",
11422            completion_text: "element",
11423            expected_with_insert_mode: "[elementˇelement]".into(),
11424            expected_with_replace_mode: "[elementˇ]".into(),
11425            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11426            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11427        },
11428        Run {
11429            run_description: "Ends with matching suffix",
11430            initial_state: "SubˇError".into(),
11431            buffer_marked_text: "<Sub|Error>".into(),
11432            completion_label: "SubscriptionError",
11433            completion_text: "SubscriptionError",
11434            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11435            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11436            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11437            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11438        },
11439        Run {
11440            run_description: "Suffix is a subsequence -- contiguous",
11441            initial_state: "SubˇErr".into(),
11442            buffer_marked_text: "<Sub|Err>".into(),
11443            completion_label: "SubscriptionError",
11444            completion_text: "SubscriptionError",
11445            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11446            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11447            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11448            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11449        },
11450        Run {
11451            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11452            initial_state: "Suˇscrirr".into(),
11453            buffer_marked_text: "<Su|scrirr>".into(),
11454            completion_label: "SubscriptionError",
11455            completion_text: "SubscriptionError",
11456            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11457            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11458            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11459            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11460        },
11461        Run {
11462            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11463            initial_state: "foo(indˇix)".into(),
11464            buffer_marked_text: "foo(<ind|ix>)".into(),
11465            completion_label: "node_index",
11466            completion_text: "node_index",
11467            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11468            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11469            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11470            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11471        },
11472        Run {
11473            run_description: "Replace range ends before cursor - should extend to cursor",
11474            initial_state: "before editˇo after".into(),
11475            buffer_marked_text: "before <{ed}>it|o after".into(),
11476            completion_label: "editor",
11477            completion_text: "editor",
11478            expected_with_insert_mode: "before editorˇo after".into(),
11479            expected_with_replace_mode: "before editorˇo after".into(),
11480            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11481            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11482        },
11483        Run {
11484            run_description: "Uses label for suffix matching",
11485            initial_state: "before ediˇtor after".into(),
11486            buffer_marked_text: "before <edi|tor> after".into(),
11487            completion_label: "editor",
11488            completion_text: "editor()",
11489            expected_with_insert_mode: "before editor()ˇtor after".into(),
11490            expected_with_replace_mode: "before editor()ˇ after".into(),
11491            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11492            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11493        },
11494        Run {
11495            run_description: "Case insensitive subsequence and suffix matching",
11496            initial_state: "before EDiˇtoR after".into(),
11497            buffer_marked_text: "before <EDi|toR> after".into(),
11498            completion_label: "editor",
11499            completion_text: "editor",
11500            expected_with_insert_mode: "before editorˇtoR after".into(),
11501            expected_with_replace_mode: "before editorˇ after".into(),
11502            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11503            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11504        },
11505    ];
11506
11507    for run in runs {
11508        let run_variations = [
11509            (LspInsertMode::Insert, run.expected_with_insert_mode),
11510            (LspInsertMode::Replace, run.expected_with_replace_mode),
11511            (
11512                LspInsertMode::ReplaceSubsequence,
11513                run.expected_with_replace_subsequence_mode,
11514            ),
11515            (
11516                LspInsertMode::ReplaceSuffix,
11517                run.expected_with_replace_suffix_mode,
11518            ),
11519        ];
11520
11521        for (lsp_insert_mode, expected_text) in run_variations {
11522            eprintln!(
11523                "run = {:?}, mode = {lsp_insert_mode:.?}",
11524                run.run_description,
11525            );
11526
11527            update_test_language_settings(&mut cx, |settings| {
11528                settings.defaults.completions = Some(CompletionSettings {
11529                    lsp_insert_mode,
11530                    words: WordsCompletionMode::Disabled,
11531                    lsp: true,
11532                    lsp_fetch_timeout_ms: 0,
11533                });
11534            });
11535
11536            cx.set_state(&run.initial_state);
11537            cx.update_editor(|editor, window, cx| {
11538                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11539            });
11540
11541            let counter = Arc::new(AtomicUsize::new(0));
11542            handle_completion_request_with_insert_and_replace(
11543                &mut cx,
11544                &run.buffer_marked_text,
11545                vec![(run.completion_label, run.completion_text)],
11546                counter.clone(),
11547            )
11548            .await;
11549            cx.condition(|editor, _| editor.context_menu_visible())
11550                .await;
11551            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11552
11553            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11554                editor
11555                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11556                    .unwrap()
11557            });
11558            cx.assert_editor_state(&expected_text);
11559            handle_resolve_completion_request(&mut cx, None).await;
11560            apply_additional_edits.await.unwrap();
11561        }
11562    }
11563}
11564
11565#[gpui::test]
11566async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11567    init_test(cx, |_| {});
11568    let mut cx = EditorLspTestContext::new_rust(
11569        lsp::ServerCapabilities {
11570            completion_provider: Some(lsp::CompletionOptions {
11571                resolve_provider: Some(true),
11572                ..Default::default()
11573            }),
11574            ..Default::default()
11575        },
11576        cx,
11577    )
11578    .await;
11579
11580    let initial_state = "SubˇError";
11581    let buffer_marked_text = "<Sub|Error>";
11582    let completion_text = "SubscriptionError";
11583    let expected_with_insert_mode = "SubscriptionErrorˇError";
11584    let expected_with_replace_mode = "SubscriptionErrorˇ";
11585
11586    update_test_language_settings(&mut cx, |settings| {
11587        settings.defaults.completions = Some(CompletionSettings {
11588            words: WordsCompletionMode::Disabled,
11589            // set the opposite here to ensure that the action is overriding the default behavior
11590            lsp_insert_mode: LspInsertMode::Insert,
11591            lsp: true,
11592            lsp_fetch_timeout_ms: 0,
11593        });
11594    });
11595
11596    cx.set_state(initial_state);
11597    cx.update_editor(|editor, window, cx| {
11598        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11599    });
11600
11601    let counter = Arc::new(AtomicUsize::new(0));
11602    handle_completion_request_with_insert_and_replace(
11603        &mut cx,
11604        &buffer_marked_text,
11605        vec![(completion_text, completion_text)],
11606        counter.clone(),
11607    )
11608    .await;
11609    cx.condition(|editor, _| editor.context_menu_visible())
11610        .await;
11611    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11612
11613    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11614        editor
11615            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11616            .unwrap()
11617    });
11618    cx.assert_editor_state(&expected_with_replace_mode);
11619    handle_resolve_completion_request(&mut cx, None).await;
11620    apply_additional_edits.await.unwrap();
11621
11622    update_test_language_settings(&mut cx, |settings| {
11623        settings.defaults.completions = Some(CompletionSettings {
11624            words: WordsCompletionMode::Disabled,
11625            // set the opposite here to ensure that the action is overriding the default behavior
11626            lsp_insert_mode: LspInsertMode::Replace,
11627            lsp: true,
11628            lsp_fetch_timeout_ms: 0,
11629        });
11630    });
11631
11632    cx.set_state(initial_state);
11633    cx.update_editor(|editor, window, cx| {
11634        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11635    });
11636    handle_completion_request_with_insert_and_replace(
11637        &mut cx,
11638        &buffer_marked_text,
11639        vec![(completion_text, completion_text)],
11640        counter.clone(),
11641    )
11642    .await;
11643    cx.condition(|editor, _| editor.context_menu_visible())
11644        .await;
11645    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11646
11647    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11648        editor
11649            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11650            .unwrap()
11651    });
11652    cx.assert_editor_state(&expected_with_insert_mode);
11653    handle_resolve_completion_request(&mut cx, None).await;
11654    apply_additional_edits.await.unwrap();
11655}
11656
11657#[gpui::test]
11658async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11659    init_test(cx, |_| {});
11660    let mut cx = EditorLspTestContext::new_rust(
11661        lsp::ServerCapabilities {
11662            completion_provider: Some(lsp::CompletionOptions {
11663                resolve_provider: Some(true),
11664                ..Default::default()
11665            }),
11666            ..Default::default()
11667        },
11668        cx,
11669    )
11670    .await;
11671
11672    // scenario: surrounding text matches completion text
11673    let completion_text = "to_offset";
11674    let initial_state = indoc! {"
11675        1. buf.to_offˇsuffix
11676        2. buf.to_offˇsuf
11677        3. buf.to_offˇfix
11678        4. buf.to_offˇ
11679        5. into_offˇensive
11680        6. ˇsuffix
11681        7. let ˇ //
11682        8. aaˇzz
11683        9. buf.to_off«zzzzzˇ»suffix
11684        10. buf.«ˇzzzzz»suffix
11685        11. to_off«ˇzzzzz»
11686
11687        buf.to_offˇsuffix  // newest cursor
11688    "};
11689    let completion_marked_buffer = indoc! {"
11690        1. buf.to_offsuffix
11691        2. buf.to_offsuf
11692        3. buf.to_offfix
11693        4. buf.to_off
11694        5. into_offensive
11695        6. suffix
11696        7. let  //
11697        8. aazz
11698        9. buf.to_offzzzzzsuffix
11699        10. buf.zzzzzsuffix
11700        11. to_offzzzzz
11701
11702        buf.<to_off|suffix>  // newest cursor
11703    "};
11704    let expected = indoc! {"
11705        1. buf.to_offsetˇ
11706        2. buf.to_offsetˇsuf
11707        3. buf.to_offsetˇfix
11708        4. buf.to_offsetˇ
11709        5. into_offsetˇensive
11710        6. to_offsetˇsuffix
11711        7. let to_offsetˇ //
11712        8. aato_offsetˇzz
11713        9. buf.to_offsetˇ
11714        10. buf.to_offsetˇsuffix
11715        11. to_offsetˇ
11716
11717        buf.to_offsetˇ  // newest cursor
11718    "};
11719    cx.set_state(initial_state);
11720    cx.update_editor(|editor, window, cx| {
11721        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11722    });
11723    handle_completion_request_with_insert_and_replace(
11724        &mut cx,
11725        completion_marked_buffer,
11726        vec![(completion_text, completion_text)],
11727        Arc::new(AtomicUsize::new(0)),
11728    )
11729    .await;
11730    cx.condition(|editor, _| editor.context_menu_visible())
11731        .await;
11732    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11733        editor
11734            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11735            .unwrap()
11736    });
11737    cx.assert_editor_state(expected);
11738    handle_resolve_completion_request(&mut cx, None).await;
11739    apply_additional_edits.await.unwrap();
11740
11741    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11742    let completion_text = "foo_and_bar";
11743    let initial_state = indoc! {"
11744        1. ooanbˇ
11745        2. zooanbˇ
11746        3. ooanbˇz
11747        4. zooanbˇz
11748        5. ooanˇ
11749        6. oanbˇ
11750
11751        ooanbˇ
11752    "};
11753    let completion_marked_buffer = indoc! {"
11754        1. ooanb
11755        2. zooanb
11756        3. ooanbz
11757        4. zooanbz
11758        5. ooan
11759        6. oanb
11760
11761        <ooanb|>
11762    "};
11763    let expected = indoc! {"
11764        1. foo_and_barˇ
11765        2. zfoo_and_barˇ
11766        3. foo_and_barˇz
11767        4. zfoo_and_barˇz
11768        5. ooanfoo_and_barˇ
11769        6. oanbfoo_and_barˇ
11770
11771        foo_and_barˇ
11772    "};
11773    cx.set_state(initial_state);
11774    cx.update_editor(|editor, window, cx| {
11775        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11776    });
11777    handle_completion_request_with_insert_and_replace(
11778        &mut cx,
11779        completion_marked_buffer,
11780        vec![(completion_text, completion_text)],
11781        Arc::new(AtomicUsize::new(0)),
11782    )
11783    .await;
11784    cx.condition(|editor, _| editor.context_menu_visible())
11785        .await;
11786    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11787        editor
11788            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11789            .unwrap()
11790    });
11791    cx.assert_editor_state(expected);
11792    handle_resolve_completion_request(&mut cx, None).await;
11793    apply_additional_edits.await.unwrap();
11794
11795    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11796    // (expects the same as if it was inserted at the end)
11797    let completion_text = "foo_and_bar";
11798    let initial_state = indoc! {"
11799        1. ooˇanb
11800        2. zooˇanb
11801        3. ooˇanbz
11802        4. zooˇanbz
11803
11804        ooˇanb
11805    "};
11806    let completion_marked_buffer = indoc! {"
11807        1. ooanb
11808        2. zooanb
11809        3. ooanbz
11810        4. zooanbz
11811
11812        <oo|anb>
11813    "};
11814    let expected = indoc! {"
11815        1. foo_and_barˇ
11816        2. zfoo_and_barˇ
11817        3. foo_and_barˇz
11818        4. zfoo_and_barˇz
11819
11820        foo_and_barˇ
11821    "};
11822    cx.set_state(initial_state);
11823    cx.update_editor(|editor, window, cx| {
11824        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11825    });
11826    handle_completion_request_with_insert_and_replace(
11827        &mut cx,
11828        completion_marked_buffer,
11829        vec![(completion_text, completion_text)],
11830        Arc::new(AtomicUsize::new(0)),
11831    )
11832    .await;
11833    cx.condition(|editor, _| editor.context_menu_visible())
11834        .await;
11835    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11836        editor
11837            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11838            .unwrap()
11839    });
11840    cx.assert_editor_state(expected);
11841    handle_resolve_completion_request(&mut cx, None).await;
11842    apply_additional_edits.await.unwrap();
11843}
11844
11845// This used to crash
11846#[gpui::test]
11847async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11848    init_test(cx, |_| {});
11849
11850    let buffer_text = indoc! {"
11851        fn main() {
11852            10.satu;
11853
11854            //
11855            // separate cursors so they open in different excerpts (manually reproducible)
11856            //
11857
11858            10.satu20;
11859        }
11860    "};
11861    let multibuffer_text_with_selections = indoc! {"
11862        fn main() {
11863            10.satuˇ;
11864
11865            //
11866
11867            //
11868
11869            10.satuˇ20;
11870        }
11871    "};
11872    let expected_multibuffer = indoc! {"
11873        fn main() {
11874            10.saturating_sub()ˇ;
11875
11876            //
11877
11878            //
11879
11880            10.saturating_sub()ˇ;
11881        }
11882    "};
11883
11884    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11885    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11886
11887    let fs = FakeFs::new(cx.executor());
11888    fs.insert_tree(
11889        path!("/a"),
11890        json!({
11891            "main.rs": buffer_text,
11892        }),
11893    )
11894    .await;
11895
11896    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11897    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11898    language_registry.add(rust_lang());
11899    let mut fake_servers = language_registry.register_fake_lsp(
11900        "Rust",
11901        FakeLspAdapter {
11902            capabilities: lsp::ServerCapabilities {
11903                completion_provider: Some(lsp::CompletionOptions {
11904                    resolve_provider: None,
11905                    ..lsp::CompletionOptions::default()
11906                }),
11907                ..lsp::ServerCapabilities::default()
11908            },
11909            ..FakeLspAdapter::default()
11910        },
11911    );
11912    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11913    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11914    let buffer = project
11915        .update(cx, |project, cx| {
11916            project.open_local_buffer(path!("/a/main.rs"), cx)
11917        })
11918        .await
11919        .unwrap();
11920
11921    let multi_buffer = cx.new(|cx| {
11922        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11923        multi_buffer.push_excerpts(
11924            buffer.clone(),
11925            [ExcerptRange::new(0..first_excerpt_end)],
11926            cx,
11927        );
11928        multi_buffer.push_excerpts(
11929            buffer.clone(),
11930            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11931            cx,
11932        );
11933        multi_buffer
11934    });
11935
11936    let editor = workspace
11937        .update(cx, |_, window, cx| {
11938            cx.new(|cx| {
11939                Editor::new(
11940                    EditorMode::Full {
11941                        scale_ui_elements_with_buffer_font_size: false,
11942                        show_active_line_background: false,
11943                        sized_by_content: false,
11944                    },
11945                    multi_buffer.clone(),
11946                    Some(project.clone()),
11947                    window,
11948                    cx,
11949                )
11950            })
11951        })
11952        .unwrap();
11953
11954    let pane = workspace
11955        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11956        .unwrap();
11957    pane.update_in(cx, |pane, window, cx| {
11958        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11959    });
11960
11961    let fake_server = fake_servers.next().await.unwrap();
11962
11963    editor.update_in(cx, |editor, window, cx| {
11964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11965            s.select_ranges([
11966                Point::new(1, 11)..Point::new(1, 11),
11967                Point::new(7, 11)..Point::new(7, 11),
11968            ])
11969        });
11970
11971        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11972    });
11973
11974    editor.update_in(cx, |editor, window, cx| {
11975        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11976    });
11977
11978    fake_server
11979        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11980            let completion_item = lsp::CompletionItem {
11981                label: "saturating_sub()".into(),
11982                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11983                    lsp::InsertReplaceEdit {
11984                        new_text: "saturating_sub()".to_owned(),
11985                        insert: lsp::Range::new(
11986                            lsp::Position::new(7, 7),
11987                            lsp::Position::new(7, 11),
11988                        ),
11989                        replace: lsp::Range::new(
11990                            lsp::Position::new(7, 7),
11991                            lsp::Position::new(7, 13),
11992                        ),
11993                    },
11994                )),
11995                ..lsp::CompletionItem::default()
11996            };
11997
11998            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11999        })
12000        .next()
12001        .await
12002        .unwrap();
12003
12004    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12005        .await;
12006
12007    editor
12008        .update_in(cx, |editor, window, cx| {
12009            editor
12010                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12011                .unwrap()
12012        })
12013        .await
12014        .unwrap();
12015
12016    editor.update(cx, |editor, cx| {
12017        assert_text_with_selections(editor, expected_multibuffer, cx);
12018    })
12019}
12020
12021#[gpui::test]
12022async fn test_completion(cx: &mut TestAppContext) {
12023    init_test(cx, |_| {});
12024
12025    let mut cx = EditorLspTestContext::new_rust(
12026        lsp::ServerCapabilities {
12027            completion_provider: Some(lsp::CompletionOptions {
12028                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12029                resolve_provider: Some(true),
12030                ..Default::default()
12031            }),
12032            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12033            ..Default::default()
12034        },
12035        cx,
12036    )
12037    .await;
12038    let counter = Arc::new(AtomicUsize::new(0));
12039
12040    cx.set_state(indoc! {"
12041        oneˇ
12042        two
12043        three
12044    "});
12045    cx.simulate_keystroke(".");
12046    handle_completion_request(
12047        indoc! {"
12048            one.|<>
12049            two
12050            three
12051        "},
12052        vec!["first_completion", "second_completion"],
12053        true,
12054        counter.clone(),
12055        &mut cx,
12056    )
12057    .await;
12058    cx.condition(|editor, _| editor.context_menu_visible())
12059        .await;
12060    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12061
12062    let _handler = handle_signature_help_request(
12063        &mut cx,
12064        lsp::SignatureHelp {
12065            signatures: vec![lsp::SignatureInformation {
12066                label: "test signature".to_string(),
12067                documentation: None,
12068                parameters: Some(vec![lsp::ParameterInformation {
12069                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12070                    documentation: None,
12071                }]),
12072                active_parameter: None,
12073            }],
12074            active_signature: None,
12075            active_parameter: None,
12076        },
12077    );
12078    cx.update_editor(|editor, window, cx| {
12079        assert!(
12080            !editor.signature_help_state.is_shown(),
12081            "No signature help was called for"
12082        );
12083        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12084    });
12085    cx.run_until_parked();
12086    cx.update_editor(|editor, _, _| {
12087        assert!(
12088            !editor.signature_help_state.is_shown(),
12089            "No signature help should be shown when completions menu is open"
12090        );
12091    });
12092
12093    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12094        editor.context_menu_next(&Default::default(), window, cx);
12095        editor
12096            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12097            .unwrap()
12098    });
12099    cx.assert_editor_state(indoc! {"
12100        one.second_completionˇ
12101        two
12102        three
12103    "});
12104
12105    handle_resolve_completion_request(
12106        &mut cx,
12107        Some(vec![
12108            (
12109                //This overlaps with the primary completion edit which is
12110                //misbehavior from the LSP spec, test that we filter it out
12111                indoc! {"
12112                    one.second_ˇcompletion
12113                    two
12114                    threeˇ
12115                "},
12116                "overlapping additional edit",
12117            ),
12118            (
12119                indoc! {"
12120                    one.second_completion
12121                    two
12122                    threeˇ
12123                "},
12124                "\nadditional edit",
12125            ),
12126        ]),
12127    )
12128    .await;
12129    apply_additional_edits.await.unwrap();
12130    cx.assert_editor_state(indoc! {"
12131        one.second_completionˇ
12132        two
12133        three
12134        additional edit
12135    "});
12136
12137    cx.set_state(indoc! {"
12138        one.second_completion
12139        twoˇ
12140        threeˇ
12141        additional edit
12142    "});
12143    cx.simulate_keystroke(" ");
12144    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12145    cx.simulate_keystroke("s");
12146    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12147
12148    cx.assert_editor_state(indoc! {"
12149        one.second_completion
12150        two sˇ
12151        three sˇ
12152        additional edit
12153    "});
12154    handle_completion_request(
12155        indoc! {"
12156            one.second_completion
12157            two s
12158            three <s|>
12159            additional edit
12160        "},
12161        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12162        true,
12163        counter.clone(),
12164        &mut cx,
12165    )
12166    .await;
12167    cx.condition(|editor, _| editor.context_menu_visible())
12168        .await;
12169    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12170
12171    cx.simulate_keystroke("i");
12172
12173    handle_completion_request(
12174        indoc! {"
12175            one.second_completion
12176            two si
12177            three <si|>
12178            additional edit
12179        "},
12180        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12181        true,
12182        counter.clone(),
12183        &mut cx,
12184    )
12185    .await;
12186    cx.condition(|editor, _| editor.context_menu_visible())
12187        .await;
12188    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12189
12190    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12191        editor
12192            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12193            .unwrap()
12194    });
12195    cx.assert_editor_state(indoc! {"
12196        one.second_completion
12197        two sixth_completionˇ
12198        three sixth_completionˇ
12199        additional edit
12200    "});
12201
12202    apply_additional_edits.await.unwrap();
12203
12204    update_test_language_settings(&mut cx, |settings| {
12205        settings.defaults.show_completions_on_input = Some(false);
12206    });
12207    cx.set_state("editorˇ");
12208    cx.simulate_keystroke(".");
12209    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12210    cx.simulate_keystrokes("c l o");
12211    cx.assert_editor_state("editor.cloˇ");
12212    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12213    cx.update_editor(|editor, window, cx| {
12214        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12215    });
12216    handle_completion_request(
12217        "editor.<clo|>",
12218        vec!["close", "clobber"],
12219        true,
12220        counter.clone(),
12221        &mut cx,
12222    )
12223    .await;
12224    cx.condition(|editor, _| editor.context_menu_visible())
12225        .await;
12226    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12227
12228    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12229        editor
12230            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12231            .unwrap()
12232    });
12233    cx.assert_editor_state("editor.clobberˇ");
12234    handle_resolve_completion_request(&mut cx, None).await;
12235    apply_additional_edits.await.unwrap();
12236}
12237
12238#[gpui::test]
12239async fn test_completion_reuse(cx: &mut TestAppContext) {
12240    init_test(cx, |_| {});
12241
12242    let mut cx = EditorLspTestContext::new_rust(
12243        lsp::ServerCapabilities {
12244            completion_provider: Some(lsp::CompletionOptions {
12245                trigger_characters: Some(vec![".".to_string()]),
12246                ..Default::default()
12247            }),
12248            ..Default::default()
12249        },
12250        cx,
12251    )
12252    .await;
12253
12254    let counter = Arc::new(AtomicUsize::new(0));
12255    cx.set_state("objˇ");
12256    cx.simulate_keystroke(".");
12257
12258    // Initial completion request returns complete results
12259    let is_incomplete = false;
12260    handle_completion_request(
12261        "obj.|<>",
12262        vec!["a", "ab", "abc"],
12263        is_incomplete,
12264        counter.clone(),
12265        &mut cx,
12266    )
12267    .await;
12268    cx.run_until_parked();
12269    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12270    cx.assert_editor_state("obj.ˇ");
12271    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12272
12273    // Type "a" - filters existing completions
12274    cx.simulate_keystroke("a");
12275    cx.run_until_parked();
12276    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12277    cx.assert_editor_state("obj.aˇ");
12278    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12279
12280    // Type "b" - filters existing completions
12281    cx.simulate_keystroke("b");
12282    cx.run_until_parked();
12283    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12284    cx.assert_editor_state("obj.abˇ");
12285    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12286
12287    // Type "c" - filters existing completions
12288    cx.simulate_keystroke("c");
12289    cx.run_until_parked();
12290    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12291    cx.assert_editor_state("obj.abcˇ");
12292    check_displayed_completions(vec!["abc"], &mut cx);
12293
12294    // Backspace to delete "c" - filters existing completions
12295    cx.update_editor(|editor, window, cx| {
12296        editor.backspace(&Backspace, window, cx);
12297    });
12298    cx.run_until_parked();
12299    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12300    cx.assert_editor_state("obj.abˇ");
12301    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12302
12303    // Moving cursor to the left dismisses menu.
12304    cx.update_editor(|editor, window, cx| {
12305        editor.move_left(&MoveLeft, window, cx);
12306    });
12307    cx.run_until_parked();
12308    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12309    cx.assert_editor_state("obj.aˇb");
12310    cx.update_editor(|editor, _, _| {
12311        assert_eq!(editor.context_menu_visible(), false);
12312    });
12313
12314    // Type "b" - new request
12315    cx.simulate_keystroke("b");
12316    let is_incomplete = false;
12317    handle_completion_request(
12318        "obj.<ab|>a",
12319        vec!["ab", "abc"],
12320        is_incomplete,
12321        counter.clone(),
12322        &mut cx,
12323    )
12324    .await;
12325    cx.run_until_parked();
12326    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12327    cx.assert_editor_state("obj.abˇb");
12328    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12329
12330    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12331    cx.update_editor(|editor, window, cx| {
12332        editor.backspace(&Backspace, window, cx);
12333    });
12334    let is_incomplete = false;
12335    handle_completion_request(
12336        "obj.<a|>b",
12337        vec!["a", "ab", "abc"],
12338        is_incomplete,
12339        counter.clone(),
12340        &mut cx,
12341    )
12342    .await;
12343    cx.run_until_parked();
12344    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12345    cx.assert_editor_state("obj.aˇb");
12346    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12347
12348    // Backspace to delete "a" - dismisses menu.
12349    cx.update_editor(|editor, window, cx| {
12350        editor.backspace(&Backspace, window, cx);
12351    });
12352    cx.run_until_parked();
12353    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12354    cx.assert_editor_state("obj.ˇb");
12355    cx.update_editor(|editor, _, _| {
12356        assert_eq!(editor.context_menu_visible(), false);
12357    });
12358}
12359
12360#[gpui::test]
12361async fn test_word_completion(cx: &mut TestAppContext) {
12362    let lsp_fetch_timeout_ms = 10;
12363    init_test(cx, |language_settings| {
12364        language_settings.defaults.completions = Some(CompletionSettings {
12365            words: WordsCompletionMode::Fallback,
12366            lsp: true,
12367            lsp_fetch_timeout_ms: 10,
12368            lsp_insert_mode: LspInsertMode::Insert,
12369        });
12370    });
12371
12372    let mut cx = EditorLspTestContext::new_rust(
12373        lsp::ServerCapabilities {
12374            completion_provider: Some(lsp::CompletionOptions {
12375                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12376                ..lsp::CompletionOptions::default()
12377            }),
12378            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12379            ..lsp::ServerCapabilities::default()
12380        },
12381        cx,
12382    )
12383    .await;
12384
12385    let throttle_completions = Arc::new(AtomicBool::new(false));
12386
12387    let lsp_throttle_completions = throttle_completions.clone();
12388    let _completion_requests_handler =
12389        cx.lsp
12390            .server
12391            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12392                let lsp_throttle_completions = lsp_throttle_completions.clone();
12393                let cx = cx.clone();
12394                async move {
12395                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12396                        cx.background_executor()
12397                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12398                            .await;
12399                    }
12400                    Ok(Some(lsp::CompletionResponse::Array(vec![
12401                        lsp::CompletionItem {
12402                            label: "first".into(),
12403                            ..lsp::CompletionItem::default()
12404                        },
12405                        lsp::CompletionItem {
12406                            label: "last".into(),
12407                            ..lsp::CompletionItem::default()
12408                        },
12409                    ])))
12410                }
12411            });
12412
12413    cx.set_state(indoc! {"
12414        oneˇ
12415        two
12416        three
12417    "});
12418    cx.simulate_keystroke(".");
12419    cx.executor().run_until_parked();
12420    cx.condition(|editor, _| editor.context_menu_visible())
12421        .await;
12422    cx.update_editor(|editor, window, cx| {
12423        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12424        {
12425            assert_eq!(
12426                completion_menu_entries(&menu),
12427                &["first", "last"],
12428                "When LSP server is fast to reply, no fallback word completions are used"
12429            );
12430        } else {
12431            panic!("expected completion menu to be open");
12432        }
12433        editor.cancel(&Cancel, window, cx);
12434    });
12435    cx.executor().run_until_parked();
12436    cx.condition(|editor, _| !editor.context_menu_visible())
12437        .await;
12438
12439    throttle_completions.store(true, atomic::Ordering::Release);
12440    cx.simulate_keystroke(".");
12441    cx.executor()
12442        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12443    cx.executor().run_until_parked();
12444    cx.condition(|editor, _| editor.context_menu_visible())
12445        .await;
12446    cx.update_editor(|editor, _, _| {
12447        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12448        {
12449            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12450                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12451        } else {
12452            panic!("expected completion menu to be open");
12453        }
12454    });
12455}
12456
12457#[gpui::test]
12458async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12459    init_test(cx, |language_settings| {
12460        language_settings.defaults.completions = Some(CompletionSettings {
12461            words: WordsCompletionMode::Enabled,
12462            lsp: true,
12463            lsp_fetch_timeout_ms: 0,
12464            lsp_insert_mode: LspInsertMode::Insert,
12465        });
12466    });
12467
12468    let mut cx = EditorLspTestContext::new_rust(
12469        lsp::ServerCapabilities {
12470            completion_provider: Some(lsp::CompletionOptions {
12471                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12472                ..lsp::CompletionOptions::default()
12473            }),
12474            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12475            ..lsp::ServerCapabilities::default()
12476        },
12477        cx,
12478    )
12479    .await;
12480
12481    let _completion_requests_handler =
12482        cx.lsp
12483            .server
12484            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12485                Ok(Some(lsp::CompletionResponse::Array(vec![
12486                    lsp::CompletionItem {
12487                        label: "first".into(),
12488                        ..lsp::CompletionItem::default()
12489                    },
12490                    lsp::CompletionItem {
12491                        label: "last".into(),
12492                        ..lsp::CompletionItem::default()
12493                    },
12494                ])))
12495            });
12496
12497    cx.set_state(indoc! {"ˇ
12498        first
12499        last
12500        second
12501    "});
12502    cx.simulate_keystroke(".");
12503    cx.executor().run_until_parked();
12504    cx.condition(|editor, _| editor.context_menu_visible())
12505        .await;
12506    cx.update_editor(|editor, _, _| {
12507        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12508        {
12509            assert_eq!(
12510                completion_menu_entries(&menu),
12511                &["first", "last", "second"],
12512                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12513            );
12514        } else {
12515            panic!("expected completion menu to be open");
12516        }
12517    });
12518}
12519
12520#[gpui::test]
12521async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12522    init_test(cx, |language_settings| {
12523        language_settings.defaults.completions = Some(CompletionSettings {
12524            words: WordsCompletionMode::Disabled,
12525            lsp: true,
12526            lsp_fetch_timeout_ms: 0,
12527            lsp_insert_mode: LspInsertMode::Insert,
12528        });
12529    });
12530
12531    let mut cx = EditorLspTestContext::new_rust(
12532        lsp::ServerCapabilities {
12533            completion_provider: Some(lsp::CompletionOptions {
12534                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12535                ..lsp::CompletionOptions::default()
12536            }),
12537            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12538            ..lsp::ServerCapabilities::default()
12539        },
12540        cx,
12541    )
12542    .await;
12543
12544    let _completion_requests_handler =
12545        cx.lsp
12546            .server
12547            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12548                panic!("LSP completions should not be queried when dealing with word completions")
12549            });
12550
12551    cx.set_state(indoc! {"ˇ
12552        first
12553        last
12554        second
12555    "});
12556    cx.update_editor(|editor, window, cx| {
12557        editor.show_word_completions(&ShowWordCompletions, window, cx);
12558    });
12559    cx.executor().run_until_parked();
12560    cx.condition(|editor, _| editor.context_menu_visible())
12561        .await;
12562    cx.update_editor(|editor, _, _| {
12563        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12564        {
12565            assert_eq!(
12566                completion_menu_entries(&menu),
12567                &["first", "last", "second"],
12568                "`ShowWordCompletions` action should show word completions"
12569            );
12570        } else {
12571            panic!("expected completion menu to be open");
12572        }
12573    });
12574
12575    cx.simulate_keystroke("l");
12576    cx.executor().run_until_parked();
12577    cx.condition(|editor, _| editor.context_menu_visible())
12578        .await;
12579    cx.update_editor(|editor, _, _| {
12580        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12581        {
12582            assert_eq!(
12583                completion_menu_entries(&menu),
12584                &["last"],
12585                "After showing word completions, further editing should filter them and not query the LSP"
12586            );
12587        } else {
12588            panic!("expected completion menu to be open");
12589        }
12590    });
12591}
12592
12593#[gpui::test]
12594async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12595    init_test(cx, |language_settings| {
12596        language_settings.defaults.completions = Some(CompletionSettings {
12597            words: WordsCompletionMode::Fallback,
12598            lsp: false,
12599            lsp_fetch_timeout_ms: 0,
12600            lsp_insert_mode: LspInsertMode::Insert,
12601        });
12602    });
12603
12604    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12605
12606    cx.set_state(indoc! {"ˇ
12607        0_usize
12608        let
12609        33
12610        4.5f32
12611    "});
12612    cx.update_editor(|editor, window, cx| {
12613        editor.show_completions(&ShowCompletions::default(), window, cx);
12614    });
12615    cx.executor().run_until_parked();
12616    cx.condition(|editor, _| editor.context_menu_visible())
12617        .await;
12618    cx.update_editor(|editor, window, cx| {
12619        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12620        {
12621            assert_eq!(
12622                completion_menu_entries(&menu),
12623                &["let"],
12624                "With no digits in the completion query, no digits should be in the word completions"
12625            );
12626        } else {
12627            panic!("expected completion menu to be open");
12628        }
12629        editor.cancel(&Cancel, window, cx);
12630    });
12631
12632    cx.set_state(indoc! {"12633        0_usize
12634        let
12635        3
12636        33.35f32
12637    "});
12638    cx.update_editor(|editor, window, cx| {
12639        editor.show_completions(&ShowCompletions::default(), window, cx);
12640    });
12641    cx.executor().run_until_parked();
12642    cx.condition(|editor, _| editor.context_menu_visible())
12643        .await;
12644    cx.update_editor(|editor, _, _| {
12645        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12646        {
12647            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12648                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12649        } else {
12650            panic!("expected completion menu to be open");
12651        }
12652    });
12653}
12654
12655fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12656    let position = || lsp::Position {
12657        line: params.text_document_position.position.line,
12658        character: params.text_document_position.position.character,
12659    };
12660    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12661        range: lsp::Range {
12662            start: position(),
12663            end: position(),
12664        },
12665        new_text: text.to_string(),
12666    }))
12667}
12668
12669#[gpui::test]
12670async fn test_multiline_completion(cx: &mut TestAppContext) {
12671    init_test(cx, |_| {});
12672
12673    let fs = FakeFs::new(cx.executor());
12674    fs.insert_tree(
12675        path!("/a"),
12676        json!({
12677            "main.ts": "a",
12678        }),
12679    )
12680    .await;
12681
12682    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12684    let typescript_language = Arc::new(Language::new(
12685        LanguageConfig {
12686            name: "TypeScript".into(),
12687            matcher: LanguageMatcher {
12688                path_suffixes: vec!["ts".to_string()],
12689                ..LanguageMatcher::default()
12690            },
12691            line_comments: vec!["// ".into()],
12692            ..LanguageConfig::default()
12693        },
12694        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12695    ));
12696    language_registry.add(typescript_language.clone());
12697    let mut fake_servers = language_registry.register_fake_lsp(
12698        "TypeScript",
12699        FakeLspAdapter {
12700            capabilities: lsp::ServerCapabilities {
12701                completion_provider: Some(lsp::CompletionOptions {
12702                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12703                    ..lsp::CompletionOptions::default()
12704                }),
12705                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12706                ..lsp::ServerCapabilities::default()
12707            },
12708            // Emulate vtsls label generation
12709            label_for_completion: Some(Box::new(|item, _| {
12710                let text = if let Some(description) = item
12711                    .label_details
12712                    .as_ref()
12713                    .and_then(|label_details| label_details.description.as_ref())
12714                {
12715                    format!("{} {}", item.label, description)
12716                } else if let Some(detail) = &item.detail {
12717                    format!("{} {}", item.label, detail)
12718                } else {
12719                    item.label.clone()
12720                };
12721                let len = text.len();
12722                Some(language::CodeLabel {
12723                    text,
12724                    runs: Vec::new(),
12725                    filter_range: 0..len,
12726                })
12727            })),
12728            ..FakeLspAdapter::default()
12729        },
12730    );
12731    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12732    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12733    let worktree_id = workspace
12734        .update(cx, |workspace, _window, cx| {
12735            workspace.project().update(cx, |project, cx| {
12736                project.worktrees(cx).next().unwrap().read(cx).id()
12737            })
12738        })
12739        .unwrap();
12740    let _buffer = project
12741        .update(cx, |project, cx| {
12742            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12743        })
12744        .await
12745        .unwrap();
12746    let editor = workspace
12747        .update(cx, |workspace, window, cx| {
12748            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12749        })
12750        .unwrap()
12751        .await
12752        .unwrap()
12753        .downcast::<Editor>()
12754        .unwrap();
12755    let fake_server = fake_servers.next().await.unwrap();
12756
12757    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12758    let multiline_label_2 = "a\nb\nc\n";
12759    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12760    let multiline_description = "d\ne\nf\n";
12761    let multiline_detail_2 = "g\nh\ni\n";
12762
12763    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12764        move |params, _| async move {
12765            Ok(Some(lsp::CompletionResponse::Array(vec![
12766                lsp::CompletionItem {
12767                    label: multiline_label.to_string(),
12768                    text_edit: gen_text_edit(&params, "new_text_1"),
12769                    ..lsp::CompletionItem::default()
12770                },
12771                lsp::CompletionItem {
12772                    label: "single line label 1".to_string(),
12773                    detail: Some(multiline_detail.to_string()),
12774                    text_edit: gen_text_edit(&params, "new_text_2"),
12775                    ..lsp::CompletionItem::default()
12776                },
12777                lsp::CompletionItem {
12778                    label: "single line label 2".to_string(),
12779                    label_details: Some(lsp::CompletionItemLabelDetails {
12780                        description: Some(multiline_description.to_string()),
12781                        detail: None,
12782                    }),
12783                    text_edit: gen_text_edit(&params, "new_text_2"),
12784                    ..lsp::CompletionItem::default()
12785                },
12786                lsp::CompletionItem {
12787                    label: multiline_label_2.to_string(),
12788                    detail: Some(multiline_detail_2.to_string()),
12789                    text_edit: gen_text_edit(&params, "new_text_3"),
12790                    ..lsp::CompletionItem::default()
12791                },
12792                lsp::CompletionItem {
12793                    label: "Label with many     spaces and \t but without newlines".to_string(),
12794                    detail: Some(
12795                        "Details with many     spaces and \t but without newlines".to_string(),
12796                    ),
12797                    text_edit: gen_text_edit(&params, "new_text_4"),
12798                    ..lsp::CompletionItem::default()
12799                },
12800            ])))
12801        },
12802    );
12803
12804    editor.update_in(cx, |editor, window, cx| {
12805        cx.focus_self(window);
12806        editor.move_to_end(&MoveToEnd, window, cx);
12807        editor.handle_input(".", window, cx);
12808    });
12809    cx.run_until_parked();
12810    completion_handle.next().await.unwrap();
12811
12812    editor.update(cx, |editor, _| {
12813        assert!(editor.context_menu_visible());
12814        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12815        {
12816            let completion_labels = menu
12817                .completions
12818                .borrow()
12819                .iter()
12820                .map(|c| c.label.text.clone())
12821                .collect::<Vec<_>>();
12822            assert_eq!(
12823                completion_labels,
12824                &[
12825                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12826                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12827                    "single line label 2 d e f ",
12828                    "a b c g h i ",
12829                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12830                ],
12831                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12832            );
12833
12834            for completion in menu
12835                .completions
12836                .borrow()
12837                .iter() {
12838                    assert_eq!(
12839                        completion.label.filter_range,
12840                        0..completion.label.text.len(),
12841                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12842                    );
12843                }
12844        } else {
12845            panic!("expected completion menu to be open");
12846        }
12847    });
12848}
12849
12850#[gpui::test]
12851async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12852    init_test(cx, |_| {});
12853    let mut cx = EditorLspTestContext::new_rust(
12854        lsp::ServerCapabilities {
12855            completion_provider: Some(lsp::CompletionOptions {
12856                trigger_characters: Some(vec![".".to_string()]),
12857                ..Default::default()
12858            }),
12859            ..Default::default()
12860        },
12861        cx,
12862    )
12863    .await;
12864    cx.lsp
12865        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12866            Ok(Some(lsp::CompletionResponse::Array(vec![
12867                lsp::CompletionItem {
12868                    label: "first".into(),
12869                    ..Default::default()
12870                },
12871                lsp::CompletionItem {
12872                    label: "last".into(),
12873                    ..Default::default()
12874                },
12875            ])))
12876        });
12877    cx.set_state("variableˇ");
12878    cx.simulate_keystroke(".");
12879    cx.executor().run_until_parked();
12880
12881    cx.update_editor(|editor, _, _| {
12882        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12883        {
12884            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12885        } else {
12886            panic!("expected completion menu to be open");
12887        }
12888    });
12889
12890    cx.update_editor(|editor, window, cx| {
12891        editor.move_page_down(&MovePageDown::default(), window, cx);
12892        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12893        {
12894            assert!(
12895                menu.selected_item == 1,
12896                "expected PageDown to select the last item from the context menu"
12897            );
12898        } else {
12899            panic!("expected completion menu to stay open after PageDown");
12900        }
12901    });
12902
12903    cx.update_editor(|editor, window, cx| {
12904        editor.move_page_up(&MovePageUp::default(), window, cx);
12905        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12906        {
12907            assert!(
12908                menu.selected_item == 0,
12909                "expected PageUp to select the first item from the context menu"
12910            );
12911        } else {
12912            panic!("expected completion menu to stay open after PageUp");
12913        }
12914    });
12915}
12916
12917#[gpui::test]
12918async fn test_as_is_completions(cx: &mut TestAppContext) {
12919    init_test(cx, |_| {});
12920    let mut cx = EditorLspTestContext::new_rust(
12921        lsp::ServerCapabilities {
12922            completion_provider: Some(lsp::CompletionOptions {
12923                ..Default::default()
12924            }),
12925            ..Default::default()
12926        },
12927        cx,
12928    )
12929    .await;
12930    cx.lsp
12931        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12932            Ok(Some(lsp::CompletionResponse::Array(vec![
12933                lsp::CompletionItem {
12934                    label: "unsafe".into(),
12935                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12936                        range: lsp::Range {
12937                            start: lsp::Position {
12938                                line: 1,
12939                                character: 2,
12940                            },
12941                            end: lsp::Position {
12942                                line: 1,
12943                                character: 3,
12944                            },
12945                        },
12946                        new_text: "unsafe".to_string(),
12947                    })),
12948                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12949                    ..Default::default()
12950                },
12951            ])))
12952        });
12953    cx.set_state("fn a() {}\n");
12954    cx.executor().run_until_parked();
12955    cx.update_editor(|editor, window, cx| {
12956        editor.show_completions(
12957            &ShowCompletions {
12958                trigger: Some("\n".into()),
12959            },
12960            window,
12961            cx,
12962        );
12963    });
12964    cx.executor().run_until_parked();
12965
12966    cx.update_editor(|editor, window, cx| {
12967        editor.confirm_completion(&Default::default(), window, cx)
12968    });
12969    cx.executor().run_until_parked();
12970    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12971}
12972
12973#[gpui::test]
12974async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12975    init_test(cx, |_| {});
12976
12977    let mut cx = EditorLspTestContext::new_rust(
12978        lsp::ServerCapabilities {
12979            completion_provider: Some(lsp::CompletionOptions {
12980                trigger_characters: Some(vec![".".to_string()]),
12981                resolve_provider: Some(true),
12982                ..Default::default()
12983            }),
12984            ..Default::default()
12985        },
12986        cx,
12987    )
12988    .await;
12989
12990    cx.set_state("fn main() { let a = 2ˇ; }");
12991    cx.simulate_keystroke(".");
12992    let completion_item = lsp::CompletionItem {
12993        label: "Some".into(),
12994        kind: Some(lsp::CompletionItemKind::SNIPPET),
12995        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12996        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12997            kind: lsp::MarkupKind::Markdown,
12998            value: "```rust\nSome(2)\n```".to_string(),
12999        })),
13000        deprecated: Some(false),
13001        sort_text: Some("Some".to_string()),
13002        filter_text: Some("Some".to_string()),
13003        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13004        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13005            range: lsp::Range {
13006                start: lsp::Position {
13007                    line: 0,
13008                    character: 22,
13009                },
13010                end: lsp::Position {
13011                    line: 0,
13012                    character: 22,
13013                },
13014            },
13015            new_text: "Some(2)".to_string(),
13016        })),
13017        additional_text_edits: Some(vec![lsp::TextEdit {
13018            range: lsp::Range {
13019                start: lsp::Position {
13020                    line: 0,
13021                    character: 20,
13022                },
13023                end: lsp::Position {
13024                    line: 0,
13025                    character: 22,
13026                },
13027            },
13028            new_text: "".to_string(),
13029        }]),
13030        ..Default::default()
13031    };
13032
13033    let closure_completion_item = completion_item.clone();
13034    let counter = Arc::new(AtomicUsize::new(0));
13035    let counter_clone = counter.clone();
13036    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13037        let task_completion_item = closure_completion_item.clone();
13038        counter_clone.fetch_add(1, atomic::Ordering::Release);
13039        async move {
13040            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13041                is_incomplete: true,
13042                item_defaults: None,
13043                items: vec![task_completion_item],
13044            })))
13045        }
13046    });
13047
13048    cx.condition(|editor, _| editor.context_menu_visible())
13049        .await;
13050    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13051    assert!(request.next().await.is_some());
13052    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13053
13054    cx.simulate_keystrokes("S o m");
13055    cx.condition(|editor, _| editor.context_menu_visible())
13056        .await;
13057    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13058    assert!(request.next().await.is_some());
13059    assert!(request.next().await.is_some());
13060    assert!(request.next().await.is_some());
13061    request.close();
13062    assert!(request.next().await.is_none());
13063    assert_eq!(
13064        counter.load(atomic::Ordering::Acquire),
13065        4,
13066        "With the completions menu open, only one LSP request should happen per input"
13067    );
13068}
13069
13070#[gpui::test]
13071async fn test_toggle_comment(cx: &mut TestAppContext) {
13072    init_test(cx, |_| {});
13073    let mut cx = EditorTestContext::new(cx).await;
13074    let language = Arc::new(Language::new(
13075        LanguageConfig {
13076            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13077            ..Default::default()
13078        },
13079        Some(tree_sitter_rust::LANGUAGE.into()),
13080    ));
13081    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13082
13083    // If multiple selections intersect a line, the line is only toggled once.
13084    cx.set_state(indoc! {"
13085        fn a() {
13086            «//b();
13087            ˇ»// «c();
13088            //ˇ»  d();
13089        }
13090    "});
13091
13092    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13093
13094    cx.assert_editor_state(indoc! {"
13095        fn a() {
13096            «b();
13097            c();
13098            ˇ» d();
13099        }
13100    "});
13101
13102    // The comment prefix is inserted at the same column for every line in a
13103    // selection.
13104    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13105
13106    cx.assert_editor_state(indoc! {"
13107        fn a() {
13108            // «b();
13109            // c();
13110            ˇ»//  d();
13111        }
13112    "});
13113
13114    // If a selection ends at the beginning of a line, that line is not toggled.
13115    cx.set_selections_state(indoc! {"
13116        fn a() {
13117            // b();
13118            «// c();
13119        ˇ»    //  d();
13120        }
13121    "});
13122
13123    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13124
13125    cx.assert_editor_state(indoc! {"
13126        fn a() {
13127            // b();
13128            «c();
13129        ˇ»    //  d();
13130        }
13131    "});
13132
13133    // If a selection span a single line and is empty, the line is toggled.
13134    cx.set_state(indoc! {"
13135        fn a() {
13136            a();
13137            b();
13138        ˇ
13139        }
13140    "});
13141
13142    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13143
13144    cx.assert_editor_state(indoc! {"
13145        fn a() {
13146            a();
13147            b();
13148        //•ˇ
13149        }
13150    "});
13151
13152    // If a selection span multiple lines, empty lines are not toggled.
13153    cx.set_state(indoc! {"
13154        fn a() {
13155            «a();
13156
13157            c();ˇ»
13158        }
13159    "});
13160
13161    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13162
13163    cx.assert_editor_state(indoc! {"
13164        fn a() {
13165            // «a();
13166
13167            // c();ˇ»
13168        }
13169    "});
13170
13171    // If a selection includes multiple comment prefixes, all lines are uncommented.
13172    cx.set_state(indoc! {"
13173        fn a() {
13174            «// a();
13175            /// b();
13176            //! c();ˇ»
13177        }
13178    "});
13179
13180    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13181
13182    cx.assert_editor_state(indoc! {"
13183        fn a() {
13184            «a();
13185            b();
13186            c();ˇ»
13187        }
13188    "});
13189}
13190
13191#[gpui::test]
13192async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13193    init_test(cx, |_| {});
13194    let mut cx = EditorTestContext::new(cx).await;
13195    let language = Arc::new(Language::new(
13196        LanguageConfig {
13197            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13198            ..Default::default()
13199        },
13200        Some(tree_sitter_rust::LANGUAGE.into()),
13201    ));
13202    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13203
13204    let toggle_comments = &ToggleComments {
13205        advance_downwards: false,
13206        ignore_indent: true,
13207    };
13208
13209    // If multiple selections intersect a line, the line is only toggled once.
13210    cx.set_state(indoc! {"
13211        fn a() {
13212        //    «b();
13213        //    c();
13214        //    ˇ» d();
13215        }
13216    "});
13217
13218    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13219
13220    cx.assert_editor_state(indoc! {"
13221        fn a() {
13222            «b();
13223            c();
13224            ˇ» d();
13225        }
13226    "});
13227
13228    // The comment prefix is inserted at the beginning of each line
13229    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13230
13231    cx.assert_editor_state(indoc! {"
13232        fn a() {
13233        //    «b();
13234        //    c();
13235        //    ˇ» d();
13236        }
13237    "});
13238
13239    // If a selection ends at the beginning of a line, that line is not toggled.
13240    cx.set_selections_state(indoc! {"
13241        fn a() {
13242        //    b();
13243        //    «c();
13244        ˇ»//     d();
13245        }
13246    "});
13247
13248    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13249
13250    cx.assert_editor_state(indoc! {"
13251        fn a() {
13252        //    b();
13253            «c();
13254        ˇ»//     d();
13255        }
13256    "});
13257
13258    // If a selection span a single line and is empty, the line is toggled.
13259    cx.set_state(indoc! {"
13260        fn a() {
13261            a();
13262            b();
13263        ˇ
13264        }
13265    "});
13266
13267    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13268
13269    cx.assert_editor_state(indoc! {"
13270        fn a() {
13271            a();
13272            b();
13273        //ˇ
13274        }
13275    "});
13276
13277    // If a selection span multiple lines, empty lines are not toggled.
13278    cx.set_state(indoc! {"
13279        fn a() {
13280            «a();
13281
13282            c();ˇ»
13283        }
13284    "});
13285
13286    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13287
13288    cx.assert_editor_state(indoc! {"
13289        fn a() {
13290        //    «a();
13291
13292        //    c();ˇ»
13293        }
13294    "});
13295
13296    // If a selection includes multiple comment prefixes, all lines are uncommented.
13297    cx.set_state(indoc! {"
13298        fn a() {
13299        //    «a();
13300        ///    b();
13301        //!    c();ˇ»
13302        }
13303    "});
13304
13305    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13306
13307    cx.assert_editor_state(indoc! {"
13308        fn a() {
13309            «a();
13310            b();
13311            c();ˇ»
13312        }
13313    "});
13314}
13315
13316#[gpui::test]
13317async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13318    init_test(cx, |_| {});
13319
13320    let language = Arc::new(Language::new(
13321        LanguageConfig {
13322            line_comments: vec!["// ".into()],
13323            ..Default::default()
13324        },
13325        Some(tree_sitter_rust::LANGUAGE.into()),
13326    ));
13327
13328    let mut cx = EditorTestContext::new(cx).await;
13329
13330    cx.language_registry().add(language.clone());
13331    cx.update_buffer(|buffer, cx| {
13332        buffer.set_language(Some(language), cx);
13333    });
13334
13335    let toggle_comments = &ToggleComments {
13336        advance_downwards: true,
13337        ignore_indent: false,
13338    };
13339
13340    // Single cursor on one line -> advance
13341    // Cursor moves horizontally 3 characters as well on non-blank line
13342    cx.set_state(indoc!(
13343        "fn a() {
13344             ˇdog();
13345             cat();
13346        }"
13347    ));
13348    cx.update_editor(|editor, window, cx| {
13349        editor.toggle_comments(toggle_comments, window, cx);
13350    });
13351    cx.assert_editor_state(indoc!(
13352        "fn a() {
13353             // dog();
13354             catˇ();
13355        }"
13356    ));
13357
13358    // Single selection on one line -> don't advance
13359    cx.set_state(indoc!(
13360        "fn a() {
13361             «dog()ˇ»;
13362             cat();
13363        }"
13364    ));
13365    cx.update_editor(|editor, window, cx| {
13366        editor.toggle_comments(toggle_comments, window, cx);
13367    });
13368    cx.assert_editor_state(indoc!(
13369        "fn a() {
13370             // «dog()ˇ»;
13371             cat();
13372        }"
13373    ));
13374
13375    // Multiple cursors on one line -> advance
13376    cx.set_state(indoc!(
13377        "fn a() {
13378             ˇdˇog();
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    // Multiple cursors on one line, with selection -> don't advance
13393    cx.set_state(indoc!(
13394        "fn a() {
13395             ˇdˇog«()ˇ»;
13396             cat();
13397        }"
13398    ));
13399    cx.update_editor(|editor, window, cx| {
13400        editor.toggle_comments(toggle_comments, window, cx);
13401    });
13402    cx.assert_editor_state(indoc!(
13403        "fn a() {
13404             // ˇdˇog«()ˇ»;
13405             cat();
13406        }"
13407    ));
13408
13409    // Single cursor on one line -> advance
13410    // Cursor moves to column 0 on blank line
13411    cx.set_state(indoc!(
13412        "fn a() {
13413             ˇdog();
13414
13415             cat();
13416        }"
13417    ));
13418    cx.update_editor(|editor, window, cx| {
13419        editor.toggle_comments(toggle_comments, window, cx);
13420    });
13421    cx.assert_editor_state(indoc!(
13422        "fn a() {
13423             // dog();
13424        ˇ
13425             cat();
13426        }"
13427    ));
13428
13429    // Single cursor on one line -> advance
13430    // Cursor starts and ends at column 0
13431    cx.set_state(indoc!(
13432        "fn a() {
13433         ˇ    dog();
13434             cat();
13435        }"
13436    ));
13437    cx.update_editor(|editor, window, cx| {
13438        editor.toggle_comments(toggle_comments, window, cx);
13439    });
13440    cx.assert_editor_state(indoc!(
13441        "fn a() {
13442             // dog();
13443         ˇ    cat();
13444        }"
13445    ));
13446}
13447
13448#[gpui::test]
13449async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13450    init_test(cx, |_| {});
13451
13452    let mut cx = EditorTestContext::new(cx).await;
13453
13454    let html_language = Arc::new(
13455        Language::new(
13456            LanguageConfig {
13457                name: "HTML".into(),
13458                block_comment: Some(("<!-- ".into(), " -->".into())),
13459                ..Default::default()
13460            },
13461            Some(tree_sitter_html::LANGUAGE.into()),
13462        )
13463        .with_injection_query(
13464            r#"
13465            (script_element
13466                (raw_text) @injection.content
13467                (#set! injection.language "javascript"))
13468            "#,
13469        )
13470        .unwrap(),
13471    );
13472
13473    let javascript_language = Arc::new(Language::new(
13474        LanguageConfig {
13475            name: "JavaScript".into(),
13476            line_comments: vec!["// ".into()],
13477            ..Default::default()
13478        },
13479        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13480    ));
13481
13482    cx.language_registry().add(html_language.clone());
13483    cx.language_registry().add(javascript_language.clone());
13484    cx.update_buffer(|buffer, cx| {
13485        buffer.set_language(Some(html_language), cx);
13486    });
13487
13488    // Toggle comments for empty selections
13489    cx.set_state(
13490        &r#"
13491            <p>A</p>ˇ
13492            <p>B</p>ˇ
13493            <p>C</p>ˇ
13494        "#
13495        .unindent(),
13496    );
13497    cx.update_editor(|editor, window, cx| {
13498        editor.toggle_comments(&ToggleComments::default(), window, cx)
13499    });
13500    cx.assert_editor_state(
13501        &r#"
13502            <!-- <p>A</p>ˇ -->
13503            <!-- <p>B</p>ˇ -->
13504            <!-- <p>C</p>ˇ -->
13505        "#
13506        .unindent(),
13507    );
13508    cx.update_editor(|editor, window, cx| {
13509        editor.toggle_comments(&ToggleComments::default(), window, cx)
13510    });
13511    cx.assert_editor_state(
13512        &r#"
13513            <p>A</p>ˇ
13514            <p>B</p>ˇ
13515            <p>C</p>ˇ
13516        "#
13517        .unindent(),
13518    );
13519
13520    // Toggle comments for mixture of empty and non-empty selections, where
13521    // multiple selections occupy a given line.
13522    cx.set_state(
13523        &r#"
13524            <p>A«</p>
13525            <p>ˇ»B</p>ˇ
13526            <p>C«</p>
13527            <p>ˇ»D</p>ˇ
13528        "#
13529        .unindent(),
13530    );
13531
13532    cx.update_editor(|editor, window, cx| {
13533        editor.toggle_comments(&ToggleComments::default(), window, cx)
13534    });
13535    cx.assert_editor_state(
13536        &r#"
13537            <!-- <p>A«</p>
13538            <p>ˇ»B</p>ˇ -->
13539            <!-- <p>C«</p>
13540            <p>ˇ»D</p>ˇ -->
13541        "#
13542        .unindent(),
13543    );
13544    cx.update_editor(|editor, window, cx| {
13545        editor.toggle_comments(&ToggleComments::default(), window, cx)
13546    });
13547    cx.assert_editor_state(
13548        &r#"
13549            <p>A«</p>
13550            <p>ˇ»B</p>ˇ
13551            <p>C«</p>
13552            <p>ˇ»D</p>ˇ
13553        "#
13554        .unindent(),
13555    );
13556
13557    // Toggle comments when different languages are active for different
13558    // selections.
13559    cx.set_state(
13560        &r#"
13561            ˇ<script>
13562                ˇvar x = new Y();
13563            ˇ</script>
13564        "#
13565        .unindent(),
13566    );
13567    cx.executor().run_until_parked();
13568    cx.update_editor(|editor, window, cx| {
13569        editor.toggle_comments(&ToggleComments::default(), window, cx)
13570    });
13571    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13572    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13573    cx.assert_editor_state(
13574        &r#"
13575            <!-- ˇ<script> -->
13576                // ˇvar x = new Y();
13577            <!-- ˇ</script> -->
13578        "#
13579        .unindent(),
13580    );
13581}
13582
13583#[gpui::test]
13584fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13585    init_test(cx, |_| {});
13586
13587    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13588    let multibuffer = cx.new(|cx| {
13589        let mut multibuffer = MultiBuffer::new(ReadWrite);
13590        multibuffer.push_excerpts(
13591            buffer.clone(),
13592            [
13593                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13594                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13595            ],
13596            cx,
13597        );
13598        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13599        multibuffer
13600    });
13601
13602    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13603    editor.update_in(cx, |editor, window, cx| {
13604        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13605        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13606            s.select_ranges([
13607                Point::new(0, 0)..Point::new(0, 0),
13608                Point::new(1, 0)..Point::new(1, 0),
13609            ])
13610        });
13611
13612        editor.handle_input("X", window, cx);
13613        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13614        assert_eq!(
13615            editor.selections.ranges(cx),
13616            [
13617                Point::new(0, 1)..Point::new(0, 1),
13618                Point::new(1, 1)..Point::new(1, 1),
13619            ]
13620        );
13621
13622        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13623        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13624            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13625        });
13626        editor.backspace(&Default::default(), window, cx);
13627        assert_eq!(editor.text(cx), "Xa\nbbb");
13628        assert_eq!(
13629            editor.selections.ranges(cx),
13630            [Point::new(1, 0)..Point::new(1, 0)]
13631        );
13632
13633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13634            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13635        });
13636        editor.backspace(&Default::default(), window, cx);
13637        assert_eq!(editor.text(cx), "X\nbb");
13638        assert_eq!(
13639            editor.selections.ranges(cx),
13640            [Point::new(0, 1)..Point::new(0, 1)]
13641        );
13642    });
13643}
13644
13645#[gpui::test]
13646fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13647    init_test(cx, |_| {});
13648
13649    let markers = vec![('[', ']').into(), ('(', ')').into()];
13650    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13651        indoc! {"
13652            [aaaa
13653            (bbbb]
13654            cccc)",
13655        },
13656        markers.clone(),
13657    );
13658    let excerpt_ranges = markers.into_iter().map(|marker| {
13659        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13660        ExcerptRange::new(context.clone())
13661    });
13662    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13663    let multibuffer = cx.new(|cx| {
13664        let mut multibuffer = MultiBuffer::new(ReadWrite);
13665        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13666        multibuffer
13667    });
13668
13669    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13670    editor.update_in(cx, |editor, window, cx| {
13671        let (expected_text, selection_ranges) = marked_text_ranges(
13672            indoc! {"
13673                aaaa
13674                bˇbbb
13675                bˇbbˇb
13676                cccc"
13677            },
13678            true,
13679        );
13680        assert_eq!(editor.text(cx), expected_text);
13681        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13682            s.select_ranges(selection_ranges)
13683        });
13684
13685        editor.handle_input("X", window, cx);
13686
13687        let (expected_text, expected_selections) = marked_text_ranges(
13688            indoc! {"
13689                aaaa
13690                bXˇbbXb
13691                bXˇbbXˇb
13692                cccc"
13693            },
13694            false,
13695        );
13696        assert_eq!(editor.text(cx), expected_text);
13697        assert_eq!(editor.selections.ranges(cx), expected_selections);
13698
13699        editor.newline(&Newline, window, cx);
13700        let (expected_text, expected_selections) = marked_text_ranges(
13701            indoc! {"
13702                aaaa
13703                bX
13704                ˇbbX
13705                b
13706                bX
13707                ˇbbX
13708                ˇb
13709                cccc"
13710            },
13711            false,
13712        );
13713        assert_eq!(editor.text(cx), expected_text);
13714        assert_eq!(editor.selections.ranges(cx), expected_selections);
13715    });
13716}
13717
13718#[gpui::test]
13719fn test_refresh_selections(cx: &mut TestAppContext) {
13720    init_test(cx, |_| {});
13721
13722    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13723    let mut excerpt1_id = None;
13724    let multibuffer = cx.new(|cx| {
13725        let mut multibuffer = MultiBuffer::new(ReadWrite);
13726        excerpt1_id = multibuffer
13727            .push_excerpts(
13728                buffer.clone(),
13729                [
13730                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13731                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13732                ],
13733                cx,
13734            )
13735            .into_iter()
13736            .next();
13737        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13738        multibuffer
13739    });
13740
13741    let editor = cx.add_window(|window, cx| {
13742        let mut editor = build_editor(multibuffer.clone(), window, cx);
13743        let snapshot = editor.snapshot(window, cx);
13744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13745            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13746        });
13747        editor.begin_selection(
13748            Point::new(2, 1).to_display_point(&snapshot),
13749            true,
13750            1,
13751            window,
13752            cx,
13753        );
13754        assert_eq!(
13755            editor.selections.ranges(cx),
13756            [
13757                Point::new(1, 3)..Point::new(1, 3),
13758                Point::new(2, 1)..Point::new(2, 1),
13759            ]
13760        );
13761        editor
13762    });
13763
13764    // Refreshing selections is a no-op when excerpts haven't changed.
13765    _ = editor.update(cx, |editor, window, cx| {
13766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13767        assert_eq!(
13768            editor.selections.ranges(cx),
13769            [
13770                Point::new(1, 3)..Point::new(1, 3),
13771                Point::new(2, 1)..Point::new(2, 1),
13772            ]
13773        );
13774    });
13775
13776    multibuffer.update(cx, |multibuffer, cx| {
13777        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13778    });
13779    _ = editor.update(cx, |editor, window, cx| {
13780        // Removing an excerpt causes the first selection to become degenerate.
13781        assert_eq!(
13782            editor.selections.ranges(cx),
13783            [
13784                Point::new(0, 0)..Point::new(0, 0),
13785                Point::new(0, 1)..Point::new(0, 1)
13786            ]
13787        );
13788
13789        // Refreshing selections will relocate the first selection to the original buffer
13790        // location.
13791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13792        assert_eq!(
13793            editor.selections.ranges(cx),
13794            [
13795                Point::new(0, 1)..Point::new(0, 1),
13796                Point::new(0, 3)..Point::new(0, 3)
13797            ]
13798        );
13799        assert!(editor.selections.pending_anchor().is_some());
13800    });
13801}
13802
13803#[gpui::test]
13804fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13805    init_test(cx, |_| {});
13806
13807    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13808    let mut excerpt1_id = None;
13809    let multibuffer = cx.new(|cx| {
13810        let mut multibuffer = MultiBuffer::new(ReadWrite);
13811        excerpt1_id = multibuffer
13812            .push_excerpts(
13813                buffer.clone(),
13814                [
13815                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13816                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13817                ],
13818                cx,
13819            )
13820            .into_iter()
13821            .next();
13822        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13823        multibuffer
13824    });
13825
13826    let editor = cx.add_window(|window, cx| {
13827        let mut editor = build_editor(multibuffer.clone(), window, cx);
13828        let snapshot = editor.snapshot(window, cx);
13829        editor.begin_selection(
13830            Point::new(1, 3).to_display_point(&snapshot),
13831            false,
13832            1,
13833            window,
13834            cx,
13835        );
13836        assert_eq!(
13837            editor.selections.ranges(cx),
13838            [Point::new(1, 3)..Point::new(1, 3)]
13839        );
13840        editor
13841    });
13842
13843    multibuffer.update(cx, |multibuffer, cx| {
13844        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13845    });
13846    _ = editor.update(cx, |editor, window, cx| {
13847        assert_eq!(
13848            editor.selections.ranges(cx),
13849            [Point::new(0, 0)..Point::new(0, 0)]
13850        );
13851
13852        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13854        assert_eq!(
13855            editor.selections.ranges(cx),
13856            [Point::new(0, 3)..Point::new(0, 3)]
13857        );
13858        assert!(editor.selections.pending_anchor().is_some());
13859    });
13860}
13861
13862#[gpui::test]
13863async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13864    init_test(cx, |_| {});
13865
13866    let language = Arc::new(
13867        Language::new(
13868            LanguageConfig {
13869                brackets: BracketPairConfig {
13870                    pairs: vec![
13871                        BracketPair {
13872                            start: "{".to_string(),
13873                            end: "}".to_string(),
13874                            close: true,
13875                            surround: true,
13876                            newline: true,
13877                        },
13878                        BracketPair {
13879                            start: "/* ".to_string(),
13880                            end: " */".to_string(),
13881                            close: true,
13882                            surround: true,
13883                            newline: true,
13884                        },
13885                    ],
13886                    ..Default::default()
13887                },
13888                ..Default::default()
13889            },
13890            Some(tree_sitter_rust::LANGUAGE.into()),
13891        )
13892        .with_indents_query("")
13893        .unwrap(),
13894    );
13895
13896    let text = concat!(
13897        "{   }\n",     //
13898        "  x\n",       //
13899        "  /*   */\n", //
13900        "x\n",         //
13901        "{{} }\n",     //
13902    );
13903
13904    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13905    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13906    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13907    editor
13908        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13909        .await;
13910
13911    editor.update_in(cx, |editor, window, cx| {
13912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13913            s.select_display_ranges([
13914                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13915                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13916                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13917            ])
13918        });
13919        editor.newline(&Newline, window, cx);
13920
13921        assert_eq!(
13922            editor.buffer().read(cx).read(cx).text(),
13923            concat!(
13924                "{ \n",    // Suppress rustfmt
13925                "\n",      //
13926                "}\n",     //
13927                "  x\n",   //
13928                "  /* \n", //
13929                "  \n",    //
13930                "  */\n",  //
13931                "x\n",     //
13932                "{{} \n",  //
13933                "}\n",     //
13934            )
13935        );
13936    });
13937}
13938
13939#[gpui::test]
13940fn test_highlighted_ranges(cx: &mut TestAppContext) {
13941    init_test(cx, |_| {});
13942
13943    let editor = cx.add_window(|window, cx| {
13944        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13945        build_editor(buffer.clone(), window, cx)
13946    });
13947
13948    _ = editor.update(cx, |editor, window, cx| {
13949        struct Type1;
13950        struct Type2;
13951
13952        let buffer = editor.buffer.read(cx).snapshot(cx);
13953
13954        let anchor_range =
13955            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13956
13957        editor.highlight_background::<Type1>(
13958            &[
13959                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13960                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13961                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13962                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13963            ],
13964            |_| Hsla::red(),
13965            cx,
13966        );
13967        editor.highlight_background::<Type2>(
13968            &[
13969                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13970                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13971                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13972                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13973            ],
13974            |_| Hsla::green(),
13975            cx,
13976        );
13977
13978        let snapshot = editor.snapshot(window, cx);
13979        let mut highlighted_ranges = editor.background_highlights_in_range(
13980            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13981            &snapshot,
13982            cx.theme(),
13983        );
13984        // Enforce a consistent ordering based on color without relying on the ordering of the
13985        // highlight's `TypeId` which is non-executor.
13986        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13987        assert_eq!(
13988            highlighted_ranges,
13989            &[
13990                (
13991                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13992                    Hsla::red(),
13993                ),
13994                (
13995                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13996                    Hsla::red(),
13997                ),
13998                (
13999                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14000                    Hsla::green(),
14001                ),
14002                (
14003                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14004                    Hsla::green(),
14005                ),
14006            ]
14007        );
14008        assert_eq!(
14009            editor.background_highlights_in_range(
14010                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14011                &snapshot,
14012                cx.theme(),
14013            ),
14014            &[(
14015                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14016                Hsla::red(),
14017            )]
14018        );
14019    });
14020}
14021
14022#[gpui::test]
14023async fn test_following(cx: &mut TestAppContext) {
14024    init_test(cx, |_| {});
14025
14026    let fs = FakeFs::new(cx.executor());
14027    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14028
14029    let buffer = project.update(cx, |project, cx| {
14030        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14031        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14032    });
14033    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14034    let follower = cx.update(|cx| {
14035        cx.open_window(
14036            WindowOptions {
14037                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14038                    gpui::Point::new(px(0.), px(0.)),
14039                    gpui::Point::new(px(10.), px(80.)),
14040                ))),
14041                ..Default::default()
14042            },
14043            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14044        )
14045        .unwrap()
14046    });
14047
14048    let is_still_following = Rc::new(RefCell::new(true));
14049    let follower_edit_event_count = Rc::new(RefCell::new(0));
14050    let pending_update = Rc::new(RefCell::new(None));
14051    let leader_entity = leader.root(cx).unwrap();
14052    let follower_entity = follower.root(cx).unwrap();
14053    _ = follower.update(cx, {
14054        let update = pending_update.clone();
14055        let is_still_following = is_still_following.clone();
14056        let follower_edit_event_count = follower_edit_event_count.clone();
14057        |_, window, cx| {
14058            cx.subscribe_in(
14059                &leader_entity,
14060                window,
14061                move |_, leader, event, window, cx| {
14062                    leader.read(cx).add_event_to_update_proto(
14063                        event,
14064                        &mut update.borrow_mut(),
14065                        window,
14066                        cx,
14067                    );
14068                },
14069            )
14070            .detach();
14071
14072            cx.subscribe_in(
14073                &follower_entity,
14074                window,
14075                move |_, _, event: &EditorEvent, _window, _cx| {
14076                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14077                        *is_still_following.borrow_mut() = false;
14078                    }
14079
14080                    if let EditorEvent::BufferEdited = event {
14081                        *follower_edit_event_count.borrow_mut() += 1;
14082                    }
14083                },
14084            )
14085            .detach();
14086        }
14087    });
14088
14089    // Update the selections only
14090    _ = leader.update(cx, |leader, window, cx| {
14091        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14092            s.select_ranges([1..1])
14093        });
14094    });
14095    follower
14096        .update(cx, |follower, window, cx| {
14097            follower.apply_update_proto(
14098                &project,
14099                pending_update.borrow_mut().take().unwrap(),
14100                window,
14101                cx,
14102            )
14103        })
14104        .unwrap()
14105        .await
14106        .unwrap();
14107    _ = follower.update(cx, |follower, _, cx| {
14108        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14109    });
14110    assert!(*is_still_following.borrow());
14111    assert_eq!(*follower_edit_event_count.borrow(), 0);
14112
14113    // Update the scroll position only
14114    _ = leader.update(cx, |leader, window, cx| {
14115        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14116    });
14117    follower
14118        .update(cx, |follower, window, cx| {
14119            follower.apply_update_proto(
14120                &project,
14121                pending_update.borrow_mut().take().unwrap(),
14122                window,
14123                cx,
14124            )
14125        })
14126        .unwrap()
14127        .await
14128        .unwrap();
14129    assert_eq!(
14130        follower
14131            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14132            .unwrap(),
14133        gpui::Point::new(1.5, 3.5)
14134    );
14135    assert!(*is_still_following.borrow());
14136    assert_eq!(*follower_edit_event_count.borrow(), 0);
14137
14138    // Update the selections and scroll position. The follower's scroll position is updated
14139    // via autoscroll, not via the leader's exact scroll position.
14140    _ = leader.update(cx, |leader, window, cx| {
14141        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14142            s.select_ranges([0..0])
14143        });
14144        leader.request_autoscroll(Autoscroll::newest(), cx);
14145        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14146    });
14147    follower
14148        .update(cx, |follower, window, cx| {
14149            follower.apply_update_proto(
14150                &project,
14151                pending_update.borrow_mut().take().unwrap(),
14152                window,
14153                cx,
14154            )
14155        })
14156        .unwrap()
14157        .await
14158        .unwrap();
14159    _ = follower.update(cx, |follower, _, cx| {
14160        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14161        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14162    });
14163    assert!(*is_still_following.borrow());
14164
14165    // Creating a pending selection that precedes another selection
14166    _ = leader.update(cx, |leader, window, cx| {
14167        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14168            s.select_ranges([1..1])
14169        });
14170        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14171    });
14172    follower
14173        .update(cx, |follower, window, cx| {
14174            follower.apply_update_proto(
14175                &project,
14176                pending_update.borrow_mut().take().unwrap(),
14177                window,
14178                cx,
14179            )
14180        })
14181        .unwrap()
14182        .await
14183        .unwrap();
14184    _ = follower.update(cx, |follower, _, cx| {
14185        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14186    });
14187    assert!(*is_still_following.borrow());
14188
14189    // Extend the pending selection so that it surrounds another selection
14190    _ = leader.update(cx, |leader, window, cx| {
14191        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14192    });
14193    follower
14194        .update(cx, |follower, window, cx| {
14195            follower.apply_update_proto(
14196                &project,
14197                pending_update.borrow_mut().take().unwrap(),
14198                window,
14199                cx,
14200            )
14201        })
14202        .unwrap()
14203        .await
14204        .unwrap();
14205    _ = follower.update(cx, |follower, _, cx| {
14206        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14207    });
14208
14209    // Scrolling locally breaks the follow
14210    _ = follower.update(cx, |follower, window, cx| {
14211        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14212        follower.set_scroll_anchor(
14213            ScrollAnchor {
14214                anchor: top_anchor,
14215                offset: gpui::Point::new(0.0, 0.5),
14216            },
14217            window,
14218            cx,
14219        );
14220    });
14221    assert!(!(*is_still_following.borrow()));
14222}
14223
14224#[gpui::test]
14225async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14226    init_test(cx, |_| {});
14227
14228    let fs = FakeFs::new(cx.executor());
14229    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14230    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14231    let pane = workspace
14232        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14233        .unwrap();
14234
14235    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14236
14237    let leader = pane.update_in(cx, |_, window, cx| {
14238        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14239        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14240    });
14241
14242    // Start following the editor when it has no excerpts.
14243    let mut state_message =
14244        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14245    let workspace_entity = workspace.root(cx).unwrap();
14246    let follower_1 = cx
14247        .update_window(*workspace.deref(), |_, window, cx| {
14248            Editor::from_state_proto(
14249                workspace_entity,
14250                ViewId {
14251                    creator: CollaboratorId::PeerId(PeerId::default()),
14252                    id: 0,
14253                },
14254                &mut state_message,
14255                window,
14256                cx,
14257            )
14258        })
14259        .unwrap()
14260        .unwrap()
14261        .await
14262        .unwrap();
14263
14264    let update_message = Rc::new(RefCell::new(None));
14265    follower_1.update_in(cx, {
14266        let update = update_message.clone();
14267        |_, window, cx| {
14268            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14269                leader.read(cx).add_event_to_update_proto(
14270                    event,
14271                    &mut update.borrow_mut(),
14272                    window,
14273                    cx,
14274                );
14275            })
14276            .detach();
14277        }
14278    });
14279
14280    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14281        (
14282            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14283            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14284        )
14285    });
14286
14287    // Insert some excerpts.
14288    leader.update(cx, |leader, cx| {
14289        leader.buffer.update(cx, |multibuffer, cx| {
14290            multibuffer.set_excerpts_for_path(
14291                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14292                buffer_1.clone(),
14293                vec![
14294                    Point::row_range(0..3),
14295                    Point::row_range(1..6),
14296                    Point::row_range(12..15),
14297                ],
14298                0,
14299                cx,
14300            );
14301            multibuffer.set_excerpts_for_path(
14302                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14303                buffer_2.clone(),
14304                vec![Point::row_range(0..6), Point::row_range(8..12)],
14305                0,
14306                cx,
14307            );
14308        });
14309    });
14310
14311    // Apply the update of adding the excerpts.
14312    follower_1
14313        .update_in(cx, |follower, window, cx| {
14314            follower.apply_update_proto(
14315                &project,
14316                update_message.borrow().clone().unwrap(),
14317                window,
14318                cx,
14319            )
14320        })
14321        .await
14322        .unwrap();
14323    assert_eq!(
14324        follower_1.update(cx, |editor, cx| editor.text(cx)),
14325        leader.update(cx, |editor, cx| editor.text(cx))
14326    );
14327    update_message.borrow_mut().take();
14328
14329    // Start following separately after it already has excerpts.
14330    let mut state_message =
14331        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14332    let workspace_entity = workspace.root(cx).unwrap();
14333    let follower_2 = cx
14334        .update_window(*workspace.deref(), |_, window, cx| {
14335            Editor::from_state_proto(
14336                workspace_entity,
14337                ViewId {
14338                    creator: CollaboratorId::PeerId(PeerId::default()),
14339                    id: 0,
14340                },
14341                &mut state_message,
14342                window,
14343                cx,
14344            )
14345        })
14346        .unwrap()
14347        .unwrap()
14348        .await
14349        .unwrap();
14350    assert_eq!(
14351        follower_2.update(cx, |editor, cx| editor.text(cx)),
14352        leader.update(cx, |editor, cx| editor.text(cx))
14353    );
14354
14355    // Remove some excerpts.
14356    leader.update(cx, |leader, cx| {
14357        leader.buffer.update(cx, |multibuffer, cx| {
14358            let excerpt_ids = multibuffer.excerpt_ids();
14359            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14360            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14361        });
14362    });
14363
14364    // Apply the update of removing the excerpts.
14365    follower_1
14366        .update_in(cx, |follower, window, cx| {
14367            follower.apply_update_proto(
14368                &project,
14369                update_message.borrow().clone().unwrap(),
14370                window,
14371                cx,
14372            )
14373        })
14374        .await
14375        .unwrap();
14376    follower_2
14377        .update_in(cx, |follower, window, cx| {
14378            follower.apply_update_proto(
14379                &project,
14380                update_message.borrow().clone().unwrap(),
14381                window,
14382                cx,
14383            )
14384        })
14385        .await
14386        .unwrap();
14387    update_message.borrow_mut().take();
14388    assert_eq!(
14389        follower_1.update(cx, |editor, cx| editor.text(cx)),
14390        leader.update(cx, |editor, cx| editor.text(cx))
14391    );
14392}
14393
14394#[gpui::test]
14395async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14396    init_test(cx, |_| {});
14397
14398    let mut cx = EditorTestContext::new(cx).await;
14399    let lsp_store =
14400        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14401
14402    cx.set_state(indoc! {"
14403        ˇfn func(abc def: i32) -> u32 {
14404        }
14405    "});
14406
14407    cx.update(|_, cx| {
14408        lsp_store.update(cx, |lsp_store, cx| {
14409            lsp_store
14410                .update_diagnostics(
14411                    LanguageServerId(0),
14412                    lsp::PublishDiagnosticsParams {
14413                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14414                        version: None,
14415                        diagnostics: vec![
14416                            lsp::Diagnostic {
14417                                range: lsp::Range::new(
14418                                    lsp::Position::new(0, 11),
14419                                    lsp::Position::new(0, 12),
14420                                ),
14421                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14422                                ..Default::default()
14423                            },
14424                            lsp::Diagnostic {
14425                                range: lsp::Range::new(
14426                                    lsp::Position::new(0, 12),
14427                                    lsp::Position::new(0, 15),
14428                                ),
14429                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14430                                ..Default::default()
14431                            },
14432                            lsp::Diagnostic {
14433                                range: lsp::Range::new(
14434                                    lsp::Position::new(0, 25),
14435                                    lsp::Position::new(0, 28),
14436                                ),
14437                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14438                                ..Default::default()
14439                            },
14440                        ],
14441                    },
14442                    None,
14443                    DiagnosticSourceKind::Pushed,
14444                    &[],
14445                    cx,
14446                )
14447                .unwrap()
14448        });
14449    });
14450
14451    executor.run_until_parked();
14452
14453    cx.update_editor(|editor, window, cx| {
14454        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14455    });
14456
14457    cx.assert_editor_state(indoc! {"
14458        fn func(abc def: i32) -> ˇu32 {
14459        }
14460    "});
14461
14462    cx.update_editor(|editor, window, cx| {
14463        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14464    });
14465
14466    cx.assert_editor_state(indoc! {"
14467        fn func(abc ˇdef: i32) -> u32 {
14468        }
14469    "});
14470
14471    cx.update_editor(|editor, window, cx| {
14472        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14473    });
14474
14475    cx.assert_editor_state(indoc! {"
14476        fn func(abcˇ def: i32) -> u32 {
14477        }
14478    "});
14479
14480    cx.update_editor(|editor, window, cx| {
14481        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14482    });
14483
14484    cx.assert_editor_state(indoc! {"
14485        fn func(abc def: i32) -> ˇu32 {
14486        }
14487    "});
14488}
14489
14490#[gpui::test]
14491async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14492    init_test(cx, |_| {});
14493
14494    let mut cx = EditorTestContext::new(cx).await;
14495
14496    let diff_base = r#"
14497        use some::mod;
14498
14499        const A: u32 = 42;
14500
14501        fn main() {
14502            println!("hello");
14503
14504            println!("world");
14505        }
14506        "#
14507    .unindent();
14508
14509    // Edits are modified, removed, modified, added
14510    cx.set_state(
14511        &r#"
14512        use some::modified;
14513
14514        ˇ
14515        fn main() {
14516            println!("hello there");
14517
14518            println!("around the");
14519            println!("world");
14520        }
14521        "#
14522        .unindent(),
14523    );
14524
14525    cx.set_head_text(&diff_base);
14526    executor.run_until_parked();
14527
14528    cx.update_editor(|editor, window, cx| {
14529        //Wrap around the bottom of the buffer
14530        for _ in 0..3 {
14531            editor.go_to_next_hunk(&GoToHunk, window, cx);
14532        }
14533    });
14534
14535    cx.assert_editor_state(
14536        &r#"
14537        ˇuse some::modified;
14538
14539
14540        fn main() {
14541            println!("hello there");
14542
14543            println!("around the");
14544            println!("world");
14545        }
14546        "#
14547        .unindent(),
14548    );
14549
14550    cx.update_editor(|editor, window, cx| {
14551        //Wrap around the top of the buffer
14552        for _ in 0..2 {
14553            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14554        }
14555    });
14556
14557    cx.assert_editor_state(
14558        &r#"
14559        use some::modified;
14560
14561
14562        fn main() {
14563        ˇ    println!("hello there");
14564
14565            println!("around the");
14566            println!("world");
14567        }
14568        "#
14569        .unindent(),
14570    );
14571
14572    cx.update_editor(|editor, window, cx| {
14573        editor.go_to_prev_hunk(&GoToPreviousHunk, 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    cx.update_editor(|editor, window, cx| {
14592        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14593    });
14594
14595    cx.assert_editor_state(
14596        &r#"
14597        ˇuse some::modified;
14598
14599
14600        fn main() {
14601            println!("hello there");
14602
14603            println!("around the");
14604            println!("world");
14605        }
14606        "#
14607        .unindent(),
14608    );
14609
14610    cx.update_editor(|editor, window, cx| {
14611        for _ in 0..2 {
14612            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14613        }
14614    });
14615
14616    cx.assert_editor_state(
14617        &r#"
14618        use some::modified;
14619
14620
14621        fn main() {
14622        ˇ    println!("hello there");
14623
14624            println!("around the");
14625            println!("world");
14626        }
14627        "#
14628        .unindent(),
14629    );
14630
14631    cx.update_editor(|editor, window, cx| {
14632        editor.fold(&Fold, window, cx);
14633    });
14634
14635    cx.update_editor(|editor, window, cx| {
14636        editor.go_to_next_hunk(&GoToHunk, window, cx);
14637    });
14638
14639    cx.assert_editor_state(
14640        &r#"
14641        ˇuse some::modified;
14642
14643
14644        fn main() {
14645            println!("hello there");
14646
14647            println!("around the");
14648            println!("world");
14649        }
14650        "#
14651        .unindent(),
14652    );
14653}
14654
14655#[test]
14656fn test_split_words() {
14657    fn split(text: &str) -> Vec<&str> {
14658        split_words(text).collect()
14659    }
14660
14661    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14662    assert_eq!(split("hello_world"), &["hello_", "world"]);
14663    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14664    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14665    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14666    assert_eq!(split("helloworld"), &["helloworld"]);
14667
14668    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14669}
14670
14671#[gpui::test]
14672async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14673    init_test(cx, |_| {});
14674
14675    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14676    let mut assert = |before, after| {
14677        let _state_context = cx.set_state(before);
14678        cx.run_until_parked();
14679        cx.update_editor(|editor, window, cx| {
14680            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14681        });
14682        cx.run_until_parked();
14683        cx.assert_editor_state(after);
14684    };
14685
14686    // Outside bracket jumps to outside of matching bracket
14687    assert("console.logˇ(var);", "console.log(var)ˇ;");
14688    assert("console.log(var)ˇ;", "console.logˇ(var);");
14689
14690    // Inside bracket jumps to inside of matching bracket
14691    assert("console.log(ˇvar);", "console.log(varˇ);");
14692    assert("console.log(varˇ);", "console.log(ˇvar);");
14693
14694    // When outside a bracket and inside, favor jumping to the inside bracket
14695    assert(
14696        "console.log('foo', [1, 2, 3]ˇ);",
14697        "console.log(ˇ'foo', [1, 2, 3]);",
14698    );
14699    assert(
14700        "console.log(ˇ'foo', [1, 2, 3]);",
14701        "console.log('foo', [1, 2, 3]ˇ);",
14702    );
14703
14704    // Bias forward if two options are equally likely
14705    assert(
14706        "let result = curried_fun()ˇ();",
14707        "let result = curried_fun()()ˇ;",
14708    );
14709
14710    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14711    assert(
14712        indoc! {"
14713            function test() {
14714                console.log('test')ˇ
14715            }"},
14716        indoc! {"
14717            function test() {
14718                console.logˇ('test')
14719            }"},
14720    );
14721}
14722
14723#[gpui::test]
14724async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14725    init_test(cx, |_| {});
14726
14727    let fs = FakeFs::new(cx.executor());
14728    fs.insert_tree(
14729        path!("/a"),
14730        json!({
14731            "main.rs": "fn main() { let a = 5; }",
14732            "other.rs": "// Test file",
14733        }),
14734    )
14735    .await;
14736    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14737
14738    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14739    language_registry.add(Arc::new(Language::new(
14740        LanguageConfig {
14741            name: "Rust".into(),
14742            matcher: LanguageMatcher {
14743                path_suffixes: vec!["rs".to_string()],
14744                ..Default::default()
14745            },
14746            brackets: BracketPairConfig {
14747                pairs: vec![BracketPair {
14748                    start: "{".to_string(),
14749                    end: "}".to_string(),
14750                    close: true,
14751                    surround: true,
14752                    newline: true,
14753                }],
14754                disabled_scopes_by_bracket_ix: Vec::new(),
14755            },
14756            ..Default::default()
14757        },
14758        Some(tree_sitter_rust::LANGUAGE.into()),
14759    )));
14760    let mut fake_servers = language_registry.register_fake_lsp(
14761        "Rust",
14762        FakeLspAdapter {
14763            capabilities: lsp::ServerCapabilities {
14764                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14765                    first_trigger_character: "{".to_string(),
14766                    more_trigger_character: None,
14767                }),
14768                ..Default::default()
14769            },
14770            ..Default::default()
14771        },
14772    );
14773
14774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14775
14776    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14777
14778    let worktree_id = workspace
14779        .update(cx, |workspace, _, cx| {
14780            workspace.project().update(cx, |project, cx| {
14781                project.worktrees(cx).next().unwrap().read(cx).id()
14782            })
14783        })
14784        .unwrap();
14785
14786    let buffer = project
14787        .update(cx, |project, cx| {
14788            project.open_local_buffer(path!("/a/main.rs"), cx)
14789        })
14790        .await
14791        .unwrap();
14792    let editor_handle = workspace
14793        .update(cx, |workspace, window, cx| {
14794            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14795        })
14796        .unwrap()
14797        .await
14798        .unwrap()
14799        .downcast::<Editor>()
14800        .unwrap();
14801
14802    cx.executor().start_waiting();
14803    let fake_server = fake_servers.next().await.unwrap();
14804
14805    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14806        |params, _| async move {
14807            assert_eq!(
14808                params.text_document_position.text_document.uri,
14809                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14810            );
14811            assert_eq!(
14812                params.text_document_position.position,
14813                lsp::Position::new(0, 21),
14814            );
14815
14816            Ok(Some(vec![lsp::TextEdit {
14817                new_text: "]".to_string(),
14818                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14819            }]))
14820        },
14821    );
14822
14823    editor_handle.update_in(cx, |editor, window, cx| {
14824        window.focus(&editor.focus_handle(cx));
14825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14826            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14827        });
14828        editor.handle_input("{", window, cx);
14829    });
14830
14831    cx.executor().run_until_parked();
14832
14833    buffer.update(cx, |buffer, _| {
14834        assert_eq!(
14835            buffer.text(),
14836            "fn main() { let a = {5}; }",
14837            "No extra braces from on type formatting should appear in the buffer"
14838        )
14839    });
14840}
14841
14842#[gpui::test(iterations = 20, seeds(31))]
14843async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14844    init_test(cx, |_| {});
14845
14846    let mut cx = EditorLspTestContext::new_rust(
14847        lsp::ServerCapabilities {
14848            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14849                first_trigger_character: ".".to_string(),
14850                more_trigger_character: None,
14851            }),
14852            ..Default::default()
14853        },
14854        cx,
14855    )
14856    .await;
14857
14858    cx.update_buffer(|buffer, _| {
14859        // This causes autoindent to be async.
14860        buffer.set_sync_parse_timeout(Duration::ZERO)
14861    });
14862
14863    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14864    cx.simulate_keystroke("\n");
14865    cx.run_until_parked();
14866
14867    let buffer_cloned =
14868        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14869    let mut request =
14870        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14871            let buffer_cloned = buffer_cloned.clone();
14872            async move {
14873                buffer_cloned.update(&mut cx, |buffer, _| {
14874                    assert_eq!(
14875                        buffer.text(),
14876                        "fn c() {\n    d()\n        .\n}\n",
14877                        "OnTypeFormatting should triggered after autoindent applied"
14878                    )
14879                })?;
14880
14881                Ok(Some(vec![]))
14882            }
14883        });
14884
14885    cx.simulate_keystroke(".");
14886    cx.run_until_parked();
14887
14888    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14889    assert!(request.next().await.is_some());
14890    request.close();
14891    assert!(request.next().await.is_none());
14892}
14893
14894#[gpui::test]
14895async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14896    init_test(cx, |_| {});
14897
14898    let fs = FakeFs::new(cx.executor());
14899    fs.insert_tree(
14900        path!("/a"),
14901        json!({
14902            "main.rs": "fn main() { let a = 5; }",
14903            "other.rs": "// Test file",
14904        }),
14905    )
14906    .await;
14907
14908    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14909
14910    let server_restarts = Arc::new(AtomicUsize::new(0));
14911    let closure_restarts = Arc::clone(&server_restarts);
14912    let language_server_name = "test language server";
14913    let language_name: LanguageName = "Rust".into();
14914
14915    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14916    language_registry.add(Arc::new(Language::new(
14917        LanguageConfig {
14918            name: language_name.clone(),
14919            matcher: LanguageMatcher {
14920                path_suffixes: vec!["rs".to_string()],
14921                ..Default::default()
14922            },
14923            ..Default::default()
14924        },
14925        Some(tree_sitter_rust::LANGUAGE.into()),
14926    )));
14927    let mut fake_servers = language_registry.register_fake_lsp(
14928        "Rust",
14929        FakeLspAdapter {
14930            name: language_server_name,
14931            initialization_options: Some(json!({
14932                "testOptionValue": true
14933            })),
14934            initializer: Some(Box::new(move |fake_server| {
14935                let task_restarts = Arc::clone(&closure_restarts);
14936                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14937                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14938                    futures::future::ready(Ok(()))
14939                });
14940            })),
14941            ..Default::default()
14942        },
14943    );
14944
14945    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14946    let _buffer = project
14947        .update(cx, |project, cx| {
14948            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14949        })
14950        .await
14951        .unwrap();
14952    let _fake_server = fake_servers.next().await.unwrap();
14953    update_test_language_settings(cx, |language_settings| {
14954        language_settings.languages.insert(
14955            language_name.clone(),
14956            LanguageSettingsContent {
14957                tab_size: NonZeroU32::new(8),
14958                ..Default::default()
14959            },
14960        );
14961    });
14962    cx.executor().run_until_parked();
14963    assert_eq!(
14964        server_restarts.load(atomic::Ordering::Acquire),
14965        0,
14966        "Should not restart LSP server on an unrelated change"
14967    );
14968
14969    update_test_project_settings(cx, |project_settings| {
14970        project_settings.lsp.insert(
14971            "Some other server name".into(),
14972            LspSettings {
14973                binary: None,
14974                settings: None,
14975                initialization_options: Some(json!({
14976                    "some other init value": false
14977                })),
14978                enable_lsp_tasks: false,
14979            },
14980        );
14981    });
14982    cx.executor().run_until_parked();
14983    assert_eq!(
14984        server_restarts.load(atomic::Ordering::Acquire),
14985        0,
14986        "Should not restart LSP server on an unrelated LSP settings change"
14987    );
14988
14989    update_test_project_settings(cx, |project_settings| {
14990        project_settings.lsp.insert(
14991            language_server_name.into(),
14992            LspSettings {
14993                binary: None,
14994                settings: None,
14995                initialization_options: Some(json!({
14996                    "anotherInitValue": false
14997                })),
14998                enable_lsp_tasks: false,
14999            },
15000        );
15001    });
15002    cx.executor().run_until_parked();
15003    assert_eq!(
15004        server_restarts.load(atomic::Ordering::Acquire),
15005        1,
15006        "Should restart LSP server on a related LSP settings change"
15007    );
15008
15009    update_test_project_settings(cx, |project_settings| {
15010        project_settings.lsp.insert(
15011            language_server_name.into(),
15012            LspSettings {
15013                binary: None,
15014                settings: None,
15015                initialization_options: Some(json!({
15016                    "anotherInitValue": false
15017                })),
15018                enable_lsp_tasks: false,
15019            },
15020        );
15021    });
15022    cx.executor().run_until_parked();
15023    assert_eq!(
15024        server_restarts.load(atomic::Ordering::Acquire),
15025        1,
15026        "Should not restart LSP server on a related LSP settings change that is the same"
15027    );
15028
15029    update_test_project_settings(cx, |project_settings| {
15030        project_settings.lsp.insert(
15031            language_server_name.into(),
15032            LspSettings {
15033                binary: None,
15034                settings: None,
15035                initialization_options: None,
15036                enable_lsp_tasks: false,
15037            },
15038        );
15039    });
15040    cx.executor().run_until_parked();
15041    assert_eq!(
15042        server_restarts.load(atomic::Ordering::Acquire),
15043        2,
15044        "Should restart LSP server on another related LSP settings change"
15045    );
15046}
15047
15048#[gpui::test]
15049async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15050    init_test(cx, |_| {});
15051
15052    let mut cx = EditorLspTestContext::new_rust(
15053        lsp::ServerCapabilities {
15054            completion_provider: Some(lsp::CompletionOptions {
15055                trigger_characters: Some(vec![".".to_string()]),
15056                resolve_provider: Some(true),
15057                ..Default::default()
15058            }),
15059            ..Default::default()
15060        },
15061        cx,
15062    )
15063    .await;
15064
15065    cx.set_state("fn main() { let a = 2ˇ; }");
15066    cx.simulate_keystroke(".");
15067    let completion_item = lsp::CompletionItem {
15068        label: "some".into(),
15069        kind: Some(lsp::CompletionItemKind::SNIPPET),
15070        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15071        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15072            kind: lsp::MarkupKind::Markdown,
15073            value: "```rust\nSome(2)\n```".to_string(),
15074        })),
15075        deprecated: Some(false),
15076        sort_text: Some("fffffff2".to_string()),
15077        filter_text: Some("some".to_string()),
15078        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15079        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15080            range: lsp::Range {
15081                start: lsp::Position {
15082                    line: 0,
15083                    character: 22,
15084                },
15085                end: lsp::Position {
15086                    line: 0,
15087                    character: 22,
15088                },
15089            },
15090            new_text: "Some(2)".to_string(),
15091        })),
15092        additional_text_edits: Some(vec![lsp::TextEdit {
15093            range: lsp::Range {
15094                start: lsp::Position {
15095                    line: 0,
15096                    character: 20,
15097                },
15098                end: lsp::Position {
15099                    line: 0,
15100                    character: 22,
15101                },
15102            },
15103            new_text: "".to_string(),
15104        }]),
15105        ..Default::default()
15106    };
15107
15108    let closure_completion_item = completion_item.clone();
15109    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15110        let task_completion_item = closure_completion_item.clone();
15111        async move {
15112            Ok(Some(lsp::CompletionResponse::Array(vec![
15113                task_completion_item,
15114            ])))
15115        }
15116    });
15117
15118    request.next().await;
15119
15120    cx.condition(|editor, _| editor.context_menu_visible())
15121        .await;
15122    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15123        editor
15124            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15125            .unwrap()
15126    });
15127    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15128
15129    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15130        let task_completion_item = completion_item.clone();
15131        async move { Ok(task_completion_item) }
15132    })
15133    .next()
15134    .await
15135    .unwrap();
15136    apply_additional_edits.await.unwrap();
15137    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15138}
15139
15140#[gpui::test]
15141async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15142    init_test(cx, |_| {});
15143
15144    let mut cx = EditorLspTestContext::new_rust(
15145        lsp::ServerCapabilities {
15146            completion_provider: Some(lsp::CompletionOptions {
15147                trigger_characters: Some(vec![".".to_string()]),
15148                resolve_provider: Some(true),
15149                ..Default::default()
15150            }),
15151            ..Default::default()
15152        },
15153        cx,
15154    )
15155    .await;
15156
15157    cx.set_state("fn main() { let a = 2ˇ; }");
15158    cx.simulate_keystroke(".");
15159
15160    let item1 = lsp::CompletionItem {
15161        label: "method id()".to_string(),
15162        filter_text: Some("id".to_string()),
15163        detail: None,
15164        documentation: None,
15165        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15166            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15167            new_text: ".id".to_string(),
15168        })),
15169        ..lsp::CompletionItem::default()
15170    };
15171
15172    let item2 = lsp::CompletionItem {
15173        label: "other".to_string(),
15174        filter_text: Some("other".to_string()),
15175        detail: None,
15176        documentation: None,
15177        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15178            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15179            new_text: ".other".to_string(),
15180        })),
15181        ..lsp::CompletionItem::default()
15182    };
15183
15184    let item1 = item1.clone();
15185    cx.set_request_handler::<lsp::request::Completion, _, _>({
15186        let item1 = item1.clone();
15187        move |_, _, _| {
15188            let item1 = item1.clone();
15189            let item2 = item2.clone();
15190            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15191        }
15192    })
15193    .next()
15194    .await;
15195
15196    cx.condition(|editor, _| editor.context_menu_visible())
15197        .await;
15198    cx.update_editor(|editor, _, _| {
15199        let context_menu = editor.context_menu.borrow_mut();
15200        let context_menu = context_menu
15201            .as_ref()
15202            .expect("Should have the context menu deployed");
15203        match context_menu {
15204            CodeContextMenu::Completions(completions_menu) => {
15205                let completions = completions_menu.completions.borrow_mut();
15206                assert_eq!(
15207                    completions
15208                        .iter()
15209                        .map(|completion| &completion.label.text)
15210                        .collect::<Vec<_>>(),
15211                    vec!["method id()", "other"]
15212                )
15213            }
15214            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15215        }
15216    });
15217
15218    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15219        let item1 = item1.clone();
15220        move |_, item_to_resolve, _| {
15221            let item1 = item1.clone();
15222            async move {
15223                if item1 == item_to_resolve {
15224                    Ok(lsp::CompletionItem {
15225                        label: "method id()".to_string(),
15226                        filter_text: Some("id".to_string()),
15227                        detail: Some("Now resolved!".to_string()),
15228                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15229                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15230                            range: lsp::Range::new(
15231                                lsp::Position::new(0, 22),
15232                                lsp::Position::new(0, 22),
15233                            ),
15234                            new_text: ".id".to_string(),
15235                        })),
15236                        ..lsp::CompletionItem::default()
15237                    })
15238                } else {
15239                    Ok(item_to_resolve)
15240                }
15241            }
15242        }
15243    })
15244    .next()
15245    .await
15246    .unwrap();
15247    cx.run_until_parked();
15248
15249    cx.update_editor(|editor, window, cx| {
15250        editor.context_menu_next(&Default::default(), window, cx);
15251    });
15252
15253    cx.update_editor(|editor, _, _| {
15254        let context_menu = editor.context_menu.borrow_mut();
15255        let context_menu = context_menu
15256            .as_ref()
15257            .expect("Should have the context menu deployed");
15258        match context_menu {
15259            CodeContextMenu::Completions(completions_menu) => {
15260                let completions = completions_menu.completions.borrow_mut();
15261                assert_eq!(
15262                    completions
15263                        .iter()
15264                        .map(|completion| &completion.label.text)
15265                        .collect::<Vec<_>>(),
15266                    vec!["method id() Now resolved!", "other"],
15267                    "Should update first completion label, but not second as the filter text did not match."
15268                );
15269            }
15270            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15271        }
15272    });
15273}
15274
15275#[gpui::test]
15276async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15277    init_test(cx, |_| {});
15278    let mut cx = EditorLspTestContext::new_rust(
15279        lsp::ServerCapabilities {
15280            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15281            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15282            completion_provider: Some(lsp::CompletionOptions {
15283                resolve_provider: Some(true),
15284                ..Default::default()
15285            }),
15286            ..Default::default()
15287        },
15288        cx,
15289    )
15290    .await;
15291    cx.set_state(indoc! {"
15292        struct TestStruct {
15293            field: i32
15294        }
15295
15296        fn mainˇ() {
15297            let unused_var = 42;
15298            let test_struct = TestStruct { field: 42 };
15299        }
15300    "});
15301    let symbol_range = cx.lsp_range(indoc! {"
15302        struct TestStruct {
15303            field: i32
15304        }
15305
15306        «fn main»() {
15307            let unused_var = 42;
15308            let test_struct = TestStruct { field: 42 };
15309        }
15310    "});
15311    let mut hover_requests =
15312        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15313            Ok(Some(lsp::Hover {
15314                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15315                    kind: lsp::MarkupKind::Markdown,
15316                    value: "Function documentation".to_string(),
15317                }),
15318                range: Some(symbol_range),
15319            }))
15320        });
15321
15322    // Case 1: Test that code action menu hide hover popover
15323    cx.dispatch_action(Hover);
15324    hover_requests.next().await;
15325    cx.condition(|editor, _| editor.hover_state.visible()).await;
15326    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15327        move |_, _, _| async move {
15328            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15329                lsp::CodeAction {
15330                    title: "Remove unused variable".to_string(),
15331                    kind: Some(CodeActionKind::QUICKFIX),
15332                    edit: Some(lsp::WorkspaceEdit {
15333                        changes: Some(
15334                            [(
15335                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15336                                vec![lsp::TextEdit {
15337                                    range: lsp::Range::new(
15338                                        lsp::Position::new(5, 4),
15339                                        lsp::Position::new(5, 27),
15340                                    ),
15341                                    new_text: "".to_string(),
15342                                }],
15343                            )]
15344                            .into_iter()
15345                            .collect(),
15346                        ),
15347                        ..Default::default()
15348                    }),
15349                    ..Default::default()
15350                },
15351            )]))
15352        },
15353    );
15354    cx.update_editor(|editor, window, cx| {
15355        editor.toggle_code_actions(
15356            &ToggleCodeActions {
15357                deployed_from: None,
15358                quick_launch: false,
15359            },
15360            window,
15361            cx,
15362        );
15363    });
15364    code_action_requests.next().await;
15365    cx.run_until_parked();
15366    cx.condition(|editor, _| editor.context_menu_visible())
15367        .await;
15368    cx.update_editor(|editor, _, _| {
15369        assert!(
15370            !editor.hover_state.visible(),
15371            "Hover popover should be hidden when code action menu is shown"
15372        );
15373        // Hide code actions
15374        editor.context_menu.take();
15375    });
15376
15377    // Case 2: Test that code completions hide hover popover
15378    cx.dispatch_action(Hover);
15379    hover_requests.next().await;
15380    cx.condition(|editor, _| editor.hover_state.visible()).await;
15381    let counter = Arc::new(AtomicUsize::new(0));
15382    let mut completion_requests =
15383        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15384            let counter = counter.clone();
15385            async move {
15386                counter.fetch_add(1, atomic::Ordering::Release);
15387                Ok(Some(lsp::CompletionResponse::Array(vec![
15388                    lsp::CompletionItem {
15389                        label: "main".into(),
15390                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15391                        detail: Some("() -> ()".to_string()),
15392                        ..Default::default()
15393                    },
15394                    lsp::CompletionItem {
15395                        label: "TestStruct".into(),
15396                        kind: Some(lsp::CompletionItemKind::STRUCT),
15397                        detail: Some("struct TestStruct".to_string()),
15398                        ..Default::default()
15399                    },
15400                ])))
15401            }
15402        });
15403    cx.update_editor(|editor, window, cx| {
15404        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15405    });
15406    completion_requests.next().await;
15407    cx.condition(|editor, _| editor.context_menu_visible())
15408        .await;
15409    cx.update_editor(|editor, _, _| {
15410        assert!(
15411            !editor.hover_state.visible(),
15412            "Hover popover should be hidden when completion menu is shown"
15413        );
15414    });
15415}
15416
15417#[gpui::test]
15418async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15419    init_test(cx, |_| {});
15420
15421    let mut cx = EditorLspTestContext::new_rust(
15422        lsp::ServerCapabilities {
15423            completion_provider: Some(lsp::CompletionOptions {
15424                trigger_characters: Some(vec![".".to_string()]),
15425                resolve_provider: Some(true),
15426                ..Default::default()
15427            }),
15428            ..Default::default()
15429        },
15430        cx,
15431    )
15432    .await;
15433
15434    cx.set_state("fn main() { let a = 2ˇ; }");
15435    cx.simulate_keystroke(".");
15436
15437    let unresolved_item_1 = lsp::CompletionItem {
15438        label: "id".to_string(),
15439        filter_text: Some("id".to_string()),
15440        detail: None,
15441        documentation: None,
15442        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15443            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15444            new_text: ".id".to_string(),
15445        })),
15446        ..lsp::CompletionItem::default()
15447    };
15448    let resolved_item_1 = lsp::CompletionItem {
15449        additional_text_edits: Some(vec![lsp::TextEdit {
15450            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15451            new_text: "!!".to_string(),
15452        }]),
15453        ..unresolved_item_1.clone()
15454    };
15455    let unresolved_item_2 = lsp::CompletionItem {
15456        label: "other".to_string(),
15457        filter_text: Some("other".to_string()),
15458        detail: None,
15459        documentation: None,
15460        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15461            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15462            new_text: ".other".to_string(),
15463        })),
15464        ..lsp::CompletionItem::default()
15465    };
15466    let resolved_item_2 = lsp::CompletionItem {
15467        additional_text_edits: Some(vec![lsp::TextEdit {
15468            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15469            new_text: "??".to_string(),
15470        }]),
15471        ..unresolved_item_2.clone()
15472    };
15473
15474    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15475    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15476    cx.lsp
15477        .server
15478        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15479            let unresolved_item_1 = unresolved_item_1.clone();
15480            let resolved_item_1 = resolved_item_1.clone();
15481            let unresolved_item_2 = unresolved_item_2.clone();
15482            let resolved_item_2 = resolved_item_2.clone();
15483            let resolve_requests_1 = resolve_requests_1.clone();
15484            let resolve_requests_2 = resolve_requests_2.clone();
15485            move |unresolved_request, _| {
15486                let unresolved_item_1 = unresolved_item_1.clone();
15487                let resolved_item_1 = resolved_item_1.clone();
15488                let unresolved_item_2 = unresolved_item_2.clone();
15489                let resolved_item_2 = resolved_item_2.clone();
15490                let resolve_requests_1 = resolve_requests_1.clone();
15491                let resolve_requests_2 = resolve_requests_2.clone();
15492                async move {
15493                    if unresolved_request == unresolved_item_1 {
15494                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15495                        Ok(resolved_item_1.clone())
15496                    } else if unresolved_request == unresolved_item_2 {
15497                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15498                        Ok(resolved_item_2.clone())
15499                    } else {
15500                        panic!("Unexpected completion item {unresolved_request:?}")
15501                    }
15502                }
15503            }
15504        })
15505        .detach();
15506
15507    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15508        let unresolved_item_1 = unresolved_item_1.clone();
15509        let unresolved_item_2 = unresolved_item_2.clone();
15510        async move {
15511            Ok(Some(lsp::CompletionResponse::Array(vec![
15512                unresolved_item_1,
15513                unresolved_item_2,
15514            ])))
15515        }
15516    })
15517    .next()
15518    .await;
15519
15520    cx.condition(|editor, _| editor.context_menu_visible())
15521        .await;
15522    cx.update_editor(|editor, _, _| {
15523        let context_menu = editor.context_menu.borrow_mut();
15524        let context_menu = context_menu
15525            .as_ref()
15526            .expect("Should have the context menu deployed");
15527        match context_menu {
15528            CodeContextMenu::Completions(completions_menu) => {
15529                let completions = completions_menu.completions.borrow_mut();
15530                assert_eq!(
15531                    completions
15532                        .iter()
15533                        .map(|completion| &completion.label.text)
15534                        .collect::<Vec<_>>(),
15535                    vec!["id", "other"]
15536                )
15537            }
15538            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15539        }
15540    });
15541    cx.run_until_parked();
15542
15543    cx.update_editor(|editor, window, cx| {
15544        editor.context_menu_next(&ContextMenuNext, window, cx);
15545    });
15546    cx.run_until_parked();
15547    cx.update_editor(|editor, window, cx| {
15548        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15549    });
15550    cx.run_until_parked();
15551    cx.update_editor(|editor, window, cx| {
15552        editor.context_menu_next(&ContextMenuNext, window, cx);
15553    });
15554    cx.run_until_parked();
15555    cx.update_editor(|editor, window, cx| {
15556        editor
15557            .compose_completion(&ComposeCompletion::default(), window, cx)
15558            .expect("No task returned")
15559    })
15560    .await
15561    .expect("Completion failed");
15562    cx.run_until_parked();
15563
15564    cx.update_editor(|editor, _, cx| {
15565        assert_eq!(
15566            resolve_requests_1.load(atomic::Ordering::Acquire),
15567            1,
15568            "Should always resolve once despite multiple selections"
15569        );
15570        assert_eq!(
15571            resolve_requests_2.load(atomic::Ordering::Acquire),
15572            1,
15573            "Should always resolve once after multiple selections and applying the completion"
15574        );
15575        assert_eq!(
15576            editor.text(cx),
15577            "fn main() { let a = ??.other; }",
15578            "Should use resolved data when applying the completion"
15579        );
15580    });
15581}
15582
15583#[gpui::test]
15584async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15585    init_test(cx, |_| {});
15586
15587    let item_0 = lsp::CompletionItem {
15588        label: "abs".into(),
15589        insert_text: Some("abs".into()),
15590        data: Some(json!({ "very": "special"})),
15591        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15592        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15593            lsp::InsertReplaceEdit {
15594                new_text: "abs".to_string(),
15595                insert: lsp::Range::default(),
15596                replace: lsp::Range::default(),
15597            },
15598        )),
15599        ..lsp::CompletionItem::default()
15600    };
15601    let items = iter::once(item_0.clone())
15602        .chain((11..51).map(|i| lsp::CompletionItem {
15603            label: format!("item_{}", i),
15604            insert_text: Some(format!("item_{}", i)),
15605            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15606            ..lsp::CompletionItem::default()
15607        }))
15608        .collect::<Vec<_>>();
15609
15610    let default_commit_characters = vec!["?".to_string()];
15611    let default_data = json!({ "default": "data"});
15612    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15613    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15614    let default_edit_range = lsp::Range {
15615        start: lsp::Position {
15616            line: 0,
15617            character: 5,
15618        },
15619        end: lsp::Position {
15620            line: 0,
15621            character: 5,
15622        },
15623    };
15624
15625    let mut cx = EditorLspTestContext::new_rust(
15626        lsp::ServerCapabilities {
15627            completion_provider: Some(lsp::CompletionOptions {
15628                trigger_characters: Some(vec![".".to_string()]),
15629                resolve_provider: Some(true),
15630                ..Default::default()
15631            }),
15632            ..Default::default()
15633        },
15634        cx,
15635    )
15636    .await;
15637
15638    cx.set_state("fn main() { let a = 2ˇ; }");
15639    cx.simulate_keystroke(".");
15640
15641    let completion_data = default_data.clone();
15642    let completion_characters = default_commit_characters.clone();
15643    let completion_items = items.clone();
15644    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15645        let default_data = completion_data.clone();
15646        let default_commit_characters = completion_characters.clone();
15647        let items = completion_items.clone();
15648        async move {
15649            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15650                items,
15651                item_defaults: Some(lsp::CompletionListItemDefaults {
15652                    data: Some(default_data.clone()),
15653                    commit_characters: Some(default_commit_characters.clone()),
15654                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15655                        default_edit_range,
15656                    )),
15657                    insert_text_format: Some(default_insert_text_format),
15658                    insert_text_mode: Some(default_insert_text_mode),
15659                }),
15660                ..lsp::CompletionList::default()
15661            })))
15662        }
15663    })
15664    .next()
15665    .await;
15666
15667    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15668    cx.lsp
15669        .server
15670        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15671            let closure_resolved_items = resolved_items.clone();
15672            move |item_to_resolve, _| {
15673                let closure_resolved_items = closure_resolved_items.clone();
15674                async move {
15675                    closure_resolved_items.lock().push(item_to_resolve.clone());
15676                    Ok(item_to_resolve)
15677                }
15678            }
15679        })
15680        .detach();
15681
15682    cx.condition(|editor, _| editor.context_menu_visible())
15683        .await;
15684    cx.run_until_parked();
15685    cx.update_editor(|editor, _, _| {
15686        let menu = editor.context_menu.borrow_mut();
15687        match menu.as_ref().expect("should have the completions menu") {
15688            CodeContextMenu::Completions(completions_menu) => {
15689                assert_eq!(
15690                    completions_menu
15691                        .entries
15692                        .borrow()
15693                        .iter()
15694                        .map(|mat| mat.string.clone())
15695                        .collect::<Vec<String>>(),
15696                    items
15697                        .iter()
15698                        .map(|completion| completion.label.clone())
15699                        .collect::<Vec<String>>()
15700                );
15701            }
15702            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15703        }
15704    });
15705    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15706    // with 4 from the end.
15707    assert_eq!(
15708        *resolved_items.lock(),
15709        [&items[0..16], &items[items.len() - 4..items.len()]]
15710            .concat()
15711            .iter()
15712            .cloned()
15713            .map(|mut item| {
15714                if item.data.is_none() {
15715                    item.data = Some(default_data.clone());
15716                }
15717                item
15718            })
15719            .collect::<Vec<lsp::CompletionItem>>(),
15720        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15721    );
15722    resolved_items.lock().clear();
15723
15724    cx.update_editor(|editor, window, cx| {
15725        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15726    });
15727    cx.run_until_parked();
15728    // Completions that have already been resolved are skipped.
15729    assert_eq!(
15730        *resolved_items.lock(),
15731        items[items.len() - 17..items.len() - 4]
15732            .iter()
15733            .cloned()
15734            .map(|mut item| {
15735                if item.data.is_none() {
15736                    item.data = Some(default_data.clone());
15737                }
15738                item
15739            })
15740            .collect::<Vec<lsp::CompletionItem>>()
15741    );
15742    resolved_items.lock().clear();
15743}
15744
15745#[gpui::test]
15746async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15747    init_test(cx, |_| {});
15748
15749    let mut cx = EditorLspTestContext::new(
15750        Language::new(
15751            LanguageConfig {
15752                matcher: LanguageMatcher {
15753                    path_suffixes: vec!["jsx".into()],
15754                    ..Default::default()
15755                },
15756                overrides: [(
15757                    "element".into(),
15758                    LanguageConfigOverride {
15759                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15760                        ..Default::default()
15761                    },
15762                )]
15763                .into_iter()
15764                .collect(),
15765                ..Default::default()
15766            },
15767            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15768        )
15769        .with_override_query("(jsx_self_closing_element) @element")
15770        .unwrap(),
15771        lsp::ServerCapabilities {
15772            completion_provider: Some(lsp::CompletionOptions {
15773                trigger_characters: Some(vec![":".to_string()]),
15774                ..Default::default()
15775            }),
15776            ..Default::default()
15777        },
15778        cx,
15779    )
15780    .await;
15781
15782    cx.lsp
15783        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15784            Ok(Some(lsp::CompletionResponse::Array(vec![
15785                lsp::CompletionItem {
15786                    label: "bg-blue".into(),
15787                    ..Default::default()
15788                },
15789                lsp::CompletionItem {
15790                    label: "bg-red".into(),
15791                    ..Default::default()
15792                },
15793                lsp::CompletionItem {
15794                    label: "bg-yellow".into(),
15795                    ..Default::default()
15796                },
15797            ])))
15798        });
15799
15800    cx.set_state(r#"<p class="bgˇ" />"#);
15801
15802    // Trigger completion when typing a dash, because the dash is an extra
15803    // word character in the 'element' scope, which contains the cursor.
15804    cx.simulate_keystroke("-");
15805    cx.executor().run_until_parked();
15806    cx.update_editor(|editor, _, _| {
15807        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15808        {
15809            assert_eq!(
15810                completion_menu_entries(&menu),
15811                &["bg-blue", "bg-red", "bg-yellow"]
15812            );
15813        } else {
15814            panic!("expected completion menu to be open");
15815        }
15816    });
15817
15818    cx.simulate_keystroke("l");
15819    cx.executor().run_until_parked();
15820    cx.update_editor(|editor, _, _| {
15821        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15822        {
15823            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15824        } else {
15825            panic!("expected completion menu to be open");
15826        }
15827    });
15828
15829    // When filtering completions, consider the character after the '-' to
15830    // be the start of a subword.
15831    cx.set_state(r#"<p class="yelˇ" />"#);
15832    cx.simulate_keystroke("l");
15833    cx.executor().run_until_parked();
15834    cx.update_editor(|editor, _, _| {
15835        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15836        {
15837            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15838        } else {
15839            panic!("expected completion menu to be open");
15840        }
15841    });
15842}
15843
15844fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15845    let entries = menu.entries.borrow();
15846    entries.iter().map(|mat| mat.string.clone()).collect()
15847}
15848
15849#[gpui::test]
15850async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15851    init_test(cx, |settings| {
15852        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15853            FormatterList(vec![Formatter::Prettier].into()),
15854        ))
15855    });
15856
15857    let fs = FakeFs::new(cx.executor());
15858    fs.insert_file(path!("/file.ts"), Default::default()).await;
15859
15860    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15861    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15862
15863    language_registry.add(Arc::new(Language::new(
15864        LanguageConfig {
15865            name: "TypeScript".into(),
15866            matcher: LanguageMatcher {
15867                path_suffixes: vec!["ts".to_string()],
15868                ..Default::default()
15869            },
15870            ..Default::default()
15871        },
15872        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15873    )));
15874    update_test_language_settings(cx, |settings| {
15875        settings.defaults.prettier = Some(PrettierSettings {
15876            allowed: true,
15877            ..PrettierSettings::default()
15878        });
15879    });
15880
15881    let test_plugin = "test_plugin";
15882    let _ = language_registry.register_fake_lsp(
15883        "TypeScript",
15884        FakeLspAdapter {
15885            prettier_plugins: vec![test_plugin],
15886            ..Default::default()
15887        },
15888    );
15889
15890    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15891    let buffer = project
15892        .update(cx, |project, cx| {
15893            project.open_local_buffer(path!("/file.ts"), cx)
15894        })
15895        .await
15896        .unwrap();
15897
15898    let buffer_text = "one\ntwo\nthree\n";
15899    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15900    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15901    editor.update_in(cx, |editor, window, cx| {
15902        editor.set_text(buffer_text, window, cx)
15903    });
15904
15905    editor
15906        .update_in(cx, |editor, window, cx| {
15907            editor.perform_format(
15908                project.clone(),
15909                FormatTrigger::Manual,
15910                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15911                window,
15912                cx,
15913            )
15914        })
15915        .unwrap()
15916        .await;
15917    assert_eq!(
15918        editor.update(cx, |editor, cx| editor.text(cx)),
15919        buffer_text.to_string() + prettier_format_suffix,
15920        "Test prettier formatting was not applied to the original buffer text",
15921    );
15922
15923    update_test_language_settings(cx, |settings| {
15924        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15925    });
15926    let format = editor.update_in(cx, |editor, window, cx| {
15927        editor.perform_format(
15928            project.clone(),
15929            FormatTrigger::Manual,
15930            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15931            window,
15932            cx,
15933        )
15934    });
15935    format.await.unwrap();
15936    assert_eq!(
15937        editor.update(cx, |editor, cx| editor.text(cx)),
15938        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15939        "Autoformatting (via test prettier) was not applied to the original buffer text",
15940    );
15941}
15942
15943#[gpui::test]
15944async fn test_addition_reverts(cx: &mut TestAppContext) {
15945    init_test(cx, |_| {});
15946    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15947    let base_text = indoc! {r#"
15948        struct Row;
15949        struct Row1;
15950        struct Row2;
15951
15952        struct Row4;
15953        struct Row5;
15954        struct Row6;
15955
15956        struct Row8;
15957        struct Row9;
15958        struct Row10;"#};
15959
15960    // When addition hunks are not adjacent to carets, no hunk revert is performed
15961    assert_hunk_revert(
15962        indoc! {r#"struct Row;
15963                   struct Row1;
15964                   struct Row1.1;
15965                   struct Row1.2;
15966                   struct Row2;ˇ
15967
15968                   struct Row4;
15969                   struct Row5;
15970                   struct Row6;
15971
15972                   struct Row8;
15973                   ˇstruct Row9;
15974                   struct Row9.1;
15975                   struct Row9.2;
15976                   struct Row9.3;
15977                   struct Row10;"#},
15978        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15979        indoc! {r#"struct Row;
15980                   struct Row1;
15981                   struct Row1.1;
15982                   struct Row1.2;
15983                   struct Row2;ˇ
15984
15985                   struct Row4;
15986                   struct Row5;
15987                   struct Row6;
15988
15989                   struct Row8;
15990                   ˇstruct Row9;
15991                   struct Row9.1;
15992                   struct Row9.2;
15993                   struct Row9.3;
15994                   struct Row10;"#},
15995        base_text,
15996        &mut cx,
15997    );
15998    // Same for selections
15999    assert_hunk_revert(
16000        indoc! {r#"struct Row;
16001                   struct Row1;
16002                   struct Row2;
16003                   struct Row2.1;
16004                   struct Row2.2;
16005                   «ˇ
16006                   struct Row4;
16007                   struct» Row5;
16008                   «struct Row6;
16009                   ˇ»
16010                   struct Row9.1;
16011                   struct Row9.2;
16012                   struct Row9.3;
16013                   struct Row8;
16014                   struct Row9;
16015                   struct Row10;"#},
16016        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16017        indoc! {r#"struct Row;
16018                   struct Row1;
16019                   struct Row2;
16020                   struct Row2.1;
16021                   struct Row2.2;
16022                   «ˇ
16023                   struct Row4;
16024                   struct» Row5;
16025                   «struct Row6;
16026                   ˇ»
16027                   struct Row9.1;
16028                   struct Row9.2;
16029                   struct Row9.3;
16030                   struct Row8;
16031                   struct Row9;
16032                   struct Row10;"#},
16033        base_text,
16034        &mut cx,
16035    );
16036
16037    // When carets and selections intersect the addition hunks, those are reverted.
16038    // Adjacent carets got merged.
16039    assert_hunk_revert(
16040        indoc! {r#"struct Row;
16041                   ˇ// something on the top
16042                   struct Row1;
16043                   struct Row2;
16044                   struct Roˇw3.1;
16045                   struct Row2.2;
16046                   struct Row2.3;ˇ
16047
16048                   struct Row4;
16049                   struct ˇRow5.1;
16050                   struct Row5.2;
16051                   struct «Rowˇ»5.3;
16052                   struct Row5;
16053                   struct Row6;
16054                   ˇ
16055                   struct Row9.1;
16056                   struct «Rowˇ»9.2;
16057                   struct «ˇRow»9.3;
16058                   struct Row8;
16059                   struct Row9;
16060                   «ˇ// something on bottom»
16061                   struct Row10;"#},
16062        vec![
16063            DiffHunkStatusKind::Added,
16064            DiffHunkStatusKind::Added,
16065            DiffHunkStatusKind::Added,
16066            DiffHunkStatusKind::Added,
16067            DiffHunkStatusKind::Added,
16068        ],
16069        indoc! {r#"struct Row;
16070                   ˇstruct Row1;
16071                   struct Row2;
16072                   ˇ
16073                   struct Row4;
16074                   ˇstruct Row5;
16075                   struct Row6;
16076                   ˇ
16077                   ˇstruct Row8;
16078                   struct Row9;
16079                   ˇstruct Row10;"#},
16080        base_text,
16081        &mut cx,
16082    );
16083}
16084
16085#[gpui::test]
16086async fn test_modification_reverts(cx: &mut TestAppContext) {
16087    init_test(cx, |_| {});
16088    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16089    let base_text = indoc! {r#"
16090        struct Row;
16091        struct Row1;
16092        struct Row2;
16093
16094        struct Row4;
16095        struct Row5;
16096        struct Row6;
16097
16098        struct Row8;
16099        struct Row9;
16100        struct Row10;"#};
16101
16102    // Modification hunks behave the same as the addition ones.
16103    assert_hunk_revert(
16104        indoc! {r#"struct Row;
16105                   struct Row1;
16106                   struct Row33;
16107                   ˇ
16108                   struct Row4;
16109                   struct Row5;
16110                   struct Row6;
16111                   ˇ
16112                   struct Row99;
16113                   struct Row9;
16114                   struct Row10;"#},
16115        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16116        indoc! {r#"struct Row;
16117                   struct Row1;
16118                   struct Row33;
16119                   ˇ
16120                   struct Row4;
16121                   struct Row5;
16122                   struct Row6;
16123                   ˇ
16124                   struct Row99;
16125                   struct Row9;
16126                   struct Row10;"#},
16127        base_text,
16128        &mut cx,
16129    );
16130    assert_hunk_revert(
16131        indoc! {r#"struct Row;
16132                   struct Row1;
16133                   struct Row33;
16134                   «ˇ
16135                   struct Row4;
16136                   struct» Row5;
16137                   «struct Row6;
16138                   ˇ»
16139                   struct Row99;
16140                   struct Row9;
16141                   struct Row10;"#},
16142        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16143        indoc! {r#"struct Row;
16144                   struct Row1;
16145                   struct Row33;
16146                   «ˇ
16147                   struct Row4;
16148                   struct» Row5;
16149                   «struct Row6;
16150                   ˇ»
16151                   struct Row99;
16152                   struct Row9;
16153                   struct Row10;"#},
16154        base_text,
16155        &mut cx,
16156    );
16157
16158    assert_hunk_revert(
16159        indoc! {r#"ˇstruct Row1.1;
16160                   struct Row1;
16161                   «ˇstr»uct Row22;
16162
16163                   struct ˇRow44;
16164                   struct Row5;
16165                   struct «Rˇ»ow66;ˇ
16166
16167                   «struˇ»ct Row88;
16168                   struct Row9;
16169                   struct Row1011;ˇ"#},
16170        vec![
16171            DiffHunkStatusKind::Modified,
16172            DiffHunkStatusKind::Modified,
16173            DiffHunkStatusKind::Modified,
16174            DiffHunkStatusKind::Modified,
16175            DiffHunkStatusKind::Modified,
16176            DiffHunkStatusKind::Modified,
16177        ],
16178        indoc! {r#"struct Row;
16179                   ˇstruct Row1;
16180                   struct Row2;
16181                   ˇ
16182                   struct Row4;
16183                   ˇstruct Row5;
16184                   struct Row6;
16185                   ˇ
16186                   struct Row8;
16187                   ˇstruct Row9;
16188                   struct Row10;ˇ"#},
16189        base_text,
16190        &mut cx,
16191    );
16192}
16193
16194#[gpui::test]
16195async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16196    init_test(cx, |_| {});
16197    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16198    let base_text = indoc! {r#"
16199        one
16200
16201        two
16202        three
16203        "#};
16204
16205    cx.set_head_text(base_text);
16206    cx.set_state("\nˇ\n");
16207    cx.executor().run_until_parked();
16208    cx.update_editor(|editor, _window, cx| {
16209        editor.expand_selected_diff_hunks(cx);
16210    });
16211    cx.executor().run_until_parked();
16212    cx.update_editor(|editor, window, cx| {
16213        editor.backspace(&Default::default(), window, cx);
16214    });
16215    cx.run_until_parked();
16216    cx.assert_state_with_diff(
16217        indoc! {r#"
16218
16219        - two
16220        - threeˇ
16221        +
16222        "#}
16223        .to_string(),
16224    );
16225}
16226
16227#[gpui::test]
16228async fn test_deletion_reverts(cx: &mut TestAppContext) {
16229    init_test(cx, |_| {});
16230    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16231    let base_text = indoc! {r#"struct Row;
16232struct Row1;
16233struct Row2;
16234
16235struct Row4;
16236struct Row5;
16237struct Row6;
16238
16239struct Row8;
16240struct Row9;
16241struct Row10;"#};
16242
16243    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16244    assert_hunk_revert(
16245        indoc! {r#"struct Row;
16246                   struct Row2;
16247
16248                   ˇstruct Row4;
16249                   struct Row5;
16250                   struct Row6;
16251                   ˇ
16252                   struct Row8;
16253                   struct Row10;"#},
16254        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16255        indoc! {r#"struct Row;
16256                   struct Row2;
16257
16258                   ˇstruct Row4;
16259                   struct Row5;
16260                   struct Row6;
16261                   ˇ
16262                   struct Row8;
16263                   struct Row10;"#},
16264        base_text,
16265        &mut cx,
16266    );
16267    assert_hunk_revert(
16268        indoc! {r#"struct Row;
16269                   struct Row2;
16270
16271                   «ˇstruct Row4;
16272                   struct» Row5;
16273                   «struct Row6;
16274                   ˇ»
16275                   struct Row8;
16276                   struct Row10;"#},
16277        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16278        indoc! {r#"struct Row;
16279                   struct Row2;
16280
16281                   «ˇstruct Row4;
16282                   struct» Row5;
16283                   «struct Row6;
16284                   ˇ»
16285                   struct Row8;
16286                   struct Row10;"#},
16287        base_text,
16288        &mut cx,
16289    );
16290
16291    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16292    assert_hunk_revert(
16293        indoc! {r#"struct Row;
16294                   ˇstruct Row2;
16295
16296                   struct Row4;
16297                   struct Row5;
16298                   struct Row6;
16299
16300                   struct Row8;ˇ
16301                   struct Row10;"#},
16302        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16303        indoc! {r#"struct Row;
16304                   struct Row1;
16305                   ˇstruct Row2;
16306
16307                   struct Row4;
16308                   struct Row5;
16309                   struct Row6;
16310
16311                   struct Row8;ˇ
16312                   struct Row9;
16313                   struct Row10;"#},
16314        base_text,
16315        &mut cx,
16316    );
16317    assert_hunk_revert(
16318        indoc! {r#"struct Row;
16319                   struct Row2«ˇ;
16320                   struct Row4;
16321                   struct» Row5;
16322                   «struct Row6;
16323
16324                   struct Row8;ˇ»
16325                   struct Row10;"#},
16326        vec![
16327            DiffHunkStatusKind::Deleted,
16328            DiffHunkStatusKind::Deleted,
16329            DiffHunkStatusKind::Deleted,
16330        ],
16331        indoc! {r#"struct Row;
16332                   struct Row1;
16333                   struct Row2«ˇ;
16334
16335                   struct Row4;
16336                   struct» Row5;
16337                   «struct Row6;
16338
16339                   struct Row8;ˇ»
16340                   struct Row9;
16341                   struct Row10;"#},
16342        base_text,
16343        &mut cx,
16344    );
16345}
16346
16347#[gpui::test]
16348async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16349    init_test(cx, |_| {});
16350
16351    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16352    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16353    let base_text_3 =
16354        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16355
16356    let text_1 = edit_first_char_of_every_line(base_text_1);
16357    let text_2 = edit_first_char_of_every_line(base_text_2);
16358    let text_3 = edit_first_char_of_every_line(base_text_3);
16359
16360    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16361    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16362    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16363
16364    let multibuffer = cx.new(|cx| {
16365        let mut multibuffer = MultiBuffer::new(ReadWrite);
16366        multibuffer.push_excerpts(
16367            buffer_1.clone(),
16368            [
16369                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16370                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16371                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16372            ],
16373            cx,
16374        );
16375        multibuffer.push_excerpts(
16376            buffer_2.clone(),
16377            [
16378                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16379                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16380                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16381            ],
16382            cx,
16383        );
16384        multibuffer.push_excerpts(
16385            buffer_3.clone(),
16386            [
16387                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16388                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16389                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16390            ],
16391            cx,
16392        );
16393        multibuffer
16394    });
16395
16396    let fs = FakeFs::new(cx.executor());
16397    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16398    let (editor, cx) = cx
16399        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16400    editor.update_in(cx, |editor, _window, cx| {
16401        for (buffer, diff_base) in [
16402            (buffer_1.clone(), base_text_1),
16403            (buffer_2.clone(), base_text_2),
16404            (buffer_3.clone(), base_text_3),
16405        ] {
16406            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16407            editor
16408                .buffer
16409                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16410        }
16411    });
16412    cx.executor().run_until_parked();
16413
16414    editor.update_in(cx, |editor, window, cx| {
16415        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}");
16416        editor.select_all(&SelectAll, window, cx);
16417        editor.git_restore(&Default::default(), window, cx);
16418    });
16419    cx.executor().run_until_parked();
16420
16421    // When all ranges are selected, all buffer hunks are reverted.
16422    editor.update(cx, |editor, cx| {
16423        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");
16424    });
16425    buffer_1.update(cx, |buffer, _| {
16426        assert_eq!(buffer.text(), base_text_1);
16427    });
16428    buffer_2.update(cx, |buffer, _| {
16429        assert_eq!(buffer.text(), base_text_2);
16430    });
16431    buffer_3.update(cx, |buffer, _| {
16432        assert_eq!(buffer.text(), base_text_3);
16433    });
16434
16435    editor.update_in(cx, |editor, window, cx| {
16436        editor.undo(&Default::default(), window, cx);
16437    });
16438
16439    editor.update_in(cx, |editor, window, cx| {
16440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16441            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16442        });
16443        editor.git_restore(&Default::default(), window, cx);
16444    });
16445
16446    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16447    // but not affect buffer_2 and its related excerpts.
16448    editor.update(cx, |editor, cx| {
16449        assert_eq!(
16450            editor.text(cx),
16451            "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}"
16452        );
16453    });
16454    buffer_1.update(cx, |buffer, _| {
16455        assert_eq!(buffer.text(), base_text_1);
16456    });
16457    buffer_2.update(cx, |buffer, _| {
16458        assert_eq!(
16459            buffer.text(),
16460            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16461        );
16462    });
16463    buffer_3.update(cx, |buffer, _| {
16464        assert_eq!(
16465            buffer.text(),
16466            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16467        );
16468    });
16469
16470    fn edit_first_char_of_every_line(text: &str) -> String {
16471        text.split('\n')
16472            .map(|line| format!("X{}", &line[1..]))
16473            .collect::<Vec<_>>()
16474            .join("\n")
16475    }
16476}
16477
16478#[gpui::test]
16479async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16480    init_test(cx, |_| {});
16481
16482    let cols = 4;
16483    let rows = 10;
16484    let sample_text_1 = sample_text(rows, cols, 'a');
16485    assert_eq!(
16486        sample_text_1,
16487        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16488    );
16489    let sample_text_2 = sample_text(rows, cols, 'l');
16490    assert_eq!(
16491        sample_text_2,
16492        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16493    );
16494    let sample_text_3 = sample_text(rows, cols, 'v');
16495    assert_eq!(
16496        sample_text_3,
16497        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16498    );
16499
16500    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16501    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16502    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16503
16504    let multi_buffer = cx.new(|cx| {
16505        let mut multibuffer = MultiBuffer::new(ReadWrite);
16506        multibuffer.push_excerpts(
16507            buffer_1.clone(),
16508            [
16509                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16510                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16511                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16512            ],
16513            cx,
16514        );
16515        multibuffer.push_excerpts(
16516            buffer_2.clone(),
16517            [
16518                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16519                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16520                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16521            ],
16522            cx,
16523        );
16524        multibuffer.push_excerpts(
16525            buffer_3.clone(),
16526            [
16527                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16528                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16529                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16530            ],
16531            cx,
16532        );
16533        multibuffer
16534    });
16535
16536    let fs = FakeFs::new(cx.executor());
16537    fs.insert_tree(
16538        "/a",
16539        json!({
16540            "main.rs": sample_text_1,
16541            "other.rs": sample_text_2,
16542            "lib.rs": sample_text_3,
16543        }),
16544    )
16545    .await;
16546    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16547    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16548    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16549    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16550        Editor::new(
16551            EditorMode::full(),
16552            multi_buffer,
16553            Some(project.clone()),
16554            window,
16555            cx,
16556        )
16557    });
16558    let multibuffer_item_id = workspace
16559        .update(cx, |workspace, window, cx| {
16560            assert!(
16561                workspace.active_item(cx).is_none(),
16562                "active item should be None before the first item is added"
16563            );
16564            workspace.add_item_to_active_pane(
16565                Box::new(multi_buffer_editor.clone()),
16566                None,
16567                true,
16568                window,
16569                cx,
16570            );
16571            let active_item = workspace
16572                .active_item(cx)
16573                .expect("should have an active item after adding the multi buffer");
16574            assert!(
16575                !active_item.is_singleton(cx),
16576                "A multi buffer was expected to active after adding"
16577            );
16578            active_item.item_id()
16579        })
16580        .unwrap();
16581    cx.executor().run_until_parked();
16582
16583    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16584        editor.change_selections(
16585            SelectionEffects::scroll(Autoscroll::Next),
16586            window,
16587            cx,
16588            |s| s.select_ranges(Some(1..2)),
16589        );
16590        editor.open_excerpts(&OpenExcerpts, window, cx);
16591    });
16592    cx.executor().run_until_parked();
16593    let first_item_id = workspace
16594        .update(cx, |workspace, window, cx| {
16595            let active_item = workspace
16596                .active_item(cx)
16597                .expect("should have an active item after navigating into the 1st buffer");
16598            let first_item_id = active_item.item_id();
16599            assert_ne!(
16600                first_item_id, multibuffer_item_id,
16601                "Should navigate into the 1st buffer and activate it"
16602            );
16603            assert!(
16604                active_item.is_singleton(cx),
16605                "New active item should be a singleton buffer"
16606            );
16607            assert_eq!(
16608                active_item
16609                    .act_as::<Editor>(cx)
16610                    .expect("should have navigated into an editor for the 1st buffer")
16611                    .read(cx)
16612                    .text(cx),
16613                sample_text_1
16614            );
16615
16616            workspace
16617                .go_back(workspace.active_pane().downgrade(), window, cx)
16618                .detach_and_log_err(cx);
16619
16620            first_item_id
16621        })
16622        .unwrap();
16623    cx.executor().run_until_parked();
16624    workspace
16625        .update(cx, |workspace, _, cx| {
16626            let active_item = workspace
16627                .active_item(cx)
16628                .expect("should have an active item after navigating back");
16629            assert_eq!(
16630                active_item.item_id(),
16631                multibuffer_item_id,
16632                "Should navigate back to the multi buffer"
16633            );
16634            assert!(!active_item.is_singleton(cx));
16635        })
16636        .unwrap();
16637
16638    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16639        editor.change_selections(
16640            SelectionEffects::scroll(Autoscroll::Next),
16641            window,
16642            cx,
16643            |s| s.select_ranges(Some(39..40)),
16644        );
16645        editor.open_excerpts(&OpenExcerpts, window, cx);
16646    });
16647    cx.executor().run_until_parked();
16648    let second_item_id = workspace
16649        .update(cx, |workspace, window, cx| {
16650            let active_item = workspace
16651                .active_item(cx)
16652                .expect("should have an active item after navigating into the 2nd buffer");
16653            let second_item_id = active_item.item_id();
16654            assert_ne!(
16655                second_item_id, multibuffer_item_id,
16656                "Should navigate away from the multibuffer"
16657            );
16658            assert_ne!(
16659                second_item_id, first_item_id,
16660                "Should navigate into the 2nd buffer and activate it"
16661            );
16662            assert!(
16663                active_item.is_singleton(cx),
16664                "New active item should be a singleton buffer"
16665            );
16666            assert_eq!(
16667                active_item
16668                    .act_as::<Editor>(cx)
16669                    .expect("should have navigated into an editor")
16670                    .read(cx)
16671                    .text(cx),
16672                sample_text_2
16673            );
16674
16675            workspace
16676                .go_back(workspace.active_pane().downgrade(), window, cx)
16677                .detach_and_log_err(cx);
16678
16679            second_item_id
16680        })
16681        .unwrap();
16682    cx.executor().run_until_parked();
16683    workspace
16684        .update(cx, |workspace, _, cx| {
16685            let active_item = workspace
16686                .active_item(cx)
16687                .expect("should have an active item after navigating back from the 2nd buffer");
16688            assert_eq!(
16689                active_item.item_id(),
16690                multibuffer_item_id,
16691                "Should navigate back from the 2nd buffer to the multi buffer"
16692            );
16693            assert!(!active_item.is_singleton(cx));
16694        })
16695        .unwrap();
16696
16697    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16698        editor.change_selections(
16699            SelectionEffects::scroll(Autoscroll::Next),
16700            window,
16701            cx,
16702            |s| s.select_ranges(Some(70..70)),
16703        );
16704        editor.open_excerpts(&OpenExcerpts, window, cx);
16705    });
16706    cx.executor().run_until_parked();
16707    workspace
16708        .update(cx, |workspace, window, cx| {
16709            let active_item = workspace
16710                .active_item(cx)
16711                .expect("should have an active item after navigating into the 3rd buffer");
16712            let third_item_id = active_item.item_id();
16713            assert_ne!(
16714                third_item_id, multibuffer_item_id,
16715                "Should navigate into the 3rd buffer and activate it"
16716            );
16717            assert_ne!(third_item_id, first_item_id);
16718            assert_ne!(third_item_id, second_item_id);
16719            assert!(
16720                active_item.is_singleton(cx),
16721                "New active item should be a singleton buffer"
16722            );
16723            assert_eq!(
16724                active_item
16725                    .act_as::<Editor>(cx)
16726                    .expect("should have navigated into an editor")
16727                    .read(cx)
16728                    .text(cx),
16729                sample_text_3
16730            );
16731
16732            workspace
16733                .go_back(workspace.active_pane().downgrade(), window, cx)
16734                .detach_and_log_err(cx);
16735        })
16736        .unwrap();
16737    cx.executor().run_until_parked();
16738    workspace
16739        .update(cx, |workspace, _, cx| {
16740            let active_item = workspace
16741                .active_item(cx)
16742                .expect("should have an active item after navigating back from the 3rd buffer");
16743            assert_eq!(
16744                active_item.item_id(),
16745                multibuffer_item_id,
16746                "Should navigate back from the 3rd buffer to the multi buffer"
16747            );
16748            assert!(!active_item.is_singleton(cx));
16749        })
16750        .unwrap();
16751}
16752
16753#[gpui::test]
16754async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16755    init_test(cx, |_| {});
16756
16757    let mut cx = EditorTestContext::new(cx).await;
16758
16759    let diff_base = r#"
16760        use some::mod;
16761
16762        const A: u32 = 42;
16763
16764        fn main() {
16765            println!("hello");
16766
16767            println!("world");
16768        }
16769        "#
16770    .unindent();
16771
16772    cx.set_state(
16773        &r#"
16774        use some::modified;
16775
16776        ˇ
16777        fn main() {
16778            println!("hello there");
16779
16780            println!("around the");
16781            println!("world");
16782        }
16783        "#
16784        .unindent(),
16785    );
16786
16787    cx.set_head_text(&diff_base);
16788    executor.run_until_parked();
16789
16790    cx.update_editor(|editor, window, cx| {
16791        editor.go_to_next_hunk(&GoToHunk, window, cx);
16792        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16793    });
16794    executor.run_until_parked();
16795    cx.assert_state_with_diff(
16796        r#"
16797          use some::modified;
16798
16799
16800          fn main() {
16801        -     println!("hello");
16802        + ˇ    println!("hello there");
16803
16804              println!("around the");
16805              println!("world");
16806          }
16807        "#
16808        .unindent(),
16809    );
16810
16811    cx.update_editor(|editor, window, cx| {
16812        for _ in 0..2 {
16813            editor.go_to_next_hunk(&GoToHunk, window, cx);
16814            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16815        }
16816    });
16817    executor.run_until_parked();
16818    cx.assert_state_with_diff(
16819        r#"
16820        - use some::mod;
16821        + ˇuse some::modified;
16822
16823
16824          fn main() {
16825        -     println!("hello");
16826        +     println!("hello there");
16827
16828        +     println!("around the");
16829              println!("world");
16830          }
16831        "#
16832        .unindent(),
16833    );
16834
16835    cx.update_editor(|editor, window, cx| {
16836        editor.go_to_next_hunk(&GoToHunk, window, cx);
16837        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16838    });
16839    executor.run_until_parked();
16840    cx.assert_state_with_diff(
16841        r#"
16842        - use some::mod;
16843        + use some::modified;
16844
16845        - const A: u32 = 42;
16846          ˇ
16847          fn main() {
16848        -     println!("hello");
16849        +     println!("hello there");
16850
16851        +     println!("around the");
16852              println!("world");
16853          }
16854        "#
16855        .unindent(),
16856    );
16857
16858    cx.update_editor(|editor, window, cx| {
16859        editor.cancel(&Cancel, window, cx);
16860    });
16861
16862    cx.assert_state_with_diff(
16863        r#"
16864          use some::modified;
16865
16866          ˇ
16867          fn main() {
16868              println!("hello there");
16869
16870              println!("around the");
16871              println!("world");
16872          }
16873        "#
16874        .unindent(),
16875    );
16876}
16877
16878#[gpui::test]
16879async fn test_diff_base_change_with_expanded_diff_hunks(
16880    executor: BackgroundExecutor,
16881    cx: &mut TestAppContext,
16882) {
16883    init_test(cx, |_| {});
16884
16885    let mut cx = EditorTestContext::new(cx).await;
16886
16887    let diff_base = r#"
16888        use some::mod1;
16889        use some::mod2;
16890
16891        const A: u32 = 42;
16892        const B: u32 = 42;
16893        const C: u32 = 42;
16894
16895        fn main() {
16896            println!("hello");
16897
16898            println!("world");
16899        }
16900        "#
16901    .unindent();
16902
16903    cx.set_state(
16904        &r#"
16905        use some::mod2;
16906
16907        const A: u32 = 42;
16908        const C: u32 = 42;
16909
16910        fn main(ˇ) {
16911            //println!("hello");
16912
16913            println!("world");
16914            //
16915            //
16916        }
16917        "#
16918        .unindent(),
16919    );
16920
16921    cx.set_head_text(&diff_base);
16922    executor.run_until_parked();
16923
16924    cx.update_editor(|editor, window, cx| {
16925        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16926    });
16927    executor.run_until_parked();
16928    cx.assert_state_with_diff(
16929        r#"
16930        - use some::mod1;
16931          use some::mod2;
16932
16933          const A: u32 = 42;
16934        - const B: u32 = 42;
16935          const C: u32 = 42;
16936
16937          fn main(ˇ) {
16938        -     println!("hello");
16939        +     //println!("hello");
16940
16941              println!("world");
16942        +     //
16943        +     //
16944          }
16945        "#
16946        .unindent(),
16947    );
16948
16949    cx.set_head_text("new diff base!");
16950    executor.run_until_parked();
16951    cx.assert_state_with_diff(
16952        r#"
16953        - new diff base!
16954        + use some::mod2;
16955        +
16956        + const A: u32 = 42;
16957        + const C: u32 = 42;
16958        +
16959        + fn main(ˇ) {
16960        +     //println!("hello");
16961        +
16962        +     println!("world");
16963        +     //
16964        +     //
16965        + }
16966        "#
16967        .unindent(),
16968    );
16969}
16970
16971#[gpui::test]
16972async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16973    init_test(cx, |_| {});
16974
16975    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16976    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16977    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16978    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16979    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16980    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16981
16982    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16983    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16984    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16985
16986    let multi_buffer = cx.new(|cx| {
16987        let mut multibuffer = MultiBuffer::new(ReadWrite);
16988        multibuffer.push_excerpts(
16989            buffer_1.clone(),
16990            [
16991                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16992                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16993                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16994            ],
16995            cx,
16996        );
16997        multibuffer.push_excerpts(
16998            buffer_2.clone(),
16999            [
17000                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17001                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17002                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17003            ],
17004            cx,
17005        );
17006        multibuffer.push_excerpts(
17007            buffer_3.clone(),
17008            [
17009                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17010                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17011                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17012            ],
17013            cx,
17014        );
17015        multibuffer
17016    });
17017
17018    let editor =
17019        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17020    editor
17021        .update(cx, |editor, _window, cx| {
17022            for (buffer, diff_base) in [
17023                (buffer_1.clone(), file_1_old),
17024                (buffer_2.clone(), file_2_old),
17025                (buffer_3.clone(), file_3_old),
17026            ] {
17027                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17028                editor
17029                    .buffer
17030                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17031            }
17032        })
17033        .unwrap();
17034
17035    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17036    cx.run_until_parked();
17037
17038    cx.assert_editor_state(
17039        &"
17040            ˇaaa
17041            ccc
17042            ddd
17043
17044            ggg
17045            hhh
17046
17047
17048            lll
17049            mmm
17050            NNN
17051
17052            qqq
17053            rrr
17054
17055            uuu
17056            111
17057            222
17058            333
17059
17060            666
17061            777
17062
17063            000
17064            !!!"
17065        .unindent(),
17066    );
17067
17068    cx.update_editor(|editor, window, cx| {
17069        editor.select_all(&SelectAll, window, cx);
17070        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17071    });
17072    cx.executor().run_until_parked();
17073
17074    cx.assert_state_with_diff(
17075        "
17076            «aaa
17077          - bbb
17078            ccc
17079            ddd
17080
17081            ggg
17082            hhh
17083
17084
17085            lll
17086            mmm
17087          - nnn
17088          + NNN
17089
17090            qqq
17091            rrr
17092
17093            uuu
17094            111
17095            222
17096            333
17097
17098          + 666
17099            777
17100
17101            000
17102            !!!ˇ»"
17103            .unindent(),
17104    );
17105}
17106
17107#[gpui::test]
17108async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17109    init_test(cx, |_| {});
17110
17111    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17112    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17113
17114    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17115    let multi_buffer = cx.new(|cx| {
17116        let mut multibuffer = MultiBuffer::new(ReadWrite);
17117        multibuffer.push_excerpts(
17118            buffer.clone(),
17119            [
17120                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17121                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17122                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17123            ],
17124            cx,
17125        );
17126        multibuffer
17127    });
17128
17129    let editor =
17130        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17131    editor
17132        .update(cx, |editor, _window, cx| {
17133            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17134            editor
17135                .buffer
17136                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17137        })
17138        .unwrap();
17139
17140    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17141    cx.run_until_parked();
17142
17143    cx.update_editor(|editor, window, cx| {
17144        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17145    });
17146    cx.executor().run_until_parked();
17147
17148    // When the start of a hunk coincides with the start of its excerpt,
17149    // the hunk is expanded. When the start of a a hunk is earlier than
17150    // the start of its excerpt, the hunk is not expanded.
17151    cx.assert_state_with_diff(
17152        "
17153            ˇaaa
17154          - bbb
17155          + BBB
17156
17157          - ddd
17158          - eee
17159          + DDD
17160          + EEE
17161            fff
17162
17163            iii
17164        "
17165        .unindent(),
17166    );
17167}
17168
17169#[gpui::test]
17170async fn test_edits_around_expanded_insertion_hunks(
17171    executor: BackgroundExecutor,
17172    cx: &mut TestAppContext,
17173) {
17174    init_test(cx, |_| {});
17175
17176    let mut cx = EditorTestContext::new(cx).await;
17177
17178    let diff_base = r#"
17179        use some::mod1;
17180        use some::mod2;
17181
17182        const A: u32 = 42;
17183
17184        fn main() {
17185            println!("hello");
17186
17187            println!("world");
17188        }
17189        "#
17190    .unindent();
17191    executor.run_until_parked();
17192    cx.set_state(
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        ˇ
17201
17202        fn main() {
17203            println!("hello");
17204
17205            println!("world");
17206        }
17207        "#
17208        .unindent(),
17209    );
17210
17211    cx.set_head_text(&diff_base);
17212    executor.run_until_parked();
17213
17214    cx.update_editor(|editor, window, cx| {
17215        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17216    });
17217    executor.run_until_parked();
17218
17219    cx.assert_state_with_diff(
17220        r#"
17221        use some::mod1;
17222        use some::mod2;
17223
17224        const A: u32 = 42;
17225      + const B: u32 = 42;
17226      + const C: u32 = 42;
17227      + ˇ
17228
17229        fn main() {
17230            println!("hello");
17231
17232            println!("world");
17233        }
17234      "#
17235        .unindent(),
17236    );
17237
17238    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17239    executor.run_until_parked();
17240
17241    cx.assert_state_with_diff(
17242        r#"
17243        use some::mod1;
17244        use some::mod2;
17245
17246        const A: u32 = 42;
17247      + const B: u32 = 42;
17248      + const C: u32 = 42;
17249      + const D: u32 = 42;
17250      + ˇ
17251
17252        fn main() {
17253            println!("hello");
17254
17255            println!("world");
17256        }
17257      "#
17258        .unindent(),
17259    );
17260
17261    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17262    executor.run_until_parked();
17263
17264    cx.assert_state_with_diff(
17265        r#"
17266        use some::mod1;
17267        use some::mod2;
17268
17269        const A: u32 = 42;
17270      + const B: u32 = 42;
17271      + const C: u32 = 42;
17272      + const D: u32 = 42;
17273      + const E: u32 = 42;
17274      + ˇ
17275
17276        fn main() {
17277            println!("hello");
17278
17279            println!("world");
17280        }
17281      "#
17282        .unindent(),
17283    );
17284
17285    cx.update_editor(|editor, window, cx| {
17286        editor.delete_line(&DeleteLine, window, cx);
17287    });
17288    executor.run_until_parked();
17289
17290    cx.assert_state_with_diff(
17291        r#"
17292        use some::mod1;
17293        use some::mod2;
17294
17295        const A: u32 = 42;
17296      + const B: u32 = 42;
17297      + const C: u32 = 42;
17298      + const D: u32 = 42;
17299      + const E: u32 = 42;
17300        ˇ
17301        fn main() {
17302            println!("hello");
17303
17304            println!("world");
17305        }
17306      "#
17307        .unindent(),
17308    );
17309
17310    cx.update_editor(|editor, window, cx| {
17311        editor.move_up(&MoveUp, window, cx);
17312        editor.delete_line(&DeleteLine, window, cx);
17313        editor.move_up(&MoveUp, window, cx);
17314        editor.delete_line(&DeleteLine, window, cx);
17315        editor.move_up(&MoveUp, window, cx);
17316        editor.delete_line(&DeleteLine, window, cx);
17317    });
17318    executor.run_until_parked();
17319    cx.assert_state_with_diff(
17320        r#"
17321        use some::mod1;
17322        use some::mod2;
17323
17324        const A: u32 = 42;
17325      + const B: u32 = 42;
17326        ˇ
17327        fn main() {
17328            println!("hello");
17329
17330            println!("world");
17331        }
17332      "#
17333        .unindent(),
17334    );
17335
17336    cx.update_editor(|editor, window, cx| {
17337        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17338        editor.delete_line(&DeleteLine, window, cx);
17339    });
17340    executor.run_until_parked();
17341    cx.assert_state_with_diff(
17342        r#"
17343        ˇ
17344        fn main() {
17345            println!("hello");
17346
17347            println!("world");
17348        }
17349      "#
17350        .unindent(),
17351    );
17352}
17353
17354#[gpui::test]
17355async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17356    init_test(cx, |_| {});
17357
17358    let mut cx = EditorTestContext::new(cx).await;
17359    cx.set_head_text(indoc! { "
17360        one
17361        two
17362        three
17363        four
17364        five
17365        "
17366    });
17367    cx.set_state(indoc! { "
17368        one
17369        ˇthree
17370        five
17371    "});
17372    cx.run_until_parked();
17373    cx.update_editor(|editor, window, cx| {
17374        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17375    });
17376    cx.assert_state_with_diff(
17377        indoc! { "
17378        one
17379      - two
17380        ˇthree
17381      - four
17382        five
17383    "}
17384        .to_string(),
17385    );
17386    cx.update_editor(|editor, window, cx| {
17387        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17388    });
17389
17390    cx.assert_state_with_diff(
17391        indoc! { "
17392        one
17393        ˇthree
17394        five
17395    "}
17396        .to_string(),
17397    );
17398
17399    cx.set_state(indoc! { "
17400        one
17401        ˇTWO
17402        three
17403        four
17404        five
17405    "});
17406    cx.run_until_parked();
17407    cx.update_editor(|editor, window, cx| {
17408        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17409    });
17410
17411    cx.assert_state_with_diff(
17412        indoc! { "
17413            one
17414          - two
17415          + ˇTWO
17416            three
17417            four
17418            five
17419        "}
17420        .to_string(),
17421    );
17422    cx.update_editor(|editor, window, cx| {
17423        editor.move_up(&Default::default(), window, cx);
17424        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17425    });
17426    cx.assert_state_with_diff(
17427        indoc! { "
17428            one
17429            ˇTWO
17430            three
17431            four
17432            five
17433        "}
17434        .to_string(),
17435    );
17436}
17437
17438#[gpui::test]
17439async fn test_edits_around_expanded_deletion_hunks(
17440    executor: BackgroundExecutor,
17441    cx: &mut TestAppContext,
17442) {
17443    init_test(cx, |_| {});
17444
17445    let mut cx = EditorTestContext::new(cx).await;
17446
17447    let diff_base = r#"
17448        use some::mod1;
17449        use some::mod2;
17450
17451        const A: u32 = 42;
17452        const B: u32 = 42;
17453        const C: u32 = 42;
17454
17455
17456        fn main() {
17457            println!("hello");
17458
17459            println!("world");
17460        }
17461    "#
17462    .unindent();
17463    executor.run_until_parked();
17464    cx.set_state(
17465        &r#"
17466        use some::mod1;
17467        use some::mod2;
17468
17469        ˇconst B: u32 = 42;
17470        const C: u32 = 42;
17471
17472
17473        fn main() {
17474            println!("hello");
17475
17476            println!("world");
17477        }
17478        "#
17479        .unindent(),
17480    );
17481
17482    cx.set_head_text(&diff_base);
17483    executor.run_until_parked();
17484
17485    cx.update_editor(|editor, window, cx| {
17486        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17487    });
17488    executor.run_until_parked();
17489
17490    cx.assert_state_with_diff(
17491        r#"
17492        use some::mod1;
17493        use some::mod2;
17494
17495      - const A: u32 = 42;
17496        ˇconst B: u32 = 42;
17497        const C: u32 = 42;
17498
17499
17500        fn main() {
17501            println!("hello");
17502
17503            println!("world");
17504        }
17505      "#
17506        .unindent(),
17507    );
17508
17509    cx.update_editor(|editor, window, cx| {
17510        editor.delete_line(&DeleteLine, window, cx);
17511    });
17512    executor.run_until_parked();
17513    cx.assert_state_with_diff(
17514        r#"
17515        use some::mod1;
17516        use some::mod2;
17517
17518      - const A: u32 = 42;
17519      - const B: u32 = 42;
17520        ˇconst C: u32 = 42;
17521
17522
17523        fn main() {
17524            println!("hello");
17525
17526            println!("world");
17527        }
17528      "#
17529        .unindent(),
17530    );
17531
17532    cx.update_editor(|editor, window, cx| {
17533        editor.delete_line(&DeleteLine, window, cx);
17534    });
17535    executor.run_until_parked();
17536    cx.assert_state_with_diff(
17537        r#"
17538        use some::mod1;
17539        use some::mod2;
17540
17541      - const A: u32 = 42;
17542      - const B: u32 = 42;
17543      - const C: u32 = 42;
17544        ˇ
17545
17546        fn main() {
17547            println!("hello");
17548
17549            println!("world");
17550        }
17551      "#
17552        .unindent(),
17553    );
17554
17555    cx.update_editor(|editor, window, cx| {
17556        editor.handle_input("replacement", window, cx);
17557    });
17558    executor.run_until_parked();
17559    cx.assert_state_with_diff(
17560        r#"
17561        use some::mod1;
17562        use some::mod2;
17563
17564      - const A: u32 = 42;
17565      - const B: u32 = 42;
17566      - const C: u32 = 42;
17567      -
17568      + replacementˇ
17569
17570        fn main() {
17571            println!("hello");
17572
17573            println!("world");
17574        }
17575      "#
17576        .unindent(),
17577    );
17578}
17579
17580#[gpui::test]
17581async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17582    init_test(cx, |_| {});
17583
17584    let mut cx = EditorTestContext::new(cx).await;
17585
17586    let base_text = r#"
17587        one
17588        two
17589        three
17590        four
17591        five
17592    "#
17593    .unindent();
17594    executor.run_until_parked();
17595    cx.set_state(
17596        &r#"
17597        one
17598        two
17599        fˇour
17600        five
17601        "#
17602        .unindent(),
17603    );
17604
17605    cx.set_head_text(&base_text);
17606    executor.run_until_parked();
17607
17608    cx.update_editor(|editor, window, cx| {
17609        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17610    });
17611    executor.run_until_parked();
17612
17613    cx.assert_state_with_diff(
17614        r#"
17615          one
17616          two
17617        - three
17618          fˇour
17619          five
17620        "#
17621        .unindent(),
17622    );
17623
17624    cx.update_editor(|editor, window, cx| {
17625        editor.backspace(&Backspace, window, cx);
17626        editor.backspace(&Backspace, window, cx);
17627    });
17628    executor.run_until_parked();
17629    cx.assert_state_with_diff(
17630        r#"
17631          one
17632          two
17633        - threeˇ
17634        - four
17635        + our
17636          five
17637        "#
17638        .unindent(),
17639    );
17640}
17641
17642#[gpui::test]
17643async fn test_edit_after_expanded_modification_hunk(
17644    executor: BackgroundExecutor,
17645    cx: &mut TestAppContext,
17646) {
17647    init_test(cx, |_| {});
17648
17649    let mut cx = EditorTestContext::new(cx).await;
17650
17651    let diff_base = r#"
17652        use some::mod1;
17653        use some::mod2;
17654
17655        const A: u32 = 42;
17656        const B: u32 = 42;
17657        const C: u32 = 42;
17658        const D: u32 = 42;
17659
17660
17661        fn main() {
17662            println!("hello");
17663
17664            println!("world");
17665        }"#
17666    .unindent();
17667
17668    cx.set_state(
17669        &r#"
17670        use some::mod1;
17671        use some::mod2;
17672
17673        const A: u32 = 42;
17674        const B: u32 = 42;
17675        const C: u32 = 43ˇ
17676        const D: u32 = 42;
17677
17678
17679        fn main() {
17680            println!("hello");
17681
17682            println!("world");
17683        }"#
17684        .unindent(),
17685    );
17686
17687    cx.set_head_text(&diff_base);
17688    executor.run_until_parked();
17689    cx.update_editor(|editor, window, cx| {
17690        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17691    });
17692    executor.run_until_parked();
17693
17694    cx.assert_state_with_diff(
17695        r#"
17696        use some::mod1;
17697        use some::mod2;
17698
17699        const A: u32 = 42;
17700        const B: u32 = 42;
17701      - const C: u32 = 42;
17702      + const C: u32 = 43ˇ
17703        const D: u32 = 42;
17704
17705
17706        fn main() {
17707            println!("hello");
17708
17709            println!("world");
17710        }"#
17711        .unindent(),
17712    );
17713
17714    cx.update_editor(|editor, window, cx| {
17715        editor.handle_input("\nnew_line\n", window, cx);
17716    });
17717    executor.run_until_parked();
17718
17719    cx.assert_state_with_diff(
17720        r#"
17721        use some::mod1;
17722        use some::mod2;
17723
17724        const A: u32 = 42;
17725        const B: u32 = 42;
17726      - const C: u32 = 42;
17727      + const C: u32 = 43
17728      + new_line
17729      + ˇ
17730        const D: u32 = 42;
17731
17732
17733        fn main() {
17734            println!("hello");
17735
17736            println!("world");
17737        }"#
17738        .unindent(),
17739    );
17740}
17741
17742#[gpui::test]
17743async fn test_stage_and_unstage_added_file_hunk(
17744    executor: BackgroundExecutor,
17745    cx: &mut TestAppContext,
17746) {
17747    init_test(cx, |_| {});
17748
17749    let mut cx = EditorTestContext::new(cx).await;
17750    cx.update_editor(|editor, _, cx| {
17751        editor.set_expand_all_diff_hunks(cx);
17752    });
17753
17754    let working_copy = r#"
17755            ˇfn main() {
17756                println!("hello, world!");
17757            }
17758        "#
17759    .unindent();
17760
17761    cx.set_state(&working_copy);
17762    executor.run_until_parked();
17763
17764    cx.assert_state_with_diff(
17765        r#"
17766            + ˇfn main() {
17767            +     println!("hello, world!");
17768            + }
17769        "#
17770        .unindent(),
17771    );
17772    cx.assert_index_text(None);
17773
17774    cx.update_editor(|editor, window, cx| {
17775        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17776    });
17777    executor.run_until_parked();
17778    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17779    cx.assert_state_with_diff(
17780        r#"
17781            + ˇfn main() {
17782            +     println!("hello, world!");
17783            + }
17784        "#
17785        .unindent(),
17786    );
17787
17788    cx.update_editor(|editor, window, cx| {
17789        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17790    });
17791    executor.run_until_parked();
17792    cx.assert_index_text(None);
17793}
17794
17795async fn setup_indent_guides_editor(
17796    text: &str,
17797    cx: &mut TestAppContext,
17798) -> (BufferId, EditorTestContext) {
17799    init_test(cx, |_| {});
17800
17801    let mut cx = EditorTestContext::new(cx).await;
17802
17803    let buffer_id = cx.update_editor(|editor, window, cx| {
17804        editor.set_text(text, window, cx);
17805        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17806
17807        buffer_ids[0]
17808    });
17809
17810    (buffer_id, cx)
17811}
17812
17813fn assert_indent_guides(
17814    range: Range<u32>,
17815    expected: Vec<IndentGuide>,
17816    active_indices: Option<Vec<usize>>,
17817    cx: &mut EditorTestContext,
17818) {
17819    let indent_guides = cx.update_editor(|editor, window, cx| {
17820        let snapshot = editor.snapshot(window, cx).display_snapshot;
17821        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17822            editor,
17823            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17824            true,
17825            &snapshot,
17826            cx,
17827        );
17828
17829        indent_guides.sort_by(|a, b| {
17830            a.depth.cmp(&b.depth).then(
17831                a.start_row
17832                    .cmp(&b.start_row)
17833                    .then(a.end_row.cmp(&b.end_row)),
17834            )
17835        });
17836        indent_guides
17837    });
17838
17839    if let Some(expected) = active_indices {
17840        let active_indices = cx.update_editor(|editor, window, cx| {
17841            let snapshot = editor.snapshot(window, cx).display_snapshot;
17842            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17843        });
17844
17845        assert_eq!(
17846            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17847            expected,
17848            "Active indent guide indices do not match"
17849        );
17850    }
17851
17852    assert_eq!(indent_guides, expected, "Indent guides do not match");
17853}
17854
17855fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17856    IndentGuide {
17857        buffer_id,
17858        start_row: MultiBufferRow(start_row),
17859        end_row: MultiBufferRow(end_row),
17860        depth,
17861        tab_size: 4,
17862        settings: IndentGuideSettings {
17863            enabled: true,
17864            line_width: 1,
17865            active_line_width: 1,
17866            ..Default::default()
17867        },
17868    }
17869}
17870
17871#[gpui::test]
17872async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17873    let (buffer_id, mut cx) = setup_indent_guides_editor(
17874        &"
17875        fn main() {
17876            let a = 1;
17877        }"
17878        .unindent(),
17879        cx,
17880    )
17881    .await;
17882
17883    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17884}
17885
17886#[gpui::test]
17887async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17888    let (buffer_id, mut cx) = setup_indent_guides_editor(
17889        &"
17890        fn main() {
17891            let a = 1;
17892            let b = 2;
17893        }"
17894        .unindent(),
17895        cx,
17896    )
17897    .await;
17898
17899    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17900}
17901
17902#[gpui::test]
17903async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17904    let (buffer_id, mut cx) = setup_indent_guides_editor(
17905        &"
17906        fn main() {
17907            let a = 1;
17908            if a == 3 {
17909                let b = 2;
17910            } else {
17911                let c = 3;
17912            }
17913        }"
17914        .unindent(),
17915        cx,
17916    )
17917    .await;
17918
17919    assert_indent_guides(
17920        0..8,
17921        vec![
17922            indent_guide(buffer_id, 1, 6, 0),
17923            indent_guide(buffer_id, 3, 3, 1),
17924            indent_guide(buffer_id, 5, 5, 1),
17925        ],
17926        None,
17927        &mut cx,
17928    );
17929}
17930
17931#[gpui::test]
17932async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17933    let (buffer_id, mut cx) = setup_indent_guides_editor(
17934        &"
17935        fn main() {
17936            let a = 1;
17937                let b = 2;
17938            let c = 3;
17939        }"
17940        .unindent(),
17941        cx,
17942    )
17943    .await;
17944
17945    assert_indent_guides(
17946        0..5,
17947        vec![
17948            indent_guide(buffer_id, 1, 3, 0),
17949            indent_guide(buffer_id, 2, 2, 1),
17950        ],
17951        None,
17952        &mut cx,
17953    );
17954}
17955
17956#[gpui::test]
17957async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17958    let (buffer_id, mut cx) = setup_indent_guides_editor(
17959        &"
17960        fn main() {
17961            let a = 1;
17962
17963            let c = 3;
17964        }"
17965        .unindent(),
17966        cx,
17967    )
17968    .await;
17969
17970    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17971}
17972
17973#[gpui::test]
17974async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17975    let (buffer_id, mut cx) = setup_indent_guides_editor(
17976        &"
17977        fn main() {
17978            let a = 1;
17979
17980            let c = 3;
17981
17982            if a == 3 {
17983                let b = 2;
17984            } else {
17985                let c = 3;
17986            }
17987        }"
17988        .unindent(),
17989        cx,
17990    )
17991    .await;
17992
17993    assert_indent_guides(
17994        0..11,
17995        vec![
17996            indent_guide(buffer_id, 1, 9, 0),
17997            indent_guide(buffer_id, 6, 6, 1),
17998            indent_guide(buffer_id, 8, 8, 1),
17999        ],
18000        None,
18001        &mut cx,
18002    );
18003}
18004
18005#[gpui::test]
18006async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18007    let (buffer_id, mut cx) = setup_indent_guides_editor(
18008        &"
18009        fn main() {
18010            let a = 1;
18011
18012            let c = 3;
18013
18014            if a == 3 {
18015                let b = 2;
18016            } else {
18017                let c = 3;
18018            }
18019        }"
18020        .unindent(),
18021        cx,
18022    )
18023    .await;
18024
18025    assert_indent_guides(
18026        1..11,
18027        vec![
18028            indent_guide(buffer_id, 1, 9, 0),
18029            indent_guide(buffer_id, 6, 6, 1),
18030            indent_guide(buffer_id, 8, 8, 1),
18031        ],
18032        None,
18033        &mut cx,
18034    );
18035}
18036
18037#[gpui::test]
18038async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18039    let (buffer_id, mut cx) = setup_indent_guides_editor(
18040        &"
18041        fn main() {
18042            let a = 1;
18043
18044            let c = 3;
18045
18046            if a == 3 {
18047                let b = 2;
18048            } else {
18049                let c = 3;
18050            }
18051        }"
18052        .unindent(),
18053        cx,
18054    )
18055    .await;
18056
18057    assert_indent_guides(
18058        1..10,
18059        vec![
18060            indent_guide(buffer_id, 1, 9, 0),
18061            indent_guide(buffer_id, 6, 6, 1),
18062            indent_guide(buffer_id, 8, 8, 1),
18063        ],
18064        None,
18065        &mut cx,
18066    );
18067}
18068
18069#[gpui::test]
18070async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18071    let (buffer_id, mut cx) = setup_indent_guides_editor(
18072        &"
18073        fn main() {
18074            if a {
18075                b(
18076                    c,
18077                    d,
18078                )
18079            } else {
18080                e(
18081                    f
18082                )
18083            }
18084        }"
18085        .unindent(),
18086        cx,
18087    )
18088    .await;
18089
18090    assert_indent_guides(
18091        0..11,
18092        vec![
18093            indent_guide(buffer_id, 1, 10, 0),
18094            indent_guide(buffer_id, 2, 5, 1),
18095            indent_guide(buffer_id, 7, 9, 1),
18096            indent_guide(buffer_id, 3, 4, 2),
18097            indent_guide(buffer_id, 8, 8, 2),
18098        ],
18099        None,
18100        &mut cx,
18101    );
18102
18103    cx.update_editor(|editor, window, cx| {
18104        editor.fold_at(MultiBufferRow(2), window, cx);
18105        assert_eq!(
18106            editor.display_text(cx),
18107            "
18108            fn main() {
18109                if a {
18110                    b(⋯
18111                    )
18112                } else {
18113                    e(
18114                        f
18115                    )
18116                }
18117            }"
18118            .unindent()
18119        );
18120    });
18121
18122    assert_indent_guides(
18123        0..11,
18124        vec![
18125            indent_guide(buffer_id, 1, 10, 0),
18126            indent_guide(buffer_id, 2, 5, 1),
18127            indent_guide(buffer_id, 7, 9, 1),
18128            indent_guide(buffer_id, 8, 8, 2),
18129        ],
18130        None,
18131        &mut cx,
18132    );
18133}
18134
18135#[gpui::test]
18136async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18137    let (buffer_id, mut cx) = setup_indent_guides_editor(
18138        &"
18139        block1
18140            block2
18141                block3
18142                    block4
18143            block2
18144        block1
18145        block1"
18146            .unindent(),
18147        cx,
18148    )
18149    .await;
18150
18151    assert_indent_guides(
18152        1..10,
18153        vec![
18154            indent_guide(buffer_id, 1, 4, 0),
18155            indent_guide(buffer_id, 2, 3, 1),
18156            indent_guide(buffer_id, 3, 3, 2),
18157        ],
18158        None,
18159        &mut cx,
18160    );
18161}
18162
18163#[gpui::test]
18164async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18165    let (buffer_id, mut cx) = setup_indent_guides_editor(
18166        &"
18167        block1
18168            block2
18169                block3
18170
18171        block1
18172        block1"
18173            .unindent(),
18174        cx,
18175    )
18176    .await;
18177
18178    assert_indent_guides(
18179        0..6,
18180        vec![
18181            indent_guide(buffer_id, 1, 2, 0),
18182            indent_guide(buffer_id, 2, 2, 1),
18183        ],
18184        None,
18185        &mut cx,
18186    );
18187}
18188
18189#[gpui::test]
18190async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18191    let (buffer_id, mut cx) = setup_indent_guides_editor(
18192        &"
18193        function component() {
18194        \treturn (
18195        \t\t\t
18196        \t\t<div>
18197        \t\t\t<abc></abc>
18198        \t\t</div>
18199        \t)
18200        }"
18201        .unindent(),
18202        cx,
18203    )
18204    .await;
18205
18206    assert_indent_guides(
18207        0..8,
18208        vec![
18209            indent_guide(buffer_id, 1, 6, 0),
18210            indent_guide(buffer_id, 2, 5, 1),
18211            indent_guide(buffer_id, 4, 4, 2),
18212        ],
18213        None,
18214        &mut cx,
18215    );
18216}
18217
18218#[gpui::test]
18219async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18220    let (buffer_id, mut cx) = setup_indent_guides_editor(
18221        &"
18222        function component() {
18223        \treturn (
18224        \t
18225        \t\t<div>
18226        \t\t\t<abc></abc>
18227        \t\t</div>
18228        \t)
18229        }"
18230        .unindent(),
18231        cx,
18232    )
18233    .await;
18234
18235    assert_indent_guides(
18236        0..8,
18237        vec![
18238            indent_guide(buffer_id, 1, 6, 0),
18239            indent_guide(buffer_id, 2, 5, 1),
18240            indent_guide(buffer_id, 4, 4, 2),
18241        ],
18242        None,
18243        &mut cx,
18244    );
18245}
18246
18247#[gpui::test]
18248async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18249    let (buffer_id, mut cx) = setup_indent_guides_editor(
18250        &"
18251        block1
18252
18253
18254
18255            block2
18256        "
18257        .unindent(),
18258        cx,
18259    )
18260    .await;
18261
18262    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18263}
18264
18265#[gpui::test]
18266async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18267    let (buffer_id, mut cx) = setup_indent_guides_editor(
18268        &"
18269        def a:
18270        \tb = 3
18271        \tif True:
18272        \t\tc = 4
18273        \t\td = 5
18274        \tprint(b)
18275        "
18276        .unindent(),
18277        cx,
18278    )
18279    .await;
18280
18281    assert_indent_guides(
18282        0..6,
18283        vec![
18284            indent_guide(buffer_id, 1, 5, 0),
18285            indent_guide(buffer_id, 3, 4, 1),
18286        ],
18287        None,
18288        &mut cx,
18289    );
18290}
18291
18292#[gpui::test]
18293async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18294    let (buffer_id, mut cx) = setup_indent_guides_editor(
18295        &"
18296    fn main() {
18297        let a = 1;
18298    }"
18299        .unindent(),
18300        cx,
18301    )
18302    .await;
18303
18304    cx.update_editor(|editor, window, cx| {
18305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18306            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18307        });
18308    });
18309
18310    assert_indent_guides(
18311        0..3,
18312        vec![indent_guide(buffer_id, 1, 1, 0)],
18313        Some(vec![0]),
18314        &mut cx,
18315    );
18316}
18317
18318#[gpui::test]
18319async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18320    let (buffer_id, mut cx) = setup_indent_guides_editor(
18321        &"
18322    fn main() {
18323        if 1 == 2 {
18324            let a = 1;
18325        }
18326    }"
18327        .unindent(),
18328        cx,
18329    )
18330    .await;
18331
18332    cx.update_editor(|editor, window, cx| {
18333        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18334            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18335        });
18336    });
18337
18338    assert_indent_guides(
18339        0..4,
18340        vec![
18341            indent_guide(buffer_id, 1, 3, 0),
18342            indent_guide(buffer_id, 2, 2, 1),
18343        ],
18344        Some(vec![1]),
18345        &mut cx,
18346    );
18347
18348    cx.update_editor(|editor, window, cx| {
18349        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18350            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18351        });
18352    });
18353
18354    assert_indent_guides(
18355        0..4,
18356        vec![
18357            indent_guide(buffer_id, 1, 3, 0),
18358            indent_guide(buffer_id, 2, 2, 1),
18359        ],
18360        Some(vec![1]),
18361        &mut cx,
18362    );
18363
18364    cx.update_editor(|editor, window, cx| {
18365        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18366            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18367        });
18368    });
18369
18370    assert_indent_guides(
18371        0..4,
18372        vec![
18373            indent_guide(buffer_id, 1, 3, 0),
18374            indent_guide(buffer_id, 2, 2, 1),
18375        ],
18376        Some(vec![0]),
18377        &mut cx,
18378    );
18379}
18380
18381#[gpui::test]
18382async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18383    let (buffer_id, mut cx) = setup_indent_guides_editor(
18384        &"
18385    fn main() {
18386        let a = 1;
18387
18388        let b = 2;
18389    }"
18390        .unindent(),
18391        cx,
18392    )
18393    .await;
18394
18395    cx.update_editor(|editor, window, cx| {
18396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18397            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18398        });
18399    });
18400
18401    assert_indent_guides(
18402        0..5,
18403        vec![indent_guide(buffer_id, 1, 3, 0)],
18404        Some(vec![0]),
18405        &mut cx,
18406    );
18407}
18408
18409#[gpui::test]
18410async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18411    let (buffer_id, mut cx) = setup_indent_guides_editor(
18412        &"
18413    def m:
18414        a = 1
18415        pass"
18416            .unindent(),
18417        cx,
18418    )
18419    .await;
18420
18421    cx.update_editor(|editor, window, cx| {
18422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18423            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18424        });
18425    });
18426
18427    assert_indent_guides(
18428        0..3,
18429        vec![indent_guide(buffer_id, 1, 2, 0)],
18430        Some(vec![0]),
18431        &mut cx,
18432    );
18433}
18434
18435#[gpui::test]
18436async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18437    init_test(cx, |_| {});
18438    let mut cx = EditorTestContext::new(cx).await;
18439    let text = indoc! {
18440        "
18441        impl A {
18442            fn b() {
18443                0;
18444                3;
18445                5;
18446                6;
18447                7;
18448            }
18449        }
18450        "
18451    };
18452    let base_text = indoc! {
18453        "
18454        impl A {
18455            fn b() {
18456                0;
18457                1;
18458                2;
18459                3;
18460                4;
18461            }
18462            fn c() {
18463                5;
18464                6;
18465                7;
18466            }
18467        }
18468        "
18469    };
18470
18471    cx.update_editor(|editor, window, cx| {
18472        editor.set_text(text, window, cx);
18473
18474        editor.buffer().update(cx, |multibuffer, cx| {
18475            let buffer = multibuffer.as_singleton().unwrap();
18476            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18477
18478            multibuffer.set_all_diff_hunks_expanded(cx);
18479            multibuffer.add_diff(diff, cx);
18480
18481            buffer.read(cx).remote_id()
18482        })
18483    });
18484    cx.run_until_parked();
18485
18486    cx.assert_state_with_diff(
18487        indoc! { "
18488          impl A {
18489              fn b() {
18490                  0;
18491        -         1;
18492        -         2;
18493                  3;
18494        -         4;
18495        -     }
18496        -     fn c() {
18497                  5;
18498                  6;
18499                  7;
18500              }
18501          }
18502          ˇ"
18503        }
18504        .to_string(),
18505    );
18506
18507    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18508        editor
18509            .snapshot(window, cx)
18510            .buffer_snapshot
18511            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18512            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18513            .collect::<Vec<_>>()
18514    });
18515    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18516    assert_eq!(
18517        actual_guides,
18518        vec![
18519            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18520            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18521            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18522        ]
18523    );
18524}
18525
18526#[gpui::test]
18527async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18528    init_test(cx, |_| {});
18529    let mut cx = EditorTestContext::new(cx).await;
18530
18531    let diff_base = r#"
18532        a
18533        b
18534        c
18535        "#
18536    .unindent();
18537
18538    cx.set_state(
18539        &r#"
18540        ˇA
18541        b
18542        C
18543        "#
18544        .unindent(),
18545    );
18546    cx.set_head_text(&diff_base);
18547    cx.update_editor(|editor, window, cx| {
18548        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18549    });
18550    executor.run_until_parked();
18551
18552    let both_hunks_expanded = r#"
18553        - a
18554        + ˇA
18555          b
18556        - c
18557        + C
18558        "#
18559    .unindent();
18560
18561    cx.assert_state_with_diff(both_hunks_expanded.clone());
18562
18563    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18564        let snapshot = editor.snapshot(window, cx);
18565        let hunks = editor
18566            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18567            .collect::<Vec<_>>();
18568        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18569        let buffer_id = hunks[0].buffer_id;
18570        hunks
18571            .into_iter()
18572            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18573            .collect::<Vec<_>>()
18574    });
18575    assert_eq!(hunk_ranges.len(), 2);
18576
18577    cx.update_editor(|editor, _, cx| {
18578        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18579    });
18580    executor.run_until_parked();
18581
18582    let second_hunk_expanded = r#"
18583          ˇA
18584          b
18585        - c
18586        + C
18587        "#
18588    .unindent();
18589
18590    cx.assert_state_with_diff(second_hunk_expanded);
18591
18592    cx.update_editor(|editor, _, cx| {
18593        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18594    });
18595    executor.run_until_parked();
18596
18597    cx.assert_state_with_diff(both_hunks_expanded.clone());
18598
18599    cx.update_editor(|editor, _, cx| {
18600        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18601    });
18602    executor.run_until_parked();
18603
18604    let first_hunk_expanded = r#"
18605        - a
18606        + ˇA
18607          b
18608          C
18609        "#
18610    .unindent();
18611
18612    cx.assert_state_with_diff(first_hunk_expanded);
18613
18614    cx.update_editor(|editor, _, cx| {
18615        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18616    });
18617    executor.run_until_parked();
18618
18619    cx.assert_state_with_diff(both_hunks_expanded);
18620
18621    cx.set_state(
18622        &r#"
18623        ˇA
18624        b
18625        "#
18626        .unindent(),
18627    );
18628    cx.run_until_parked();
18629
18630    // TODO this cursor position seems bad
18631    cx.assert_state_with_diff(
18632        r#"
18633        - ˇa
18634        + A
18635          b
18636        "#
18637        .unindent(),
18638    );
18639
18640    cx.update_editor(|editor, window, cx| {
18641        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18642    });
18643
18644    cx.assert_state_with_diff(
18645        r#"
18646            - ˇa
18647            + A
18648              b
18649            - c
18650            "#
18651        .unindent(),
18652    );
18653
18654    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18655        let snapshot = editor.snapshot(window, cx);
18656        let hunks = editor
18657            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18658            .collect::<Vec<_>>();
18659        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18660        let buffer_id = hunks[0].buffer_id;
18661        hunks
18662            .into_iter()
18663            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18664            .collect::<Vec<_>>()
18665    });
18666    assert_eq!(hunk_ranges.len(), 2);
18667
18668    cx.update_editor(|editor, _, cx| {
18669        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18670    });
18671    executor.run_until_parked();
18672
18673    cx.assert_state_with_diff(
18674        r#"
18675        - ˇa
18676        + A
18677          b
18678        "#
18679        .unindent(),
18680    );
18681}
18682
18683#[gpui::test]
18684async fn test_toggle_deletion_hunk_at_start_of_file(
18685    executor: BackgroundExecutor,
18686    cx: &mut TestAppContext,
18687) {
18688    init_test(cx, |_| {});
18689    let mut cx = EditorTestContext::new(cx).await;
18690
18691    let diff_base = r#"
18692        a
18693        b
18694        c
18695        "#
18696    .unindent();
18697
18698    cx.set_state(
18699        &r#"
18700        ˇb
18701        c
18702        "#
18703        .unindent(),
18704    );
18705    cx.set_head_text(&diff_base);
18706    cx.update_editor(|editor, window, cx| {
18707        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18708    });
18709    executor.run_until_parked();
18710
18711    let hunk_expanded = r#"
18712        - a
18713          ˇb
18714          c
18715        "#
18716    .unindent();
18717
18718    cx.assert_state_with_diff(hunk_expanded.clone());
18719
18720    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18721        let snapshot = editor.snapshot(window, cx);
18722        let hunks = editor
18723            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18724            .collect::<Vec<_>>();
18725        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18726        let buffer_id = hunks[0].buffer_id;
18727        hunks
18728            .into_iter()
18729            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18730            .collect::<Vec<_>>()
18731    });
18732    assert_eq!(hunk_ranges.len(), 1);
18733
18734    cx.update_editor(|editor, _, cx| {
18735        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18736    });
18737    executor.run_until_parked();
18738
18739    let hunk_collapsed = r#"
18740          ˇb
18741          c
18742        "#
18743    .unindent();
18744
18745    cx.assert_state_with_diff(hunk_collapsed);
18746
18747    cx.update_editor(|editor, _, cx| {
18748        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18749    });
18750    executor.run_until_parked();
18751
18752    cx.assert_state_with_diff(hunk_expanded.clone());
18753}
18754
18755#[gpui::test]
18756async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18757    init_test(cx, |_| {});
18758
18759    let fs = FakeFs::new(cx.executor());
18760    fs.insert_tree(
18761        path!("/test"),
18762        json!({
18763            ".git": {},
18764            "file-1": "ONE\n",
18765            "file-2": "TWO\n",
18766            "file-3": "THREE\n",
18767        }),
18768    )
18769    .await;
18770
18771    fs.set_head_for_repo(
18772        path!("/test/.git").as_ref(),
18773        &[
18774            ("file-1".into(), "one\n".into()),
18775            ("file-2".into(), "two\n".into()),
18776            ("file-3".into(), "three\n".into()),
18777        ],
18778        "deadbeef",
18779    );
18780
18781    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18782    let mut buffers = vec![];
18783    for i in 1..=3 {
18784        let buffer = project
18785            .update(cx, |project, cx| {
18786                let path = format!(path!("/test/file-{}"), i);
18787                project.open_local_buffer(path, cx)
18788            })
18789            .await
18790            .unwrap();
18791        buffers.push(buffer);
18792    }
18793
18794    let multibuffer = cx.new(|cx| {
18795        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18796        multibuffer.set_all_diff_hunks_expanded(cx);
18797        for buffer in &buffers {
18798            let snapshot = buffer.read(cx).snapshot();
18799            multibuffer.set_excerpts_for_path(
18800                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18801                buffer.clone(),
18802                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18803                DEFAULT_MULTIBUFFER_CONTEXT,
18804                cx,
18805            );
18806        }
18807        multibuffer
18808    });
18809
18810    let editor = cx.add_window(|window, cx| {
18811        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18812    });
18813    cx.run_until_parked();
18814
18815    let snapshot = editor
18816        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18817        .unwrap();
18818    let hunks = snapshot
18819        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18820        .map(|hunk| match hunk {
18821            DisplayDiffHunk::Unfolded {
18822                display_row_range, ..
18823            } => display_row_range,
18824            DisplayDiffHunk::Folded { .. } => unreachable!(),
18825        })
18826        .collect::<Vec<_>>();
18827    assert_eq!(
18828        hunks,
18829        [
18830            DisplayRow(2)..DisplayRow(4),
18831            DisplayRow(7)..DisplayRow(9),
18832            DisplayRow(12)..DisplayRow(14),
18833        ]
18834    );
18835}
18836
18837#[gpui::test]
18838async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18839    init_test(cx, |_| {});
18840
18841    let mut cx = EditorTestContext::new(cx).await;
18842    cx.set_head_text(indoc! { "
18843        one
18844        two
18845        three
18846        four
18847        five
18848        "
18849    });
18850    cx.set_index_text(indoc! { "
18851        one
18852        two
18853        three
18854        four
18855        five
18856        "
18857    });
18858    cx.set_state(indoc! {"
18859        one
18860        TWO
18861        ˇTHREE
18862        FOUR
18863        five
18864    "});
18865    cx.run_until_parked();
18866    cx.update_editor(|editor, window, cx| {
18867        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18868    });
18869    cx.run_until_parked();
18870    cx.assert_index_text(Some(indoc! {"
18871        one
18872        TWO
18873        THREE
18874        FOUR
18875        five
18876    "}));
18877    cx.set_state(indoc! { "
18878        one
18879        TWO
18880        ˇTHREE-HUNDRED
18881        FOUR
18882        five
18883    "});
18884    cx.run_until_parked();
18885    cx.update_editor(|editor, window, cx| {
18886        let snapshot = editor.snapshot(window, cx);
18887        let hunks = editor
18888            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18889            .collect::<Vec<_>>();
18890        assert_eq!(hunks.len(), 1);
18891        assert_eq!(
18892            hunks[0].status(),
18893            DiffHunkStatus {
18894                kind: DiffHunkStatusKind::Modified,
18895                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18896            }
18897        );
18898
18899        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18900    });
18901    cx.run_until_parked();
18902    cx.assert_index_text(Some(indoc! {"
18903        one
18904        TWO
18905        THREE-HUNDRED
18906        FOUR
18907        five
18908    "}));
18909}
18910
18911#[gpui::test]
18912fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18913    init_test(cx, |_| {});
18914
18915    let editor = cx.add_window(|window, cx| {
18916        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18917        build_editor(buffer, window, cx)
18918    });
18919
18920    let render_args = Arc::new(Mutex::new(None));
18921    let snapshot = editor
18922        .update(cx, |editor, window, cx| {
18923            let snapshot = editor.buffer().read(cx).snapshot(cx);
18924            let range =
18925                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18926
18927            struct RenderArgs {
18928                row: MultiBufferRow,
18929                folded: bool,
18930                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18931            }
18932
18933            let crease = Crease::inline(
18934                range,
18935                FoldPlaceholder::test(),
18936                {
18937                    let toggle_callback = render_args.clone();
18938                    move |row, folded, callback, _window, _cx| {
18939                        *toggle_callback.lock() = Some(RenderArgs {
18940                            row,
18941                            folded,
18942                            callback,
18943                        });
18944                        div()
18945                    }
18946                },
18947                |_row, _folded, _window, _cx| div(),
18948            );
18949
18950            editor.insert_creases(Some(crease), cx);
18951            let snapshot = editor.snapshot(window, cx);
18952            let _div = snapshot.render_crease_toggle(
18953                MultiBufferRow(1),
18954                false,
18955                cx.entity().clone(),
18956                window,
18957                cx,
18958            );
18959            snapshot
18960        })
18961        .unwrap();
18962
18963    let render_args = render_args.lock().take().unwrap();
18964    assert_eq!(render_args.row, MultiBufferRow(1));
18965    assert!(!render_args.folded);
18966    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18967
18968    cx.update_window(*editor, |_, window, cx| {
18969        (render_args.callback)(true, window, cx)
18970    })
18971    .unwrap();
18972    let snapshot = editor
18973        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18974        .unwrap();
18975    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18976
18977    cx.update_window(*editor, |_, window, cx| {
18978        (render_args.callback)(false, window, cx)
18979    })
18980    .unwrap();
18981    let snapshot = editor
18982        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18983        .unwrap();
18984    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18985}
18986
18987#[gpui::test]
18988async fn test_input_text(cx: &mut TestAppContext) {
18989    init_test(cx, |_| {});
18990    let mut cx = EditorTestContext::new(cx).await;
18991
18992    cx.set_state(
18993        &r#"ˇone
18994        two
18995
18996        three
18997        fourˇ
18998        five
18999
19000        siˇx"#
19001            .unindent(),
19002    );
19003
19004    cx.dispatch_action(HandleInput(String::new()));
19005    cx.assert_editor_state(
19006        &r#"ˇone
19007        two
19008
19009        three
19010        fourˇ
19011        five
19012
19013        siˇx"#
19014            .unindent(),
19015    );
19016
19017    cx.dispatch_action(HandleInput("AAAA".to_string()));
19018    cx.assert_editor_state(
19019        &r#"AAAAˇone
19020        two
19021
19022        three
19023        fourAAAAˇ
19024        five
19025
19026        siAAAAˇx"#
19027            .unindent(),
19028    );
19029}
19030
19031#[gpui::test]
19032async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19033    init_test(cx, |_| {});
19034
19035    let mut cx = EditorTestContext::new(cx).await;
19036    cx.set_state(
19037        r#"let foo = 1;
19038let foo = 2;
19039let foo = 3;
19040let fooˇ = 4;
19041let foo = 5;
19042let foo = 6;
19043let foo = 7;
19044let foo = 8;
19045let foo = 9;
19046let foo = 10;
19047let foo = 11;
19048let foo = 12;
19049let foo = 13;
19050let foo = 14;
19051let foo = 15;"#,
19052    );
19053
19054    cx.update_editor(|e, window, cx| {
19055        assert_eq!(
19056            e.next_scroll_position,
19057            NextScrollCursorCenterTopBottom::Center,
19058            "Default next scroll direction is center",
19059        );
19060
19061        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19062        assert_eq!(
19063            e.next_scroll_position,
19064            NextScrollCursorCenterTopBottom::Top,
19065            "After center, next scroll direction should be top",
19066        );
19067
19068        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19069        assert_eq!(
19070            e.next_scroll_position,
19071            NextScrollCursorCenterTopBottom::Bottom,
19072            "After top, next scroll direction should be bottom",
19073        );
19074
19075        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19076        assert_eq!(
19077            e.next_scroll_position,
19078            NextScrollCursorCenterTopBottom::Center,
19079            "After bottom, scrolling should start over",
19080        );
19081
19082        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19083        assert_eq!(
19084            e.next_scroll_position,
19085            NextScrollCursorCenterTopBottom::Top,
19086            "Scrolling continues if retriggered fast enough"
19087        );
19088    });
19089
19090    cx.executor()
19091        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19092    cx.executor().run_until_parked();
19093    cx.update_editor(|e, _, _| {
19094        assert_eq!(
19095            e.next_scroll_position,
19096            NextScrollCursorCenterTopBottom::Center,
19097            "If scrolling is not triggered fast enough, it should reset"
19098        );
19099    });
19100}
19101
19102#[gpui::test]
19103async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19104    init_test(cx, |_| {});
19105    let mut cx = EditorLspTestContext::new_rust(
19106        lsp::ServerCapabilities {
19107            definition_provider: Some(lsp::OneOf::Left(true)),
19108            references_provider: Some(lsp::OneOf::Left(true)),
19109            ..lsp::ServerCapabilities::default()
19110        },
19111        cx,
19112    )
19113    .await;
19114
19115    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19116        let go_to_definition = cx
19117            .lsp
19118            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19119                move |params, _| async move {
19120                    if empty_go_to_definition {
19121                        Ok(None)
19122                    } else {
19123                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19124                            uri: params.text_document_position_params.text_document.uri,
19125                            range: lsp::Range::new(
19126                                lsp::Position::new(4, 3),
19127                                lsp::Position::new(4, 6),
19128                            ),
19129                        })))
19130                    }
19131                },
19132            );
19133        let references = cx
19134            .lsp
19135            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19136                Ok(Some(vec![lsp::Location {
19137                    uri: params.text_document_position.text_document.uri,
19138                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19139                }]))
19140            });
19141        (go_to_definition, references)
19142    };
19143
19144    cx.set_state(
19145        &r#"fn one() {
19146            let mut a = ˇtwo();
19147        }
19148
19149        fn two() {}"#
19150            .unindent(),
19151    );
19152    set_up_lsp_handlers(false, &mut cx);
19153    let navigated = cx
19154        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19155        .await
19156        .expect("Failed to navigate to definition");
19157    assert_eq!(
19158        navigated,
19159        Navigated::Yes,
19160        "Should have navigated to definition from the GetDefinition response"
19161    );
19162    cx.assert_editor_state(
19163        &r#"fn one() {
19164            let mut a = two();
19165        }
19166
19167        fn «twoˇ»() {}"#
19168            .unindent(),
19169    );
19170
19171    let editors = cx.update_workspace(|workspace, _, cx| {
19172        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19173    });
19174    cx.update_editor(|_, _, test_editor_cx| {
19175        assert_eq!(
19176            editors.len(),
19177            1,
19178            "Initially, only one, test, editor should be open in the workspace"
19179        );
19180        assert_eq!(
19181            test_editor_cx.entity(),
19182            editors.last().expect("Asserted len is 1").clone()
19183        );
19184    });
19185
19186    set_up_lsp_handlers(true, &mut cx);
19187    let navigated = cx
19188        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19189        .await
19190        .expect("Failed to navigate to lookup references");
19191    assert_eq!(
19192        navigated,
19193        Navigated::Yes,
19194        "Should have navigated to references as a fallback after empty GoToDefinition response"
19195    );
19196    // We should not change the selections in the existing file,
19197    // if opening another milti buffer with the references
19198    cx.assert_editor_state(
19199        &r#"fn one() {
19200            let mut a = two();
19201        }
19202
19203        fn «twoˇ»() {}"#
19204            .unindent(),
19205    );
19206    let editors = cx.update_workspace(|workspace, _, cx| {
19207        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19208    });
19209    cx.update_editor(|_, _, test_editor_cx| {
19210        assert_eq!(
19211            editors.len(),
19212            2,
19213            "After falling back to references search, we open a new editor with the results"
19214        );
19215        let references_fallback_text = editors
19216            .into_iter()
19217            .find(|new_editor| *new_editor != test_editor_cx.entity())
19218            .expect("Should have one non-test editor now")
19219            .read(test_editor_cx)
19220            .text(test_editor_cx);
19221        assert_eq!(
19222            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19223            "Should use the range from the references response and not the GoToDefinition one"
19224        );
19225    });
19226}
19227
19228#[gpui::test]
19229async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19230    init_test(cx, |_| {});
19231    cx.update(|cx| {
19232        let mut editor_settings = EditorSettings::get_global(cx).clone();
19233        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19234        EditorSettings::override_global(editor_settings, cx);
19235    });
19236    let mut cx = EditorLspTestContext::new_rust(
19237        lsp::ServerCapabilities {
19238            definition_provider: Some(lsp::OneOf::Left(true)),
19239            references_provider: Some(lsp::OneOf::Left(true)),
19240            ..lsp::ServerCapabilities::default()
19241        },
19242        cx,
19243    )
19244    .await;
19245    let original_state = r#"fn one() {
19246        let mut a = ˇtwo();
19247    }
19248
19249    fn two() {}"#
19250        .unindent();
19251    cx.set_state(&original_state);
19252
19253    let mut go_to_definition = cx
19254        .lsp
19255        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19256            move |_, _| async move { Ok(None) },
19257        );
19258    let _references = cx
19259        .lsp
19260        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19261            panic!("Should not call for references with no go to definition fallback")
19262        });
19263
19264    let navigated = cx
19265        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19266        .await
19267        .expect("Failed to navigate to lookup references");
19268    go_to_definition
19269        .next()
19270        .await
19271        .expect("Should have called the go_to_definition handler");
19272
19273    assert_eq!(
19274        navigated,
19275        Navigated::No,
19276        "Should have navigated to references as a fallback after empty GoToDefinition response"
19277    );
19278    cx.assert_editor_state(&original_state);
19279    let editors = cx.update_workspace(|workspace, _, cx| {
19280        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19281    });
19282    cx.update_editor(|_, _, _| {
19283        assert_eq!(
19284            editors.len(),
19285            1,
19286            "After unsuccessful fallback, no other editor should have been opened"
19287        );
19288    });
19289}
19290
19291#[gpui::test]
19292async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19293    init_test(cx, |_| {});
19294
19295    let language = Arc::new(Language::new(
19296        LanguageConfig::default(),
19297        Some(tree_sitter_rust::LANGUAGE.into()),
19298    ));
19299
19300    let text = r#"
19301        #[cfg(test)]
19302        mod tests() {
19303            #[test]
19304            fn runnable_1() {
19305                let a = 1;
19306            }
19307
19308            #[test]
19309            fn runnable_2() {
19310                let a = 1;
19311                let b = 2;
19312            }
19313        }
19314    "#
19315    .unindent();
19316
19317    let fs = FakeFs::new(cx.executor());
19318    fs.insert_file("/file.rs", Default::default()).await;
19319
19320    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19321    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19322    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19323    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19324    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19325
19326    let editor = cx.new_window_entity(|window, cx| {
19327        Editor::new(
19328            EditorMode::full(),
19329            multi_buffer,
19330            Some(project.clone()),
19331            window,
19332            cx,
19333        )
19334    });
19335
19336    editor.update_in(cx, |editor, window, cx| {
19337        let snapshot = editor.buffer().read(cx).snapshot(cx);
19338        editor.tasks.insert(
19339            (buffer.read(cx).remote_id(), 3),
19340            RunnableTasks {
19341                templates: vec![],
19342                offset: snapshot.anchor_before(43),
19343                column: 0,
19344                extra_variables: HashMap::default(),
19345                context_range: BufferOffset(43)..BufferOffset(85),
19346            },
19347        );
19348        editor.tasks.insert(
19349            (buffer.read(cx).remote_id(), 8),
19350            RunnableTasks {
19351                templates: vec![],
19352                offset: snapshot.anchor_before(86),
19353                column: 0,
19354                extra_variables: HashMap::default(),
19355                context_range: BufferOffset(86)..BufferOffset(191),
19356            },
19357        );
19358
19359        // Test finding task when cursor is inside function body
19360        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19361            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19362        });
19363        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19364        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19365
19366        // Test finding task when cursor is on function name
19367        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19368            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19369        });
19370        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19371        assert_eq!(row, 8, "Should find task when cursor is on function name");
19372    });
19373}
19374
19375#[gpui::test]
19376async fn test_folding_buffers(cx: &mut TestAppContext) {
19377    init_test(cx, |_| {});
19378
19379    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19380    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19381    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19382
19383    let fs = FakeFs::new(cx.executor());
19384    fs.insert_tree(
19385        path!("/a"),
19386        json!({
19387            "first.rs": sample_text_1,
19388            "second.rs": sample_text_2,
19389            "third.rs": sample_text_3,
19390        }),
19391    )
19392    .await;
19393    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19394    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19395    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19396    let worktree = project.update(cx, |project, cx| {
19397        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19398        assert_eq!(worktrees.len(), 1);
19399        worktrees.pop().unwrap()
19400    });
19401    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19402
19403    let buffer_1 = project
19404        .update(cx, |project, cx| {
19405            project.open_buffer((worktree_id, "first.rs"), cx)
19406        })
19407        .await
19408        .unwrap();
19409    let buffer_2 = project
19410        .update(cx, |project, cx| {
19411            project.open_buffer((worktree_id, "second.rs"), cx)
19412        })
19413        .await
19414        .unwrap();
19415    let buffer_3 = project
19416        .update(cx, |project, cx| {
19417            project.open_buffer((worktree_id, "third.rs"), cx)
19418        })
19419        .await
19420        .unwrap();
19421
19422    let multi_buffer = cx.new(|cx| {
19423        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19424        multi_buffer.push_excerpts(
19425            buffer_1.clone(),
19426            [
19427                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19428                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19429                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19430            ],
19431            cx,
19432        );
19433        multi_buffer.push_excerpts(
19434            buffer_2.clone(),
19435            [
19436                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19437                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19438                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19439            ],
19440            cx,
19441        );
19442        multi_buffer.push_excerpts(
19443            buffer_3.clone(),
19444            [
19445                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19446                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19447                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19448            ],
19449            cx,
19450        );
19451        multi_buffer
19452    });
19453    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19454        Editor::new(
19455            EditorMode::full(),
19456            multi_buffer.clone(),
19457            Some(project.clone()),
19458            window,
19459            cx,
19460        )
19461    });
19462
19463    assert_eq!(
19464        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19465        "\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",
19466    );
19467
19468    multi_buffer_editor.update(cx, |editor, cx| {
19469        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19470    });
19471    assert_eq!(
19472        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19473        "\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",
19474        "After folding the first buffer, its text should not be displayed"
19475    );
19476
19477    multi_buffer_editor.update(cx, |editor, cx| {
19478        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19479    });
19480    assert_eq!(
19481        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19482        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19483        "After folding the second buffer, its text should not be displayed"
19484    );
19485
19486    multi_buffer_editor.update(cx, |editor, cx| {
19487        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19488    });
19489    assert_eq!(
19490        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19491        "\n\n\n\n\n",
19492        "After folding the third buffer, its text should not be displayed"
19493    );
19494
19495    // Emulate selection inside the fold logic, that should work
19496    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19497        editor
19498            .snapshot(window, cx)
19499            .next_line_boundary(Point::new(0, 4));
19500    });
19501
19502    multi_buffer_editor.update(cx, |editor, cx| {
19503        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19504    });
19505    assert_eq!(
19506        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19507        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19508        "After unfolding the second buffer, its text should be displayed"
19509    );
19510
19511    // Typing inside of buffer 1 causes that buffer to be unfolded.
19512    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19513        assert_eq!(
19514            multi_buffer
19515                .read(cx)
19516                .snapshot(cx)
19517                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19518                .collect::<String>(),
19519            "bbbb"
19520        );
19521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19522            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19523        });
19524        editor.handle_input("B", window, cx);
19525    });
19526
19527    assert_eq!(
19528        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19529        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19530        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19531    );
19532
19533    multi_buffer_editor.update(cx, |editor, cx| {
19534        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19535    });
19536    assert_eq!(
19537        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19538        "\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",
19539        "After unfolding the all buffers, all original text should be displayed"
19540    );
19541}
19542
19543#[gpui::test]
19544async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19545    init_test(cx, |_| {});
19546
19547    let sample_text_1 = "1111\n2222\n3333".to_string();
19548    let sample_text_2 = "4444\n5555\n6666".to_string();
19549    let sample_text_3 = "7777\n8888\n9999".to_string();
19550
19551    let fs = FakeFs::new(cx.executor());
19552    fs.insert_tree(
19553        path!("/a"),
19554        json!({
19555            "first.rs": sample_text_1,
19556            "second.rs": sample_text_2,
19557            "third.rs": sample_text_3,
19558        }),
19559    )
19560    .await;
19561    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19562    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19563    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19564    let worktree = project.update(cx, |project, cx| {
19565        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19566        assert_eq!(worktrees.len(), 1);
19567        worktrees.pop().unwrap()
19568    });
19569    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19570
19571    let buffer_1 = project
19572        .update(cx, |project, cx| {
19573            project.open_buffer((worktree_id, "first.rs"), cx)
19574        })
19575        .await
19576        .unwrap();
19577    let buffer_2 = project
19578        .update(cx, |project, cx| {
19579            project.open_buffer((worktree_id, "second.rs"), cx)
19580        })
19581        .await
19582        .unwrap();
19583    let buffer_3 = project
19584        .update(cx, |project, cx| {
19585            project.open_buffer((worktree_id, "third.rs"), cx)
19586        })
19587        .await
19588        .unwrap();
19589
19590    let multi_buffer = cx.new(|cx| {
19591        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19592        multi_buffer.push_excerpts(
19593            buffer_1.clone(),
19594            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19595            cx,
19596        );
19597        multi_buffer.push_excerpts(
19598            buffer_2.clone(),
19599            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19600            cx,
19601        );
19602        multi_buffer.push_excerpts(
19603            buffer_3.clone(),
19604            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19605            cx,
19606        );
19607        multi_buffer
19608    });
19609
19610    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19611        Editor::new(
19612            EditorMode::full(),
19613            multi_buffer,
19614            Some(project.clone()),
19615            window,
19616            cx,
19617        )
19618    });
19619
19620    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19621    assert_eq!(
19622        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19623        full_text,
19624    );
19625
19626    multi_buffer_editor.update(cx, |editor, cx| {
19627        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19628    });
19629    assert_eq!(
19630        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19631        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19632        "After folding the first buffer, its text should not be displayed"
19633    );
19634
19635    multi_buffer_editor.update(cx, |editor, cx| {
19636        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19637    });
19638
19639    assert_eq!(
19640        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19641        "\n\n\n\n\n\n7777\n8888\n9999",
19642        "After folding the second buffer, its text should not be displayed"
19643    );
19644
19645    multi_buffer_editor.update(cx, |editor, cx| {
19646        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19647    });
19648    assert_eq!(
19649        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19650        "\n\n\n\n\n",
19651        "After folding the third buffer, its text should not be displayed"
19652    );
19653
19654    multi_buffer_editor.update(cx, |editor, cx| {
19655        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19656    });
19657    assert_eq!(
19658        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19659        "\n\n\n\n4444\n5555\n6666\n\n",
19660        "After unfolding the second buffer, its text should be displayed"
19661    );
19662
19663    multi_buffer_editor.update(cx, |editor, cx| {
19664        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19665    });
19666    assert_eq!(
19667        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19668        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19669        "After unfolding the first buffer, its text should be displayed"
19670    );
19671
19672    multi_buffer_editor.update(cx, |editor, cx| {
19673        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19674    });
19675    assert_eq!(
19676        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19677        full_text,
19678        "After unfolding all buffers, all original text should be displayed"
19679    );
19680}
19681
19682#[gpui::test]
19683async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19684    init_test(cx, |_| {});
19685
19686    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19687
19688    let fs = FakeFs::new(cx.executor());
19689    fs.insert_tree(
19690        path!("/a"),
19691        json!({
19692            "main.rs": sample_text,
19693        }),
19694    )
19695    .await;
19696    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19697    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19698    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19699    let worktree = project.update(cx, |project, cx| {
19700        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19701        assert_eq!(worktrees.len(), 1);
19702        worktrees.pop().unwrap()
19703    });
19704    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19705
19706    let buffer_1 = project
19707        .update(cx, |project, cx| {
19708            project.open_buffer((worktree_id, "main.rs"), cx)
19709        })
19710        .await
19711        .unwrap();
19712
19713    let multi_buffer = cx.new(|cx| {
19714        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19715        multi_buffer.push_excerpts(
19716            buffer_1.clone(),
19717            [ExcerptRange::new(
19718                Point::new(0, 0)
19719                    ..Point::new(
19720                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19721                        0,
19722                    ),
19723            )],
19724            cx,
19725        );
19726        multi_buffer
19727    });
19728    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19729        Editor::new(
19730            EditorMode::full(),
19731            multi_buffer,
19732            Some(project.clone()),
19733            window,
19734            cx,
19735        )
19736    });
19737
19738    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19739    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19740        enum TestHighlight {}
19741        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19742        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19743        editor.highlight_text::<TestHighlight>(
19744            vec![highlight_range.clone()],
19745            HighlightStyle::color(Hsla::green()),
19746            cx,
19747        );
19748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19749            s.select_ranges(Some(highlight_range))
19750        });
19751    });
19752
19753    let full_text = format!("\n\n{sample_text}");
19754    assert_eq!(
19755        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19756        full_text,
19757    );
19758}
19759
19760#[gpui::test]
19761async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19762    init_test(cx, |_| {});
19763    cx.update(|cx| {
19764        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19765            "keymaps/default-linux.json",
19766            cx,
19767        )
19768        .unwrap();
19769        cx.bind_keys(default_key_bindings);
19770    });
19771
19772    let (editor, cx) = cx.add_window_view(|window, cx| {
19773        let multi_buffer = MultiBuffer::build_multi(
19774            [
19775                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19776                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19777                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19778                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19779            ],
19780            cx,
19781        );
19782        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19783
19784        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19785        // fold all but the second buffer, so that we test navigating between two
19786        // adjacent folded buffers, as well as folded buffers at the start and
19787        // end the multibuffer
19788        editor.fold_buffer(buffer_ids[0], cx);
19789        editor.fold_buffer(buffer_ids[2], cx);
19790        editor.fold_buffer(buffer_ids[3], cx);
19791
19792        editor
19793    });
19794    cx.simulate_resize(size(px(1000.), px(1000.)));
19795
19796    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19797    cx.assert_excerpts_with_selections(indoc! {"
19798        [EXCERPT]
19799        ˇ[FOLDED]
19800        [EXCERPT]
19801        a1
19802        b1
19803        [EXCERPT]
19804        [FOLDED]
19805        [EXCERPT]
19806        [FOLDED]
19807        "
19808    });
19809    cx.simulate_keystroke("down");
19810    cx.assert_excerpts_with_selections(indoc! {"
19811        [EXCERPT]
19812        [FOLDED]
19813        [EXCERPT]
19814        ˇa1
19815        b1
19816        [EXCERPT]
19817        [FOLDED]
19818        [EXCERPT]
19819        [FOLDED]
19820        "
19821    });
19822    cx.simulate_keystroke("down");
19823    cx.assert_excerpts_with_selections(indoc! {"
19824        [EXCERPT]
19825        [FOLDED]
19826        [EXCERPT]
19827        a1
19828        ˇb1
19829        [EXCERPT]
19830        [FOLDED]
19831        [EXCERPT]
19832        [FOLDED]
19833        "
19834    });
19835    cx.simulate_keystroke("down");
19836    cx.assert_excerpts_with_selections(indoc! {"
19837        [EXCERPT]
19838        [FOLDED]
19839        [EXCERPT]
19840        a1
19841        b1
19842        ˇ[EXCERPT]
19843        [FOLDED]
19844        [EXCERPT]
19845        [FOLDED]
19846        "
19847    });
19848    cx.simulate_keystroke("down");
19849    cx.assert_excerpts_with_selections(indoc! {"
19850        [EXCERPT]
19851        [FOLDED]
19852        [EXCERPT]
19853        a1
19854        b1
19855        [EXCERPT]
19856        ˇ[FOLDED]
19857        [EXCERPT]
19858        [FOLDED]
19859        "
19860    });
19861    for _ in 0..5 {
19862        cx.simulate_keystroke("down");
19863        cx.assert_excerpts_with_selections(indoc! {"
19864            [EXCERPT]
19865            [FOLDED]
19866            [EXCERPT]
19867            a1
19868            b1
19869            [EXCERPT]
19870            [FOLDED]
19871            [EXCERPT]
19872            ˇ[FOLDED]
19873            "
19874        });
19875    }
19876
19877    cx.simulate_keystroke("up");
19878    cx.assert_excerpts_with_selections(indoc! {"
19879        [EXCERPT]
19880        [FOLDED]
19881        [EXCERPT]
19882        a1
19883        b1
19884        [EXCERPT]
19885        ˇ[FOLDED]
19886        [EXCERPT]
19887        [FOLDED]
19888        "
19889    });
19890    cx.simulate_keystroke("up");
19891    cx.assert_excerpts_with_selections(indoc! {"
19892        [EXCERPT]
19893        [FOLDED]
19894        [EXCERPT]
19895        a1
19896        b1
19897        ˇ[EXCERPT]
19898        [FOLDED]
19899        [EXCERPT]
19900        [FOLDED]
19901        "
19902    });
19903    cx.simulate_keystroke("up");
19904    cx.assert_excerpts_with_selections(indoc! {"
19905        [EXCERPT]
19906        [FOLDED]
19907        [EXCERPT]
19908        a1
19909        ˇb1
19910        [EXCERPT]
19911        [FOLDED]
19912        [EXCERPT]
19913        [FOLDED]
19914        "
19915    });
19916    cx.simulate_keystroke("up");
19917    cx.assert_excerpts_with_selections(indoc! {"
19918        [EXCERPT]
19919        [FOLDED]
19920        [EXCERPT]
19921        ˇa1
19922        b1
19923        [EXCERPT]
19924        [FOLDED]
19925        [EXCERPT]
19926        [FOLDED]
19927        "
19928    });
19929    for _ in 0..5 {
19930        cx.simulate_keystroke("up");
19931        cx.assert_excerpts_with_selections(indoc! {"
19932            [EXCERPT]
19933            ˇ[FOLDED]
19934            [EXCERPT]
19935            a1
19936            b1
19937            [EXCERPT]
19938            [FOLDED]
19939            [EXCERPT]
19940            [FOLDED]
19941            "
19942        });
19943    }
19944}
19945
19946#[gpui::test]
19947async fn test_inline_completion_text(cx: &mut TestAppContext) {
19948    init_test(cx, |_| {});
19949
19950    // Simple insertion
19951    assert_highlighted_edits(
19952        "Hello, world!",
19953        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19954        true,
19955        cx,
19956        |highlighted_edits, cx| {
19957            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19958            assert_eq!(highlighted_edits.highlights.len(), 1);
19959            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19960            assert_eq!(
19961                highlighted_edits.highlights[0].1.background_color,
19962                Some(cx.theme().status().created_background)
19963            );
19964        },
19965    )
19966    .await;
19967
19968    // Replacement
19969    assert_highlighted_edits(
19970        "This is a test.",
19971        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19972        false,
19973        cx,
19974        |highlighted_edits, cx| {
19975            assert_eq!(highlighted_edits.text, "That is a test.");
19976            assert_eq!(highlighted_edits.highlights.len(), 1);
19977            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19978            assert_eq!(
19979                highlighted_edits.highlights[0].1.background_color,
19980                Some(cx.theme().status().created_background)
19981            );
19982        },
19983    )
19984    .await;
19985
19986    // Multiple edits
19987    assert_highlighted_edits(
19988        "Hello, world!",
19989        vec![
19990            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19991            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19992        ],
19993        false,
19994        cx,
19995        |highlighted_edits, cx| {
19996            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19997            assert_eq!(highlighted_edits.highlights.len(), 2);
19998            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19999            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20000            assert_eq!(
20001                highlighted_edits.highlights[0].1.background_color,
20002                Some(cx.theme().status().created_background)
20003            );
20004            assert_eq!(
20005                highlighted_edits.highlights[1].1.background_color,
20006                Some(cx.theme().status().created_background)
20007            );
20008        },
20009    )
20010    .await;
20011
20012    // Multiple lines with edits
20013    assert_highlighted_edits(
20014        "First line\nSecond line\nThird line\nFourth line",
20015        vec![
20016            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20017            (
20018                Point::new(2, 0)..Point::new(2, 10),
20019                "New third line".to_string(),
20020            ),
20021            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20022        ],
20023        false,
20024        cx,
20025        |highlighted_edits, cx| {
20026            assert_eq!(
20027                highlighted_edits.text,
20028                "Second modified\nNew third line\nFourth updated line"
20029            );
20030            assert_eq!(highlighted_edits.highlights.len(), 3);
20031            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20032            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20033            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20034            for highlight in &highlighted_edits.highlights {
20035                assert_eq!(
20036                    highlight.1.background_color,
20037                    Some(cx.theme().status().created_background)
20038                );
20039            }
20040        },
20041    )
20042    .await;
20043}
20044
20045#[gpui::test]
20046async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20047    init_test(cx, |_| {});
20048
20049    // Deletion
20050    assert_highlighted_edits(
20051        "Hello, world!",
20052        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20053        true,
20054        cx,
20055        |highlighted_edits, cx| {
20056            assert_eq!(highlighted_edits.text, "Hello, world!");
20057            assert_eq!(highlighted_edits.highlights.len(), 1);
20058            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20059            assert_eq!(
20060                highlighted_edits.highlights[0].1.background_color,
20061                Some(cx.theme().status().deleted_background)
20062            );
20063        },
20064    )
20065    .await;
20066
20067    // Insertion
20068    assert_highlighted_edits(
20069        "Hello, world!",
20070        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20071        true,
20072        cx,
20073        |highlighted_edits, cx| {
20074            assert_eq!(highlighted_edits.highlights.len(), 1);
20075            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20076            assert_eq!(
20077                highlighted_edits.highlights[0].1.background_color,
20078                Some(cx.theme().status().created_background)
20079            );
20080        },
20081    )
20082    .await;
20083}
20084
20085async fn assert_highlighted_edits(
20086    text: &str,
20087    edits: Vec<(Range<Point>, String)>,
20088    include_deletions: bool,
20089    cx: &mut TestAppContext,
20090    assertion_fn: impl Fn(HighlightedText, &App),
20091) {
20092    let window = cx.add_window(|window, cx| {
20093        let buffer = MultiBuffer::build_simple(text, cx);
20094        Editor::new(EditorMode::full(), buffer, None, window, cx)
20095    });
20096    let cx = &mut VisualTestContext::from_window(*window, cx);
20097
20098    let (buffer, snapshot) = window
20099        .update(cx, |editor, _window, cx| {
20100            (
20101                editor.buffer().clone(),
20102                editor.buffer().read(cx).snapshot(cx),
20103            )
20104        })
20105        .unwrap();
20106
20107    let edits = edits
20108        .into_iter()
20109        .map(|(range, edit)| {
20110            (
20111                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20112                edit,
20113            )
20114        })
20115        .collect::<Vec<_>>();
20116
20117    let text_anchor_edits = edits
20118        .clone()
20119        .into_iter()
20120        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20121        .collect::<Vec<_>>();
20122
20123    let edit_preview = window
20124        .update(cx, |_, _window, cx| {
20125            buffer
20126                .read(cx)
20127                .as_singleton()
20128                .unwrap()
20129                .read(cx)
20130                .preview_edits(text_anchor_edits.into(), cx)
20131        })
20132        .unwrap()
20133        .await;
20134
20135    cx.update(|_window, cx| {
20136        let highlighted_edits = inline_completion_edit_text(
20137            &snapshot.as_singleton().unwrap().2,
20138            &edits,
20139            &edit_preview,
20140            include_deletions,
20141            cx,
20142        );
20143        assertion_fn(highlighted_edits, cx)
20144    });
20145}
20146
20147#[track_caller]
20148fn assert_breakpoint(
20149    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20150    path: &Arc<Path>,
20151    expected: Vec<(u32, Breakpoint)>,
20152) {
20153    if expected.len() == 0usize {
20154        assert!(!breakpoints.contains_key(path), "{}", path.display());
20155    } else {
20156        let mut breakpoint = breakpoints
20157            .get(path)
20158            .unwrap()
20159            .into_iter()
20160            .map(|breakpoint| {
20161                (
20162                    breakpoint.row,
20163                    Breakpoint {
20164                        message: breakpoint.message.clone(),
20165                        state: breakpoint.state,
20166                        condition: breakpoint.condition.clone(),
20167                        hit_condition: breakpoint.hit_condition.clone(),
20168                    },
20169                )
20170            })
20171            .collect::<Vec<_>>();
20172
20173        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20174
20175        assert_eq!(expected, breakpoint);
20176    }
20177}
20178
20179fn add_log_breakpoint_at_cursor(
20180    editor: &mut Editor,
20181    log_message: &str,
20182    window: &mut Window,
20183    cx: &mut Context<Editor>,
20184) {
20185    let (anchor, bp) = editor
20186        .breakpoints_at_cursors(window, cx)
20187        .first()
20188        .and_then(|(anchor, bp)| {
20189            if let Some(bp) = bp {
20190                Some((*anchor, bp.clone()))
20191            } else {
20192                None
20193            }
20194        })
20195        .unwrap_or_else(|| {
20196            let cursor_position: Point = editor.selections.newest(cx).head();
20197
20198            let breakpoint_position = editor
20199                .snapshot(window, cx)
20200                .display_snapshot
20201                .buffer_snapshot
20202                .anchor_before(Point::new(cursor_position.row, 0));
20203
20204            (breakpoint_position, Breakpoint::new_log(&log_message))
20205        });
20206
20207    editor.edit_breakpoint_at_anchor(
20208        anchor,
20209        bp,
20210        BreakpointEditAction::EditLogMessage(log_message.into()),
20211        cx,
20212    );
20213}
20214
20215#[gpui::test]
20216async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20217    init_test(cx, |_| {});
20218
20219    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20220    let fs = FakeFs::new(cx.executor());
20221    fs.insert_tree(
20222        path!("/a"),
20223        json!({
20224            "main.rs": sample_text,
20225        }),
20226    )
20227    .await;
20228    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20229    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20230    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20231
20232    let fs = FakeFs::new(cx.executor());
20233    fs.insert_tree(
20234        path!("/a"),
20235        json!({
20236            "main.rs": sample_text,
20237        }),
20238    )
20239    .await;
20240    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20241    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20242    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20243    let worktree_id = workspace
20244        .update(cx, |workspace, _window, cx| {
20245            workspace.project().update(cx, |project, cx| {
20246                project.worktrees(cx).next().unwrap().read(cx).id()
20247            })
20248        })
20249        .unwrap();
20250
20251    let buffer = project
20252        .update(cx, |project, cx| {
20253            project.open_buffer((worktree_id, "main.rs"), cx)
20254        })
20255        .await
20256        .unwrap();
20257
20258    let (editor, cx) = cx.add_window_view(|window, cx| {
20259        Editor::new(
20260            EditorMode::full(),
20261            MultiBuffer::build_from_buffer(buffer, cx),
20262            Some(project.clone()),
20263            window,
20264            cx,
20265        )
20266    });
20267
20268    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20269    let abs_path = project.read_with(cx, |project, cx| {
20270        project
20271            .absolute_path(&project_path, cx)
20272            .map(|path_buf| Arc::from(path_buf.to_owned()))
20273            .unwrap()
20274    });
20275
20276    // assert we can add breakpoint on the first line
20277    editor.update_in(cx, |editor, window, cx| {
20278        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20279        editor.move_to_end(&MoveToEnd, window, cx);
20280        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20281    });
20282
20283    let breakpoints = editor.update(cx, |editor, cx| {
20284        editor
20285            .breakpoint_store()
20286            .as_ref()
20287            .unwrap()
20288            .read(cx)
20289            .all_source_breakpoints(cx)
20290            .clone()
20291    });
20292
20293    assert_eq!(1, breakpoints.len());
20294    assert_breakpoint(
20295        &breakpoints,
20296        &abs_path,
20297        vec![
20298            (0, Breakpoint::new_standard()),
20299            (3, Breakpoint::new_standard()),
20300        ],
20301    );
20302
20303    editor.update_in(cx, |editor, window, cx| {
20304        editor.move_to_beginning(&MoveToBeginning, window, cx);
20305        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20306    });
20307
20308    let breakpoints = editor.update(cx, |editor, cx| {
20309        editor
20310            .breakpoint_store()
20311            .as_ref()
20312            .unwrap()
20313            .read(cx)
20314            .all_source_breakpoints(cx)
20315            .clone()
20316    });
20317
20318    assert_eq!(1, breakpoints.len());
20319    assert_breakpoint(
20320        &breakpoints,
20321        &abs_path,
20322        vec![(3, Breakpoint::new_standard())],
20323    );
20324
20325    editor.update_in(cx, |editor, window, cx| {
20326        editor.move_to_end(&MoveToEnd, window, cx);
20327        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20328    });
20329
20330    let breakpoints = editor.update(cx, |editor, cx| {
20331        editor
20332            .breakpoint_store()
20333            .as_ref()
20334            .unwrap()
20335            .read(cx)
20336            .all_source_breakpoints(cx)
20337            .clone()
20338    });
20339
20340    assert_eq!(0, breakpoints.len());
20341    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20342}
20343
20344#[gpui::test]
20345async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20346    init_test(cx, |_| {});
20347
20348    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20349
20350    let fs = FakeFs::new(cx.executor());
20351    fs.insert_tree(
20352        path!("/a"),
20353        json!({
20354            "main.rs": sample_text,
20355        }),
20356    )
20357    .await;
20358    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20359    let (workspace, cx) =
20360        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20361
20362    let worktree_id = workspace.update(cx, |workspace, cx| {
20363        workspace.project().update(cx, |project, cx| {
20364            project.worktrees(cx).next().unwrap().read(cx).id()
20365        })
20366    });
20367
20368    let buffer = project
20369        .update(cx, |project, cx| {
20370            project.open_buffer((worktree_id, "main.rs"), cx)
20371        })
20372        .await
20373        .unwrap();
20374
20375    let (editor, cx) = cx.add_window_view(|window, cx| {
20376        Editor::new(
20377            EditorMode::full(),
20378            MultiBuffer::build_from_buffer(buffer, cx),
20379            Some(project.clone()),
20380            window,
20381            cx,
20382        )
20383    });
20384
20385    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20386    let abs_path = project.read_with(cx, |project, cx| {
20387        project
20388            .absolute_path(&project_path, cx)
20389            .map(|path_buf| Arc::from(path_buf.to_owned()))
20390            .unwrap()
20391    });
20392
20393    editor.update_in(cx, |editor, window, cx| {
20394        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20395    });
20396
20397    let breakpoints = editor.update(cx, |editor, cx| {
20398        editor
20399            .breakpoint_store()
20400            .as_ref()
20401            .unwrap()
20402            .read(cx)
20403            .all_source_breakpoints(cx)
20404            .clone()
20405    });
20406
20407    assert_breakpoint(
20408        &breakpoints,
20409        &abs_path,
20410        vec![(0, Breakpoint::new_log("hello world"))],
20411    );
20412
20413    // Removing a log message from a log breakpoint should remove it
20414    editor.update_in(cx, |editor, window, cx| {
20415        add_log_breakpoint_at_cursor(editor, "", window, cx);
20416    });
20417
20418    let breakpoints = editor.update(cx, |editor, cx| {
20419        editor
20420            .breakpoint_store()
20421            .as_ref()
20422            .unwrap()
20423            .read(cx)
20424            .all_source_breakpoints(cx)
20425            .clone()
20426    });
20427
20428    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20429
20430    editor.update_in(cx, |editor, window, cx| {
20431        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20432        editor.move_to_end(&MoveToEnd, window, cx);
20433        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20434        // Not adding a log message to a standard breakpoint shouldn't remove it
20435        add_log_breakpoint_at_cursor(editor, "", window, cx);
20436    });
20437
20438    let breakpoints = editor.update(cx, |editor, cx| {
20439        editor
20440            .breakpoint_store()
20441            .as_ref()
20442            .unwrap()
20443            .read(cx)
20444            .all_source_breakpoints(cx)
20445            .clone()
20446    });
20447
20448    assert_breakpoint(
20449        &breakpoints,
20450        &abs_path,
20451        vec![
20452            (0, Breakpoint::new_standard()),
20453            (3, Breakpoint::new_standard()),
20454        ],
20455    );
20456
20457    editor.update_in(cx, |editor, window, cx| {
20458        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20459    });
20460
20461    let breakpoints = editor.update(cx, |editor, cx| {
20462        editor
20463            .breakpoint_store()
20464            .as_ref()
20465            .unwrap()
20466            .read(cx)
20467            .all_source_breakpoints(cx)
20468            .clone()
20469    });
20470
20471    assert_breakpoint(
20472        &breakpoints,
20473        &abs_path,
20474        vec![
20475            (0, Breakpoint::new_standard()),
20476            (3, Breakpoint::new_log("hello world")),
20477        ],
20478    );
20479
20480    editor.update_in(cx, |editor, window, cx| {
20481        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20482    });
20483
20484    let breakpoints = editor.update(cx, |editor, cx| {
20485        editor
20486            .breakpoint_store()
20487            .as_ref()
20488            .unwrap()
20489            .read(cx)
20490            .all_source_breakpoints(cx)
20491            .clone()
20492    });
20493
20494    assert_breakpoint(
20495        &breakpoints,
20496        &abs_path,
20497        vec![
20498            (0, Breakpoint::new_standard()),
20499            (3, Breakpoint::new_log("hello Earth!!")),
20500        ],
20501    );
20502}
20503
20504/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20505/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20506/// or when breakpoints were placed out of order. This tests for a regression too
20507#[gpui::test]
20508async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20509    init_test(cx, |_| {});
20510
20511    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20512    let fs = FakeFs::new(cx.executor());
20513    fs.insert_tree(
20514        path!("/a"),
20515        json!({
20516            "main.rs": sample_text,
20517        }),
20518    )
20519    .await;
20520    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20521    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20522    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20523
20524    let fs = FakeFs::new(cx.executor());
20525    fs.insert_tree(
20526        path!("/a"),
20527        json!({
20528            "main.rs": sample_text,
20529        }),
20530    )
20531    .await;
20532    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20533    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20534    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20535    let worktree_id = workspace
20536        .update(cx, |workspace, _window, cx| {
20537            workspace.project().update(cx, |project, cx| {
20538                project.worktrees(cx).next().unwrap().read(cx).id()
20539            })
20540        })
20541        .unwrap();
20542
20543    let buffer = project
20544        .update(cx, |project, cx| {
20545            project.open_buffer((worktree_id, "main.rs"), cx)
20546        })
20547        .await
20548        .unwrap();
20549
20550    let (editor, cx) = cx.add_window_view(|window, cx| {
20551        Editor::new(
20552            EditorMode::full(),
20553            MultiBuffer::build_from_buffer(buffer, cx),
20554            Some(project.clone()),
20555            window,
20556            cx,
20557        )
20558    });
20559
20560    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20561    let abs_path = project.read_with(cx, |project, cx| {
20562        project
20563            .absolute_path(&project_path, cx)
20564            .map(|path_buf| Arc::from(path_buf.to_owned()))
20565            .unwrap()
20566    });
20567
20568    // assert we can add breakpoint on the first line
20569    editor.update_in(cx, |editor, window, cx| {
20570        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20571        editor.move_to_end(&MoveToEnd, window, cx);
20572        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20573        editor.move_up(&MoveUp, window, cx);
20574        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20575    });
20576
20577    let breakpoints = editor.update(cx, |editor, cx| {
20578        editor
20579            .breakpoint_store()
20580            .as_ref()
20581            .unwrap()
20582            .read(cx)
20583            .all_source_breakpoints(cx)
20584            .clone()
20585    });
20586
20587    assert_eq!(1, breakpoints.len());
20588    assert_breakpoint(
20589        &breakpoints,
20590        &abs_path,
20591        vec![
20592            (0, Breakpoint::new_standard()),
20593            (2, Breakpoint::new_standard()),
20594            (3, Breakpoint::new_standard()),
20595        ],
20596    );
20597
20598    editor.update_in(cx, |editor, window, cx| {
20599        editor.move_to_beginning(&MoveToBeginning, window, cx);
20600        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20601        editor.move_to_end(&MoveToEnd, window, cx);
20602        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20603        // Disabling a breakpoint that doesn't exist should do nothing
20604        editor.move_up(&MoveUp, window, cx);
20605        editor.move_up(&MoveUp, window, cx);
20606        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20607    });
20608
20609    let breakpoints = editor.update(cx, |editor, cx| {
20610        editor
20611            .breakpoint_store()
20612            .as_ref()
20613            .unwrap()
20614            .read(cx)
20615            .all_source_breakpoints(cx)
20616            .clone()
20617    });
20618
20619    let disable_breakpoint = {
20620        let mut bp = Breakpoint::new_standard();
20621        bp.state = BreakpointState::Disabled;
20622        bp
20623    };
20624
20625    assert_eq!(1, breakpoints.len());
20626    assert_breakpoint(
20627        &breakpoints,
20628        &abs_path,
20629        vec![
20630            (0, disable_breakpoint.clone()),
20631            (2, Breakpoint::new_standard()),
20632            (3, disable_breakpoint.clone()),
20633        ],
20634    );
20635
20636    editor.update_in(cx, |editor, window, cx| {
20637        editor.move_to_beginning(&MoveToBeginning, window, cx);
20638        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20639        editor.move_to_end(&MoveToEnd, window, cx);
20640        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20641        editor.move_up(&MoveUp, window, cx);
20642        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20643    });
20644
20645    let breakpoints = editor.update(cx, |editor, cx| {
20646        editor
20647            .breakpoint_store()
20648            .as_ref()
20649            .unwrap()
20650            .read(cx)
20651            .all_source_breakpoints(cx)
20652            .clone()
20653    });
20654
20655    assert_eq!(1, breakpoints.len());
20656    assert_breakpoint(
20657        &breakpoints,
20658        &abs_path,
20659        vec![
20660            (0, Breakpoint::new_standard()),
20661            (2, disable_breakpoint),
20662            (3, Breakpoint::new_standard()),
20663        ],
20664    );
20665}
20666
20667#[gpui::test]
20668async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20669    init_test(cx, |_| {});
20670    let capabilities = lsp::ServerCapabilities {
20671        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20672            prepare_provider: Some(true),
20673            work_done_progress_options: Default::default(),
20674        })),
20675        ..Default::default()
20676    };
20677    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20678
20679    cx.set_state(indoc! {"
20680        struct Fˇoo {}
20681    "});
20682
20683    cx.update_editor(|editor, _, cx| {
20684        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20685        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20686        editor.highlight_background::<DocumentHighlightRead>(
20687            &[highlight_range],
20688            |theme| theme.colors().editor_document_highlight_read_background,
20689            cx,
20690        );
20691    });
20692
20693    let mut prepare_rename_handler = cx
20694        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20695            move |_, _, _| async move {
20696                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20697                    start: lsp::Position {
20698                        line: 0,
20699                        character: 7,
20700                    },
20701                    end: lsp::Position {
20702                        line: 0,
20703                        character: 10,
20704                    },
20705                })))
20706            },
20707        );
20708    let prepare_rename_task = cx
20709        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20710        .expect("Prepare rename was not started");
20711    prepare_rename_handler.next().await.unwrap();
20712    prepare_rename_task.await.expect("Prepare rename failed");
20713
20714    let mut rename_handler =
20715        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20716            let edit = lsp::TextEdit {
20717                range: lsp::Range {
20718                    start: lsp::Position {
20719                        line: 0,
20720                        character: 7,
20721                    },
20722                    end: lsp::Position {
20723                        line: 0,
20724                        character: 10,
20725                    },
20726                },
20727                new_text: "FooRenamed".to_string(),
20728            };
20729            Ok(Some(lsp::WorkspaceEdit::new(
20730                // Specify the same edit twice
20731                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20732            )))
20733        });
20734    let rename_task = cx
20735        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20736        .expect("Confirm rename was not started");
20737    rename_handler.next().await.unwrap();
20738    rename_task.await.expect("Confirm rename failed");
20739    cx.run_until_parked();
20740
20741    // Despite two edits, only one is actually applied as those are identical
20742    cx.assert_editor_state(indoc! {"
20743        struct FooRenamedˇ {}
20744    "});
20745}
20746
20747#[gpui::test]
20748async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20749    init_test(cx, |_| {});
20750    // These capabilities indicate that the server does not support prepare rename.
20751    let capabilities = lsp::ServerCapabilities {
20752        rename_provider: Some(lsp::OneOf::Left(true)),
20753        ..Default::default()
20754    };
20755    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20756
20757    cx.set_state(indoc! {"
20758        struct Fˇoo {}
20759    "});
20760
20761    cx.update_editor(|editor, _window, cx| {
20762        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20763        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20764        editor.highlight_background::<DocumentHighlightRead>(
20765            &[highlight_range],
20766            |theme| theme.colors().editor_document_highlight_read_background,
20767            cx,
20768        );
20769    });
20770
20771    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20772        .expect("Prepare rename was not started")
20773        .await
20774        .expect("Prepare rename failed");
20775
20776    let mut rename_handler =
20777        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20778            let edit = lsp::TextEdit {
20779                range: lsp::Range {
20780                    start: lsp::Position {
20781                        line: 0,
20782                        character: 7,
20783                    },
20784                    end: lsp::Position {
20785                        line: 0,
20786                        character: 10,
20787                    },
20788                },
20789                new_text: "FooRenamed".to_string(),
20790            };
20791            Ok(Some(lsp::WorkspaceEdit::new(
20792                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20793            )))
20794        });
20795    let rename_task = cx
20796        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20797        .expect("Confirm rename was not started");
20798    rename_handler.next().await.unwrap();
20799    rename_task.await.expect("Confirm rename failed");
20800    cx.run_until_parked();
20801
20802    // Correct range is renamed, as `surrounding_word` is used to find it.
20803    cx.assert_editor_state(indoc! {"
20804        struct FooRenamedˇ {}
20805    "});
20806}
20807
20808#[gpui::test]
20809async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20810    init_test(cx, |_| {});
20811    let mut cx = EditorTestContext::new(cx).await;
20812
20813    let language = Arc::new(
20814        Language::new(
20815            LanguageConfig::default(),
20816            Some(tree_sitter_html::LANGUAGE.into()),
20817        )
20818        .with_brackets_query(
20819            r#"
20820            ("<" @open "/>" @close)
20821            ("</" @open ">" @close)
20822            ("<" @open ">" @close)
20823            ("\"" @open "\"" @close)
20824            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20825        "#,
20826        )
20827        .unwrap(),
20828    );
20829    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20830
20831    cx.set_state(indoc! {"
20832        <span>ˇ</span>
20833    "});
20834    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20835    cx.assert_editor_state(indoc! {"
20836        <span>
20837        ˇ
20838        </span>
20839    "});
20840
20841    cx.set_state(indoc! {"
20842        <span><span></span>ˇ</span>
20843    "});
20844    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20845    cx.assert_editor_state(indoc! {"
20846        <span><span></span>
20847        ˇ</span>
20848    "});
20849
20850    cx.set_state(indoc! {"
20851        <span>ˇ
20852        </span>
20853    "});
20854    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20855    cx.assert_editor_state(indoc! {"
20856        <span>
20857        ˇ
20858        </span>
20859    "});
20860}
20861
20862#[gpui::test(iterations = 10)]
20863async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20864    init_test(cx, |_| {});
20865
20866    let fs = FakeFs::new(cx.executor());
20867    fs.insert_tree(
20868        path!("/dir"),
20869        json!({
20870            "a.ts": "a",
20871        }),
20872    )
20873    .await;
20874
20875    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20876    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20877    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20878
20879    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20880    language_registry.add(Arc::new(Language::new(
20881        LanguageConfig {
20882            name: "TypeScript".into(),
20883            matcher: LanguageMatcher {
20884                path_suffixes: vec!["ts".to_string()],
20885                ..Default::default()
20886            },
20887            ..Default::default()
20888        },
20889        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20890    )));
20891    let mut fake_language_servers = language_registry.register_fake_lsp(
20892        "TypeScript",
20893        FakeLspAdapter {
20894            capabilities: lsp::ServerCapabilities {
20895                code_lens_provider: Some(lsp::CodeLensOptions {
20896                    resolve_provider: Some(true),
20897                }),
20898                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20899                    commands: vec!["_the/command".to_string()],
20900                    ..lsp::ExecuteCommandOptions::default()
20901                }),
20902                ..lsp::ServerCapabilities::default()
20903            },
20904            ..FakeLspAdapter::default()
20905        },
20906    );
20907
20908    let (buffer, _handle) = project
20909        .update(cx, |p, cx| {
20910            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20911        })
20912        .await
20913        .unwrap();
20914    cx.executor().run_until_parked();
20915
20916    let fake_server = fake_language_servers.next().await.unwrap();
20917
20918    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20919    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20920    drop(buffer_snapshot);
20921    let actions = cx
20922        .update_window(*workspace, |_, window, cx| {
20923            project.code_actions(&buffer, anchor..anchor, window, cx)
20924        })
20925        .unwrap();
20926
20927    fake_server
20928        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20929            Ok(Some(vec![
20930                lsp::CodeLens {
20931                    range: lsp::Range::default(),
20932                    command: Some(lsp::Command {
20933                        title: "Code lens command".to_owned(),
20934                        command: "_the/command".to_owned(),
20935                        arguments: None,
20936                    }),
20937                    data: None,
20938                },
20939                lsp::CodeLens {
20940                    range: lsp::Range::default(),
20941                    command: Some(lsp::Command {
20942                        title: "Command not in capabilities".to_owned(),
20943                        command: "not in capabilities".to_owned(),
20944                        arguments: None,
20945                    }),
20946                    data: None,
20947                },
20948                lsp::CodeLens {
20949                    range: lsp::Range {
20950                        start: lsp::Position {
20951                            line: 1,
20952                            character: 1,
20953                        },
20954                        end: lsp::Position {
20955                            line: 1,
20956                            character: 1,
20957                        },
20958                    },
20959                    command: Some(lsp::Command {
20960                        title: "Command not in range".to_owned(),
20961                        command: "_the/command".to_owned(),
20962                        arguments: None,
20963                    }),
20964                    data: None,
20965                },
20966            ]))
20967        })
20968        .next()
20969        .await;
20970
20971    let actions = actions.await.unwrap();
20972    assert_eq!(
20973        actions.len(),
20974        1,
20975        "Should have only one valid action for the 0..0 range"
20976    );
20977    let action = actions[0].clone();
20978    let apply = project.update(cx, |project, cx| {
20979        project.apply_code_action(buffer.clone(), action, true, cx)
20980    });
20981
20982    // Resolving the code action does not populate its edits. In absence of
20983    // edits, we must execute the given command.
20984    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20985        |mut lens, _| async move {
20986            let lens_command = lens.command.as_mut().expect("should have a command");
20987            assert_eq!(lens_command.title, "Code lens command");
20988            lens_command.arguments = Some(vec![json!("the-argument")]);
20989            Ok(lens)
20990        },
20991    );
20992
20993    // While executing the command, the language server sends the editor
20994    // a `workspaceEdit` request.
20995    fake_server
20996        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20997            let fake = fake_server.clone();
20998            move |params, _| {
20999                assert_eq!(params.command, "_the/command");
21000                let fake = fake.clone();
21001                async move {
21002                    fake.server
21003                        .request::<lsp::request::ApplyWorkspaceEdit>(
21004                            lsp::ApplyWorkspaceEditParams {
21005                                label: None,
21006                                edit: lsp::WorkspaceEdit {
21007                                    changes: Some(
21008                                        [(
21009                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21010                                            vec![lsp::TextEdit {
21011                                                range: lsp::Range::new(
21012                                                    lsp::Position::new(0, 0),
21013                                                    lsp::Position::new(0, 0),
21014                                                ),
21015                                                new_text: "X".into(),
21016                                            }],
21017                                        )]
21018                                        .into_iter()
21019                                        .collect(),
21020                                    ),
21021                                    ..Default::default()
21022                                },
21023                            },
21024                        )
21025                        .await
21026                        .into_response()
21027                        .unwrap();
21028                    Ok(Some(json!(null)))
21029                }
21030            }
21031        })
21032        .next()
21033        .await;
21034
21035    // Applying the code lens command returns a project transaction containing the edits
21036    // sent by the language server in its `workspaceEdit` request.
21037    let transaction = apply.await.unwrap();
21038    assert!(transaction.0.contains_key(&buffer));
21039    buffer.update(cx, |buffer, cx| {
21040        assert_eq!(buffer.text(), "Xa");
21041        buffer.undo(cx);
21042        assert_eq!(buffer.text(), "a");
21043    });
21044}
21045
21046#[gpui::test]
21047async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21048    init_test(cx, |_| {});
21049
21050    let fs = FakeFs::new(cx.executor());
21051    let main_text = r#"fn main() {
21052println!("1");
21053println!("2");
21054println!("3");
21055println!("4");
21056println!("5");
21057}"#;
21058    let lib_text = "mod foo {}";
21059    fs.insert_tree(
21060        path!("/a"),
21061        json!({
21062            "lib.rs": lib_text,
21063            "main.rs": main_text,
21064        }),
21065    )
21066    .await;
21067
21068    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21069    let (workspace, cx) =
21070        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21071    let worktree_id = workspace.update(cx, |workspace, cx| {
21072        workspace.project().update(cx, |project, cx| {
21073            project.worktrees(cx).next().unwrap().read(cx).id()
21074        })
21075    });
21076
21077    let expected_ranges = vec![
21078        Point::new(0, 0)..Point::new(0, 0),
21079        Point::new(1, 0)..Point::new(1, 1),
21080        Point::new(2, 0)..Point::new(2, 2),
21081        Point::new(3, 0)..Point::new(3, 3),
21082    ];
21083
21084    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21085    let editor_1 = workspace
21086        .update_in(cx, |workspace, window, cx| {
21087            workspace.open_path(
21088                (worktree_id, "main.rs"),
21089                Some(pane_1.downgrade()),
21090                true,
21091                window,
21092                cx,
21093            )
21094        })
21095        .unwrap()
21096        .await
21097        .downcast::<Editor>()
21098        .unwrap();
21099    pane_1.update(cx, |pane, cx| {
21100        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21101        open_editor.update(cx, |editor, cx| {
21102            assert_eq!(
21103                editor.display_text(cx),
21104                main_text,
21105                "Original main.rs text on initial open",
21106            );
21107            assert_eq!(
21108                editor
21109                    .selections
21110                    .all::<Point>(cx)
21111                    .into_iter()
21112                    .map(|s| s.range())
21113                    .collect::<Vec<_>>(),
21114                vec![Point::zero()..Point::zero()],
21115                "Default selections on initial open",
21116            );
21117        })
21118    });
21119    editor_1.update_in(cx, |editor, window, cx| {
21120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21121            s.select_ranges(expected_ranges.clone());
21122        });
21123    });
21124
21125    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21126        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21127    });
21128    let editor_2 = workspace
21129        .update_in(cx, |workspace, window, cx| {
21130            workspace.open_path(
21131                (worktree_id, "main.rs"),
21132                Some(pane_2.downgrade()),
21133                true,
21134                window,
21135                cx,
21136            )
21137        })
21138        .unwrap()
21139        .await
21140        .downcast::<Editor>()
21141        .unwrap();
21142    pane_2.update(cx, |pane, cx| {
21143        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21144        open_editor.update(cx, |editor, cx| {
21145            assert_eq!(
21146                editor.display_text(cx),
21147                main_text,
21148                "Original main.rs text on initial open in another panel",
21149            );
21150            assert_eq!(
21151                editor
21152                    .selections
21153                    .all::<Point>(cx)
21154                    .into_iter()
21155                    .map(|s| s.range())
21156                    .collect::<Vec<_>>(),
21157                vec![Point::zero()..Point::zero()],
21158                "Default selections on initial open in another panel",
21159            );
21160        })
21161    });
21162
21163    editor_2.update_in(cx, |editor, window, cx| {
21164        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21165    });
21166
21167    let _other_editor_1 = workspace
21168        .update_in(cx, |workspace, window, cx| {
21169            workspace.open_path(
21170                (worktree_id, "lib.rs"),
21171                Some(pane_1.downgrade()),
21172                true,
21173                window,
21174                cx,
21175            )
21176        })
21177        .unwrap()
21178        .await
21179        .downcast::<Editor>()
21180        .unwrap();
21181    pane_1
21182        .update_in(cx, |pane, window, cx| {
21183            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21184        })
21185        .await
21186        .unwrap();
21187    drop(editor_1);
21188    pane_1.update(cx, |pane, cx| {
21189        pane.active_item()
21190            .unwrap()
21191            .downcast::<Editor>()
21192            .unwrap()
21193            .update(cx, |editor, cx| {
21194                assert_eq!(
21195                    editor.display_text(cx),
21196                    lib_text,
21197                    "Other file should be open and active",
21198                );
21199            });
21200        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21201    });
21202
21203    let _other_editor_2 = workspace
21204        .update_in(cx, |workspace, window, cx| {
21205            workspace.open_path(
21206                (worktree_id, "lib.rs"),
21207                Some(pane_2.downgrade()),
21208                true,
21209                window,
21210                cx,
21211            )
21212        })
21213        .unwrap()
21214        .await
21215        .downcast::<Editor>()
21216        .unwrap();
21217    pane_2
21218        .update_in(cx, |pane, window, cx| {
21219            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21220        })
21221        .await
21222        .unwrap();
21223    drop(editor_2);
21224    pane_2.update(cx, |pane, cx| {
21225        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21226        open_editor.update(cx, |editor, cx| {
21227            assert_eq!(
21228                editor.display_text(cx),
21229                lib_text,
21230                "Other file should be open and active in another panel too",
21231            );
21232        });
21233        assert_eq!(
21234            pane.items().count(),
21235            1,
21236            "No other editors should be open in another pane",
21237        );
21238    });
21239
21240    let _editor_1_reopened = workspace
21241        .update_in(cx, |workspace, window, cx| {
21242            workspace.open_path(
21243                (worktree_id, "main.rs"),
21244                Some(pane_1.downgrade()),
21245                true,
21246                window,
21247                cx,
21248            )
21249        })
21250        .unwrap()
21251        .await
21252        .downcast::<Editor>()
21253        .unwrap();
21254    let _editor_2_reopened = workspace
21255        .update_in(cx, |workspace, window, cx| {
21256            workspace.open_path(
21257                (worktree_id, "main.rs"),
21258                Some(pane_2.downgrade()),
21259                true,
21260                window,
21261                cx,
21262            )
21263        })
21264        .unwrap()
21265        .await
21266        .downcast::<Editor>()
21267        .unwrap();
21268    pane_1.update(cx, |pane, cx| {
21269        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21270        open_editor.update(cx, |editor, cx| {
21271            assert_eq!(
21272                editor.display_text(cx),
21273                main_text,
21274                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21275            );
21276            assert_eq!(
21277                editor
21278                    .selections
21279                    .all::<Point>(cx)
21280                    .into_iter()
21281                    .map(|s| s.range())
21282                    .collect::<Vec<_>>(),
21283                expected_ranges,
21284                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21285            );
21286        })
21287    });
21288    pane_2.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                r#"fn main() {
21294⋯rintln!("1");
21295⋯intln!("2");
21296⋯ntln!("3");
21297println!("4");
21298println!("5");
21299}"#,
21300                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21301            );
21302            assert_eq!(
21303                editor
21304                    .selections
21305                    .all::<Point>(cx)
21306                    .into_iter()
21307                    .map(|s| s.range())
21308                    .collect::<Vec<_>>(),
21309                vec![Point::zero()..Point::zero()],
21310                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21311            );
21312        })
21313    });
21314}
21315
21316#[gpui::test]
21317async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21318    init_test(cx, |_| {});
21319
21320    let fs = FakeFs::new(cx.executor());
21321    let main_text = r#"fn main() {
21322println!("1");
21323println!("2");
21324println!("3");
21325println!("4");
21326println!("5");
21327}"#;
21328    let lib_text = "mod foo {}";
21329    fs.insert_tree(
21330        path!("/a"),
21331        json!({
21332            "lib.rs": lib_text,
21333            "main.rs": main_text,
21334        }),
21335    )
21336    .await;
21337
21338    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21339    let (workspace, cx) =
21340        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21341    let worktree_id = workspace.update(cx, |workspace, cx| {
21342        workspace.project().update(cx, |project, cx| {
21343            project.worktrees(cx).next().unwrap().read(cx).id()
21344        })
21345    });
21346
21347    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21348    let editor = workspace
21349        .update_in(cx, |workspace, window, cx| {
21350            workspace.open_path(
21351                (worktree_id, "main.rs"),
21352                Some(pane.downgrade()),
21353                true,
21354                window,
21355                cx,
21356            )
21357        })
21358        .unwrap()
21359        .await
21360        .downcast::<Editor>()
21361        .unwrap();
21362    pane.update(cx, |pane, cx| {
21363        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21364        open_editor.update(cx, |editor, cx| {
21365            assert_eq!(
21366                editor.display_text(cx),
21367                main_text,
21368                "Original main.rs text on initial open",
21369            );
21370        })
21371    });
21372    editor.update_in(cx, |editor, window, cx| {
21373        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21374    });
21375
21376    cx.update_global(|store: &mut SettingsStore, cx| {
21377        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21378            s.restore_on_file_reopen = Some(false);
21379        });
21380    });
21381    editor.update_in(cx, |editor, window, cx| {
21382        editor.fold_ranges(
21383            vec![
21384                Point::new(1, 0)..Point::new(1, 1),
21385                Point::new(2, 0)..Point::new(2, 2),
21386                Point::new(3, 0)..Point::new(3, 3),
21387            ],
21388            false,
21389            window,
21390            cx,
21391        );
21392    });
21393    pane.update_in(cx, |pane, window, cx| {
21394        pane.close_all_items(&CloseAllItems::default(), window, cx)
21395    })
21396    .await
21397    .unwrap();
21398    pane.update(cx, |pane, _| {
21399        assert!(pane.active_item().is_none());
21400    });
21401    cx.update_global(|store: &mut SettingsStore, cx| {
21402        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21403            s.restore_on_file_reopen = Some(true);
21404        });
21405    });
21406
21407    let _editor_reopened = workspace
21408        .update_in(cx, |workspace, window, cx| {
21409            workspace.open_path(
21410                (worktree_id, "main.rs"),
21411                Some(pane.downgrade()),
21412                true,
21413                window,
21414                cx,
21415            )
21416        })
21417        .unwrap()
21418        .await
21419        .downcast::<Editor>()
21420        .unwrap();
21421    pane.update(cx, |pane, cx| {
21422        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21423        open_editor.update(cx, |editor, cx| {
21424            assert_eq!(
21425                editor.display_text(cx),
21426                main_text,
21427                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21428            );
21429        })
21430    });
21431}
21432
21433#[gpui::test]
21434async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21435    struct EmptyModalView {
21436        focus_handle: gpui::FocusHandle,
21437    }
21438    impl EventEmitter<DismissEvent> for EmptyModalView {}
21439    impl Render for EmptyModalView {
21440        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21441            div()
21442        }
21443    }
21444    impl Focusable for EmptyModalView {
21445        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21446            self.focus_handle.clone()
21447        }
21448    }
21449    impl workspace::ModalView for EmptyModalView {}
21450    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21451        EmptyModalView {
21452            focus_handle: cx.focus_handle(),
21453        }
21454    }
21455
21456    init_test(cx, |_| {});
21457
21458    let fs = FakeFs::new(cx.executor());
21459    let project = Project::test(fs, [], cx).await;
21460    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21461    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21462    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21463    let editor = cx.new_window_entity(|window, cx| {
21464        Editor::new(
21465            EditorMode::full(),
21466            buffer,
21467            Some(project.clone()),
21468            window,
21469            cx,
21470        )
21471    });
21472    workspace
21473        .update(cx, |workspace, window, cx| {
21474            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21475        })
21476        .unwrap();
21477    editor.update_in(cx, |editor, window, cx| {
21478        editor.open_context_menu(&OpenContextMenu, window, cx);
21479        assert!(editor.mouse_context_menu.is_some());
21480    });
21481    workspace
21482        .update(cx, |workspace, window, cx| {
21483            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21484        })
21485        .unwrap();
21486    cx.read(|cx| {
21487        assert!(editor.read(cx).mouse_context_menu.is_none());
21488    });
21489}
21490
21491#[gpui::test]
21492async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21493    init_test(cx, |_| {});
21494
21495    let fs = FakeFs::new(cx.executor());
21496    fs.insert_file(path!("/file.html"), Default::default())
21497        .await;
21498
21499    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21500
21501    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21502    let html_language = Arc::new(Language::new(
21503        LanguageConfig {
21504            name: "HTML".into(),
21505            matcher: LanguageMatcher {
21506                path_suffixes: vec!["html".to_string()],
21507                ..LanguageMatcher::default()
21508            },
21509            brackets: BracketPairConfig {
21510                pairs: vec![BracketPair {
21511                    start: "<".into(),
21512                    end: ">".into(),
21513                    close: true,
21514                    ..Default::default()
21515                }],
21516                ..Default::default()
21517            },
21518            ..Default::default()
21519        },
21520        Some(tree_sitter_html::LANGUAGE.into()),
21521    ));
21522    language_registry.add(html_language);
21523    let mut fake_servers = language_registry.register_fake_lsp(
21524        "HTML",
21525        FakeLspAdapter {
21526            capabilities: lsp::ServerCapabilities {
21527                completion_provider: Some(lsp::CompletionOptions {
21528                    resolve_provider: Some(true),
21529                    ..Default::default()
21530                }),
21531                ..Default::default()
21532            },
21533            ..Default::default()
21534        },
21535    );
21536
21537    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21538    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21539
21540    let worktree_id = workspace
21541        .update(cx, |workspace, _window, cx| {
21542            workspace.project().update(cx, |project, cx| {
21543                project.worktrees(cx).next().unwrap().read(cx).id()
21544            })
21545        })
21546        .unwrap();
21547    project
21548        .update(cx, |project, cx| {
21549            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21550        })
21551        .await
21552        .unwrap();
21553    let editor = workspace
21554        .update(cx, |workspace, window, cx| {
21555            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21556        })
21557        .unwrap()
21558        .await
21559        .unwrap()
21560        .downcast::<Editor>()
21561        .unwrap();
21562
21563    let fake_server = fake_servers.next().await.unwrap();
21564    editor.update_in(cx, |editor, window, cx| {
21565        editor.set_text("<ad></ad>", window, cx);
21566        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21567            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21568        });
21569        let Some((buffer, _)) = editor
21570            .buffer
21571            .read(cx)
21572            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21573        else {
21574            panic!("Failed to get buffer for selection position");
21575        };
21576        let buffer = buffer.read(cx);
21577        let buffer_id = buffer.remote_id();
21578        let opening_range =
21579            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21580        let closing_range =
21581            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21582        let mut linked_ranges = HashMap::default();
21583        linked_ranges.insert(
21584            buffer_id,
21585            vec![(opening_range.clone(), vec![closing_range.clone()])],
21586        );
21587        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21588    });
21589    let mut completion_handle =
21590        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21591            Ok(Some(lsp::CompletionResponse::Array(vec![
21592                lsp::CompletionItem {
21593                    label: "head".to_string(),
21594                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21595                        lsp::InsertReplaceEdit {
21596                            new_text: "head".to_string(),
21597                            insert: lsp::Range::new(
21598                                lsp::Position::new(0, 1),
21599                                lsp::Position::new(0, 3),
21600                            ),
21601                            replace: lsp::Range::new(
21602                                lsp::Position::new(0, 1),
21603                                lsp::Position::new(0, 3),
21604                            ),
21605                        },
21606                    )),
21607                    ..Default::default()
21608                },
21609            ])))
21610        });
21611    editor.update_in(cx, |editor, window, cx| {
21612        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21613    });
21614    cx.run_until_parked();
21615    completion_handle.next().await.unwrap();
21616    editor.update(cx, |editor, _| {
21617        assert!(
21618            editor.context_menu_visible(),
21619            "Completion menu should be visible"
21620        );
21621    });
21622    editor.update_in(cx, |editor, window, cx| {
21623        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21624    });
21625    cx.executor().run_until_parked();
21626    editor.update(cx, |editor, cx| {
21627        assert_eq!(editor.text(cx), "<head></head>");
21628    });
21629}
21630
21631#[gpui::test]
21632async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21633    init_test(cx, |_| {});
21634
21635    let fs = FakeFs::new(cx.executor());
21636    fs.insert_tree(
21637        path!("/root"),
21638        json!({
21639            "a": {
21640                "main.rs": "fn main() {}",
21641            },
21642            "foo": {
21643                "bar": {
21644                    "external_file.rs": "pub mod external {}",
21645                }
21646            }
21647        }),
21648    )
21649    .await;
21650
21651    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21652    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21653    language_registry.add(rust_lang());
21654    let _fake_servers = language_registry.register_fake_lsp(
21655        "Rust",
21656        FakeLspAdapter {
21657            ..FakeLspAdapter::default()
21658        },
21659    );
21660    let (workspace, cx) =
21661        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21662    let worktree_id = workspace.update(cx, |workspace, cx| {
21663        workspace.project().update(cx, |project, cx| {
21664            project.worktrees(cx).next().unwrap().read(cx).id()
21665        })
21666    });
21667
21668    let assert_language_servers_count =
21669        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21670            project.update(cx, |project, cx| {
21671                let current = project
21672                    .lsp_store()
21673                    .read(cx)
21674                    .as_local()
21675                    .unwrap()
21676                    .language_servers
21677                    .len();
21678                assert_eq!(expected, current, "{context}");
21679            });
21680        };
21681
21682    assert_language_servers_count(
21683        0,
21684        "No servers should be running before any file is open",
21685        cx,
21686    );
21687    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21688    let main_editor = workspace
21689        .update_in(cx, |workspace, window, cx| {
21690            workspace.open_path(
21691                (worktree_id, "main.rs"),
21692                Some(pane.downgrade()),
21693                true,
21694                window,
21695                cx,
21696            )
21697        })
21698        .unwrap()
21699        .await
21700        .downcast::<Editor>()
21701        .unwrap();
21702    pane.update(cx, |pane, cx| {
21703        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21704        open_editor.update(cx, |editor, cx| {
21705            assert_eq!(
21706                editor.display_text(cx),
21707                "fn main() {}",
21708                "Original main.rs text on initial open",
21709            );
21710        });
21711        assert_eq!(open_editor, main_editor);
21712    });
21713    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21714
21715    let external_editor = workspace
21716        .update_in(cx, |workspace, window, cx| {
21717            workspace.open_abs_path(
21718                PathBuf::from("/root/foo/bar/external_file.rs"),
21719                OpenOptions::default(),
21720                window,
21721                cx,
21722            )
21723        })
21724        .await
21725        .expect("opening external file")
21726        .downcast::<Editor>()
21727        .expect("downcasted external file's open element to editor");
21728    pane.update(cx, |pane, cx| {
21729        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21730        open_editor.update(cx, |editor, cx| {
21731            assert_eq!(
21732                editor.display_text(cx),
21733                "pub mod external {}",
21734                "External file is open now",
21735            );
21736        });
21737        assert_eq!(open_editor, external_editor);
21738    });
21739    assert_language_servers_count(
21740        1,
21741        "Second, external, *.rs file should join the existing server",
21742        cx,
21743    );
21744
21745    pane.update_in(cx, |pane, window, cx| {
21746        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21747    })
21748    .await
21749    .unwrap();
21750    pane.update_in(cx, |pane, window, cx| {
21751        pane.navigate_backward(window, cx);
21752    });
21753    cx.run_until_parked();
21754    pane.update(cx, |pane, cx| {
21755        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21756        open_editor.update(cx, |editor, cx| {
21757            assert_eq!(
21758                editor.display_text(cx),
21759                "pub mod external {}",
21760                "External file is open now",
21761            );
21762        });
21763    });
21764    assert_language_servers_count(
21765        1,
21766        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21767        cx,
21768    );
21769
21770    cx.update(|_, cx| {
21771        workspace::reload(&workspace::Reload::default(), cx);
21772    });
21773    assert_language_servers_count(
21774        1,
21775        "After reloading the worktree with local and external files opened, only one project should be started",
21776        cx,
21777    );
21778}
21779
21780#[gpui::test]
21781async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21782    init_test(cx, |_| {});
21783
21784    let mut cx = EditorTestContext::new(cx).await;
21785    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21786    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21787
21788    // test cursor move to start of each line on tab
21789    // for `if`, `elif`, `else`, `while`, `with` and `for`
21790    cx.set_state(indoc! {"
21791        def main():
21792        ˇ    for item in items:
21793        ˇ        while item.active:
21794        ˇ            if item.value > 10:
21795        ˇ                continue
21796        ˇ            elif item.value < 0:
21797        ˇ                break
21798        ˇ            else:
21799        ˇ                with item.context() as ctx:
21800        ˇ                    yield count
21801        ˇ        else:
21802        ˇ            log('while else')
21803        ˇ    else:
21804        ˇ        log('for else')
21805    "});
21806    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21807    cx.assert_editor_state(indoc! {"
21808        def main():
21809            ˇfor item in items:
21810                ˇwhile item.active:
21811                    ˇif item.value > 10:
21812                        ˇcontinue
21813                    ˇelif item.value < 0:
21814                        ˇbreak
21815                    ˇelse:
21816                        ˇwith item.context() as ctx:
21817                            ˇyield count
21818                ˇelse:
21819                    ˇlog('while else')
21820            ˇelse:
21821                ˇlog('for else')
21822    "});
21823    // test relative indent is preserved when tab
21824    // for `if`, `elif`, `else`, `while`, `with` and `for`
21825    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21826    cx.assert_editor_state(indoc! {"
21827        def main():
21828                ˇfor item in items:
21829                    ˇwhile item.active:
21830                        ˇif item.value > 10:
21831                            ˇcontinue
21832                        ˇelif item.value < 0:
21833                            ˇbreak
21834                        ˇelse:
21835                            ˇwith item.context() as ctx:
21836                                ˇyield count
21837                    ˇelse:
21838                        ˇlog('while else')
21839                ˇelse:
21840                    ˇlog('for else')
21841    "});
21842
21843    // test cursor move to start of each line on tab
21844    // for `try`, `except`, `else`, `finally`, `match` and `def`
21845    cx.set_state(indoc! {"
21846        def main():
21847        ˇ    try:
21848        ˇ        fetch()
21849        ˇ    except ValueError:
21850        ˇ        handle_error()
21851        ˇ    else:
21852        ˇ        match value:
21853        ˇ            case _:
21854        ˇ    finally:
21855        ˇ        def status():
21856        ˇ            return 0
21857    "});
21858    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21859    cx.assert_editor_state(indoc! {"
21860        def main():
21861            ˇtry:
21862                ˇfetch()
21863            ˇexcept ValueError:
21864                ˇhandle_error()
21865            ˇelse:
21866                ˇmatch value:
21867                    ˇcase _:
21868            ˇfinally:
21869                ˇdef status():
21870                    ˇreturn 0
21871    "});
21872    // test relative indent is preserved when tab
21873    // for `try`, `except`, `else`, `finally`, `match` and `def`
21874    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21875    cx.assert_editor_state(indoc! {"
21876        def main():
21877                ˇtry:
21878                    ˇfetch()
21879                ˇexcept ValueError:
21880                    ˇhandle_error()
21881                ˇelse:
21882                    ˇmatch value:
21883                        ˇcase _:
21884                ˇfinally:
21885                    ˇdef status():
21886                        ˇreturn 0
21887    "});
21888}
21889
21890#[gpui::test]
21891async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21892    init_test(cx, |_| {});
21893
21894    let mut cx = EditorTestContext::new(cx).await;
21895    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21896    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21897
21898    // test `else` auto outdents when typed inside `if` block
21899    cx.set_state(indoc! {"
21900        def main():
21901            if i == 2:
21902                return
21903                ˇ
21904    "});
21905    cx.update_editor(|editor, window, cx| {
21906        editor.handle_input("else:", window, cx);
21907    });
21908    cx.assert_editor_state(indoc! {"
21909        def main():
21910            if i == 2:
21911                return
21912            else:ˇ
21913    "});
21914
21915    // test `except` auto outdents when typed inside `try` block
21916    cx.set_state(indoc! {"
21917        def main():
21918            try:
21919                i = 2
21920                ˇ
21921    "});
21922    cx.update_editor(|editor, window, cx| {
21923        editor.handle_input("except:", window, cx);
21924    });
21925    cx.assert_editor_state(indoc! {"
21926        def main():
21927            try:
21928                i = 2
21929            except:ˇ
21930    "});
21931
21932    // test `else` auto outdents when typed inside `except` block
21933    cx.set_state(indoc! {"
21934        def main():
21935            try:
21936                i = 2
21937            except:
21938                j = 2
21939                ˇ
21940    "});
21941    cx.update_editor(|editor, window, cx| {
21942        editor.handle_input("else:", window, cx);
21943    });
21944    cx.assert_editor_state(indoc! {"
21945        def main():
21946            try:
21947                i = 2
21948            except:
21949                j = 2
21950            else:ˇ
21951    "});
21952
21953    // test `finally` auto outdents when typed inside `else` block
21954    cx.set_state(indoc! {"
21955        def main():
21956            try:
21957                i = 2
21958            except:
21959                j = 2
21960            else:
21961                k = 2
21962                ˇ
21963    "});
21964    cx.update_editor(|editor, window, cx| {
21965        editor.handle_input("finally:", window, cx);
21966    });
21967    cx.assert_editor_state(indoc! {"
21968        def main():
21969            try:
21970                i = 2
21971            except:
21972                j = 2
21973            else:
21974                k = 2
21975            finally:ˇ
21976    "});
21977
21978    // test `else` does not outdents when typed inside `except` block right after for block
21979    cx.set_state(indoc! {"
21980        def main():
21981            try:
21982                i = 2
21983            except:
21984                for i in range(n):
21985                    pass
21986                ˇ
21987    "});
21988    cx.update_editor(|editor, window, cx| {
21989        editor.handle_input("else:", window, cx);
21990    });
21991    cx.assert_editor_state(indoc! {"
21992        def main():
21993            try:
21994                i = 2
21995            except:
21996                for i in range(n):
21997                    pass
21998                else:ˇ
21999    "});
22000
22001    // test `finally` auto outdents when typed inside `else` block right after for block
22002    cx.set_state(indoc! {"
22003        def main():
22004            try:
22005                i = 2
22006            except:
22007                j = 2
22008            else:
22009                for i in range(n):
22010                    pass
22011                ˇ
22012    "});
22013    cx.update_editor(|editor, window, cx| {
22014        editor.handle_input("finally:", window, cx);
22015    });
22016    cx.assert_editor_state(indoc! {"
22017        def main():
22018            try:
22019                i = 2
22020            except:
22021                j = 2
22022            else:
22023                for i in range(n):
22024                    pass
22025            finally:ˇ
22026    "});
22027
22028    // test `except` outdents to inner "try" block
22029    cx.set_state(indoc! {"
22030        def main():
22031            try:
22032                i = 2
22033                if i == 2:
22034                    try:
22035                        i = 3
22036                        ˇ
22037    "});
22038    cx.update_editor(|editor, window, cx| {
22039        editor.handle_input("except:", window, cx);
22040    });
22041    cx.assert_editor_state(indoc! {"
22042        def main():
22043            try:
22044                i = 2
22045                if i == 2:
22046                    try:
22047                        i = 3
22048                    except:ˇ
22049    "});
22050
22051    // test `except` outdents to outer "try" block
22052    cx.set_state(indoc! {"
22053        def main():
22054            try:
22055                i = 2
22056                if i == 2:
22057                    try:
22058                        i = 3
22059                ˇ
22060    "});
22061    cx.update_editor(|editor, window, cx| {
22062        editor.handle_input("except:", window, cx);
22063    });
22064    cx.assert_editor_state(indoc! {"
22065        def main():
22066            try:
22067                i = 2
22068                if i == 2:
22069                    try:
22070                        i = 3
22071            except:ˇ
22072    "});
22073
22074    // test `else` stays at correct indent when typed after `for` block
22075    cx.set_state(indoc! {"
22076        def main():
22077            for i in range(10):
22078                if i == 3:
22079                    break
22080            ˇ
22081    "});
22082    cx.update_editor(|editor, window, cx| {
22083        editor.handle_input("else:", window, cx);
22084    });
22085    cx.assert_editor_state(indoc! {"
22086        def main():
22087            for i in range(10):
22088                if i == 3:
22089                    break
22090            else:ˇ
22091    "});
22092
22093    // test does not outdent on typing after line with square brackets
22094    cx.set_state(indoc! {"
22095        def f() -> list[str]:
22096            ˇ
22097    "});
22098    cx.update_editor(|editor, window, cx| {
22099        editor.handle_input("a", window, cx);
22100    });
22101    cx.assert_editor_state(indoc! {"
22102        def f() -> list[str]:
2210322104    "});
22105}
22106
22107#[gpui::test]
22108async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22109    init_test(cx, |_| {});
22110    update_test_language_settings(cx, |settings| {
22111        settings.defaults.extend_comment_on_newline = Some(false);
22112    });
22113    let mut cx = EditorTestContext::new(cx).await;
22114    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22115    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22116
22117    // test correct indent after newline on comment
22118    cx.set_state(indoc! {"
22119        # COMMENT:ˇ
22120    "});
22121    cx.update_editor(|editor, window, cx| {
22122        editor.newline(&Newline, window, cx);
22123    });
22124    cx.assert_editor_state(indoc! {"
22125        # COMMENT:
22126        ˇ
22127    "});
22128
22129    // test correct indent after newline in brackets
22130    cx.set_state(indoc! {"
22131        {ˇ}
22132    "});
22133    cx.update_editor(|editor, window, cx| {
22134        editor.newline(&Newline, window, cx);
22135    });
22136    cx.run_until_parked();
22137    cx.assert_editor_state(indoc! {"
22138        {
22139            ˇ
22140        }
22141    "});
22142
22143    cx.set_state(indoc! {"
22144        (ˇ)
22145    "});
22146    cx.update_editor(|editor, window, cx| {
22147        editor.newline(&Newline, window, cx);
22148    });
22149    cx.run_until_parked();
22150    cx.assert_editor_state(indoc! {"
22151        (
22152            ˇ
22153        )
22154    "});
22155
22156    // do not indent after empty lists or dictionaries
22157    cx.set_state(indoc! {"
22158        a = []ˇ
22159    "});
22160    cx.update_editor(|editor, window, cx| {
22161        editor.newline(&Newline, window, cx);
22162    });
22163    cx.run_until_parked();
22164    cx.assert_editor_state(indoc! {"
22165        a = []
22166        ˇ
22167    "});
22168}
22169
22170fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22171    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22172    point..point
22173}
22174
22175#[track_caller]
22176fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22177    let (text, ranges) = marked_text_ranges(marked_text, true);
22178    assert_eq!(editor.text(cx), text);
22179    assert_eq!(
22180        editor.selections.ranges(cx),
22181        ranges,
22182        "Assert selections are {}",
22183        marked_text
22184    );
22185}
22186
22187pub fn handle_signature_help_request(
22188    cx: &mut EditorLspTestContext,
22189    mocked_response: lsp::SignatureHelp,
22190) -> impl Future<Output = ()> + use<> {
22191    let mut request =
22192        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22193            let mocked_response = mocked_response.clone();
22194            async move { Ok(Some(mocked_response)) }
22195        });
22196
22197    async move {
22198        request.next().await;
22199    }
22200}
22201
22202#[track_caller]
22203pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22204    cx.update_editor(|editor, _, _| {
22205        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22206            let entries = menu.entries.borrow();
22207            let entries = entries
22208                .iter()
22209                .map(|entry| entry.string.as_str())
22210                .collect::<Vec<_>>();
22211            assert_eq!(entries, expected);
22212        } else {
22213            panic!("Expected completions menu");
22214        }
22215    });
22216}
22217
22218/// Handle completion request passing a marked string specifying where the completion
22219/// should be triggered from using '|' character, what range should be replaced, and what completions
22220/// should be returned using '<' and '>' to delimit the range.
22221///
22222/// Also see `handle_completion_request_with_insert_and_replace`.
22223#[track_caller]
22224pub fn handle_completion_request(
22225    marked_string: &str,
22226    completions: Vec<&'static str>,
22227    is_incomplete: bool,
22228    counter: Arc<AtomicUsize>,
22229    cx: &mut EditorLspTestContext,
22230) -> impl Future<Output = ()> {
22231    let complete_from_marker: TextRangeMarker = '|'.into();
22232    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22233    let (_, mut marked_ranges) = marked_text_ranges_by(
22234        marked_string,
22235        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22236    );
22237
22238    let complete_from_position =
22239        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22240    let replace_range =
22241        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22242
22243    let mut request =
22244        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22245            let completions = completions.clone();
22246            counter.fetch_add(1, atomic::Ordering::Release);
22247            async move {
22248                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22249                assert_eq!(
22250                    params.text_document_position.position,
22251                    complete_from_position
22252                );
22253                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22254                    is_incomplete: is_incomplete,
22255                    item_defaults: None,
22256                    items: completions
22257                        .iter()
22258                        .map(|completion_text| lsp::CompletionItem {
22259                            label: completion_text.to_string(),
22260                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22261                                range: replace_range,
22262                                new_text: completion_text.to_string(),
22263                            })),
22264                            ..Default::default()
22265                        })
22266                        .collect(),
22267                })))
22268            }
22269        });
22270
22271    async move {
22272        request.next().await;
22273    }
22274}
22275
22276/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22277/// given instead, which also contains an `insert` range.
22278///
22279/// This function uses markers to define ranges:
22280/// - `|` marks the cursor position
22281/// - `<>` marks the replace range
22282/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22283pub fn handle_completion_request_with_insert_and_replace(
22284    cx: &mut EditorLspTestContext,
22285    marked_string: &str,
22286    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22287    counter: Arc<AtomicUsize>,
22288) -> impl Future<Output = ()> {
22289    let complete_from_marker: TextRangeMarker = '|'.into();
22290    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22291    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22292
22293    let (_, mut marked_ranges) = marked_text_ranges_by(
22294        marked_string,
22295        vec![
22296            complete_from_marker.clone(),
22297            replace_range_marker.clone(),
22298            insert_range_marker.clone(),
22299        ],
22300    );
22301
22302    let complete_from_position =
22303        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22304    let replace_range =
22305        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22306
22307    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22308        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22309        _ => lsp::Range {
22310            start: replace_range.start,
22311            end: complete_from_position,
22312        },
22313    };
22314
22315    let mut request =
22316        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22317            let completions = completions.clone();
22318            counter.fetch_add(1, atomic::Ordering::Release);
22319            async move {
22320                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22321                assert_eq!(
22322                    params.text_document_position.position, complete_from_position,
22323                    "marker `|` position doesn't match",
22324                );
22325                Ok(Some(lsp::CompletionResponse::Array(
22326                    completions
22327                        .iter()
22328                        .map(|(label, new_text)| lsp::CompletionItem {
22329                            label: label.to_string(),
22330                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22331                                lsp::InsertReplaceEdit {
22332                                    insert: insert_range,
22333                                    replace: replace_range,
22334                                    new_text: new_text.to_string(),
22335                                },
22336                            )),
22337                            ..Default::default()
22338                        })
22339                        .collect(),
22340                )))
22341            }
22342        });
22343
22344    async move {
22345        request.next().await;
22346    }
22347}
22348
22349fn handle_resolve_completion_request(
22350    cx: &mut EditorLspTestContext,
22351    edits: Option<Vec<(&'static str, &'static str)>>,
22352) -> impl Future<Output = ()> {
22353    let edits = edits.map(|edits| {
22354        edits
22355            .iter()
22356            .map(|(marked_string, new_text)| {
22357                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22358                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22359                lsp::TextEdit::new(replace_range, new_text.to_string())
22360            })
22361            .collect::<Vec<_>>()
22362    });
22363
22364    let mut request =
22365        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22366            let edits = edits.clone();
22367            async move {
22368                Ok(lsp::CompletionItem {
22369                    additional_text_edits: edits,
22370                    ..Default::default()
22371                })
22372            }
22373        });
22374
22375    async move {
22376        request.next().await;
22377    }
22378}
22379
22380pub(crate) fn update_test_language_settings(
22381    cx: &mut TestAppContext,
22382    f: impl Fn(&mut AllLanguageSettingsContent),
22383) {
22384    cx.update(|cx| {
22385        SettingsStore::update_global(cx, |store, cx| {
22386            store.update_user_settings::<AllLanguageSettings>(cx, f);
22387        });
22388    });
22389}
22390
22391pub(crate) fn update_test_project_settings(
22392    cx: &mut TestAppContext,
22393    f: impl Fn(&mut ProjectSettings),
22394) {
22395    cx.update(|cx| {
22396        SettingsStore::update_global(cx, |store, cx| {
22397            store.update_user_settings::<ProjectSettings>(cx, f);
22398        });
22399    });
22400}
22401
22402pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22403    cx.update(|cx| {
22404        assets::Assets.load_test_fonts(cx);
22405        let store = SettingsStore::test(cx);
22406        cx.set_global(store);
22407        theme::init(theme::LoadThemes::JustBase, cx);
22408        release_channel::init(SemanticVersion::default(), cx);
22409        client::init_settings(cx);
22410        language::init(cx);
22411        Project::init_settings(cx);
22412        workspace::init_settings(cx);
22413        crate::init(cx);
22414    });
22415
22416    update_test_language_settings(cx, f);
22417}
22418
22419#[track_caller]
22420fn assert_hunk_revert(
22421    not_reverted_text_with_selections: &str,
22422    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22423    expected_reverted_text_with_selections: &str,
22424    base_text: &str,
22425    cx: &mut EditorLspTestContext,
22426) {
22427    cx.set_state(not_reverted_text_with_selections);
22428    cx.set_head_text(base_text);
22429    cx.executor().run_until_parked();
22430
22431    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22432        let snapshot = editor.snapshot(window, cx);
22433        let reverted_hunk_statuses = snapshot
22434            .buffer_snapshot
22435            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22436            .map(|hunk| hunk.status().kind)
22437            .collect::<Vec<_>>();
22438
22439        editor.git_restore(&Default::default(), window, cx);
22440        reverted_hunk_statuses
22441    });
22442    cx.executor().run_until_parked();
22443    cx.assert_editor_state(expected_reverted_text_with_selections);
22444    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22445}
22446
22447#[gpui::test(iterations = 10)]
22448async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22449    init_test(cx, |_| {});
22450
22451    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22452    let counter = diagnostic_requests.clone();
22453
22454    let fs = FakeFs::new(cx.executor());
22455    fs.insert_tree(
22456        path!("/a"),
22457        json!({
22458            "first.rs": "fn main() { let a = 5; }",
22459            "second.rs": "// Test file",
22460        }),
22461    )
22462    .await;
22463
22464    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22465    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22466    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22467
22468    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22469    language_registry.add(rust_lang());
22470    let mut fake_servers = language_registry.register_fake_lsp(
22471        "Rust",
22472        FakeLspAdapter {
22473            capabilities: lsp::ServerCapabilities {
22474                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22475                    lsp::DiagnosticOptions {
22476                        identifier: None,
22477                        inter_file_dependencies: true,
22478                        workspace_diagnostics: true,
22479                        work_done_progress_options: Default::default(),
22480                    },
22481                )),
22482                ..Default::default()
22483            },
22484            ..Default::default()
22485        },
22486    );
22487
22488    let editor = workspace
22489        .update(cx, |workspace, window, cx| {
22490            workspace.open_abs_path(
22491                PathBuf::from(path!("/a/first.rs")),
22492                OpenOptions::default(),
22493                window,
22494                cx,
22495            )
22496        })
22497        .unwrap()
22498        .await
22499        .unwrap()
22500        .downcast::<Editor>()
22501        .unwrap();
22502    let fake_server = fake_servers.next().await.unwrap();
22503    let server_id = fake_server.server.server_id();
22504    let mut first_request = fake_server
22505        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22506            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22507            let result_id = Some(new_result_id.to_string());
22508            assert_eq!(
22509                params.text_document.uri,
22510                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22511            );
22512            async move {
22513                Ok(lsp::DocumentDiagnosticReportResult::Report(
22514                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22515                        related_documents: None,
22516                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22517                            items: Vec::new(),
22518                            result_id,
22519                        },
22520                    }),
22521                ))
22522            }
22523        });
22524
22525    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22526        project.update(cx, |project, cx| {
22527            let buffer_id = editor
22528                .read(cx)
22529                .buffer()
22530                .read(cx)
22531                .as_singleton()
22532                .expect("created a singleton buffer")
22533                .read(cx)
22534                .remote_id();
22535            let buffer_result_id = project
22536                .lsp_store()
22537                .read(cx)
22538                .result_id(server_id, buffer_id, cx);
22539            assert_eq!(expected, buffer_result_id);
22540        });
22541    };
22542
22543    ensure_result_id(None, cx);
22544    cx.executor().advance_clock(Duration::from_millis(60));
22545    cx.executor().run_until_parked();
22546    assert_eq!(
22547        diagnostic_requests.load(atomic::Ordering::Acquire),
22548        1,
22549        "Opening file should trigger diagnostic request"
22550    );
22551    first_request
22552        .next()
22553        .await
22554        .expect("should have sent the first diagnostics pull request");
22555    ensure_result_id(Some("1".to_string()), cx);
22556
22557    // Editing should trigger diagnostics
22558    editor.update_in(cx, |editor, window, cx| {
22559        editor.handle_input("2", window, cx)
22560    });
22561    cx.executor().advance_clock(Duration::from_millis(60));
22562    cx.executor().run_until_parked();
22563    assert_eq!(
22564        diagnostic_requests.load(atomic::Ordering::Acquire),
22565        2,
22566        "Editing should trigger diagnostic request"
22567    );
22568    ensure_result_id(Some("2".to_string()), cx);
22569
22570    // Moving cursor should not trigger diagnostic request
22571    editor.update_in(cx, |editor, window, cx| {
22572        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22573            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22574        });
22575    });
22576    cx.executor().advance_clock(Duration::from_millis(60));
22577    cx.executor().run_until_parked();
22578    assert_eq!(
22579        diagnostic_requests.load(atomic::Ordering::Acquire),
22580        2,
22581        "Cursor movement should not trigger diagnostic request"
22582    );
22583    ensure_result_id(Some("2".to_string()), cx);
22584    // Multiple rapid edits should be debounced
22585    for _ in 0..5 {
22586        editor.update_in(cx, |editor, window, cx| {
22587            editor.handle_input("x", window, cx)
22588        });
22589    }
22590    cx.executor().advance_clock(Duration::from_millis(60));
22591    cx.executor().run_until_parked();
22592
22593    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22594    assert!(
22595        final_requests <= 4,
22596        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22597    );
22598    ensure_result_id(Some(final_requests.to_string()), cx);
22599}
22600
22601#[gpui::test]
22602async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22603    // Regression test for issue #11671
22604    // Previously, adding a cursor after moving multiple cursors would reset
22605    // the cursor count instead of adding to the existing cursors.
22606    init_test(cx, |_| {});
22607    let mut cx = EditorTestContext::new(cx).await;
22608
22609    // Create a simple buffer with cursor at start
22610    cx.set_state(indoc! {"
22611        ˇaaaa
22612        bbbb
22613        cccc
22614        dddd
22615        eeee
22616        ffff
22617        gggg
22618        hhhh"});
22619
22620    // Add 2 cursors below (so we have 3 total)
22621    cx.update_editor(|editor, window, cx| {
22622        editor.add_selection_below(&Default::default(), window, cx);
22623        editor.add_selection_below(&Default::default(), window, cx);
22624    });
22625
22626    // Verify we have 3 cursors
22627    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22628    assert_eq!(
22629        initial_count, 3,
22630        "Should have 3 cursors after adding 2 below"
22631    );
22632
22633    // Move down one line
22634    cx.update_editor(|editor, window, cx| {
22635        editor.move_down(&MoveDown, window, cx);
22636    });
22637
22638    // Add another cursor below
22639    cx.update_editor(|editor, window, cx| {
22640        editor.add_selection_below(&Default::default(), window, cx);
22641    });
22642
22643    // Should now have 4 cursors (3 original + 1 new)
22644    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22645    assert_eq!(
22646        final_count, 4,
22647        "Should have 4 cursors after moving and adding another"
22648    );
22649}
22650
22651#[gpui::test]
22652async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
22653    let expected_color = Rgba {
22654        r: 0.33,
22655        g: 0.33,
22656        b: 0.33,
22657        a: 0.33,
22658    };
22659
22660    init_test(cx, |_| {});
22661
22662    let fs = FakeFs::new(cx.executor());
22663    fs.insert_tree(
22664        path!("/a"),
22665        json!({
22666            "first.rs": "fn main() { let a = 5; }",
22667        }),
22668    )
22669    .await;
22670
22671    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22672    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22673    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22674
22675    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22676    language_registry.add(rust_lang());
22677    let mut fake_servers = language_registry.register_fake_lsp(
22678        "Rust",
22679        FakeLspAdapter {
22680            capabilities: lsp::ServerCapabilities {
22681                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22682                ..lsp::ServerCapabilities::default()
22683            },
22684            name: "rust-analyzer",
22685            ..FakeLspAdapter::default()
22686        },
22687    );
22688    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22689        "Rust",
22690        FakeLspAdapter {
22691            capabilities: lsp::ServerCapabilities {
22692                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22693                ..lsp::ServerCapabilities::default()
22694            },
22695            name: "not-rust-analyzer",
22696            ..FakeLspAdapter::default()
22697        },
22698    );
22699
22700    let editor = workspace
22701        .update(cx, |workspace, window, cx| {
22702            workspace.open_abs_path(
22703                PathBuf::from(path!("/a/first.rs")),
22704                OpenOptions::default(),
22705                window,
22706                cx,
22707            )
22708        })
22709        .unwrap()
22710        .await
22711        .unwrap()
22712        .downcast::<Editor>()
22713        .unwrap();
22714    let fake_language_server = fake_servers.next().await.unwrap();
22715    let fake_language_server_without_capabilities =
22716        fake_servers_without_capabilities.next().await.unwrap();
22717    let requests_made = Arc::new(AtomicUsize::new(0));
22718    let closure_requests_made = Arc::clone(&requests_made);
22719    let mut color_request_handle = fake_language_server
22720        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22721            let requests_made = Arc::clone(&closure_requests_made);
22722            async move {
22723                assert_eq!(
22724                    params.text_document.uri,
22725                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22726                );
22727                requests_made.fetch_add(1, atomic::Ordering::Release);
22728                Ok(vec![
22729                    lsp::ColorInformation {
22730                        range: lsp::Range {
22731                            start: lsp::Position {
22732                                line: 0,
22733                                character: 0,
22734                            },
22735                            end: lsp::Position {
22736                                line: 0,
22737                                character: 1,
22738                            },
22739                        },
22740                        color: lsp::Color {
22741                            red: 0.33,
22742                            green: 0.33,
22743                            blue: 0.33,
22744                            alpha: 0.33,
22745                        },
22746                    },
22747                    lsp::ColorInformation {
22748                        range: lsp::Range {
22749                            start: lsp::Position {
22750                                line: 0,
22751                                character: 0,
22752                            },
22753                            end: lsp::Position {
22754                                line: 0,
22755                                character: 1,
22756                            },
22757                        },
22758                        color: lsp::Color {
22759                            red: 0.33,
22760                            green: 0.33,
22761                            blue: 0.33,
22762                            alpha: 0.33,
22763                        },
22764                    },
22765                ])
22766            }
22767        });
22768
22769    let _handle = fake_language_server_without_capabilities
22770        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22771            panic!("Should not be called");
22772        });
22773    color_request_handle.next().await.unwrap();
22774    cx.run_until_parked();
22775    color_request_handle.next().await.unwrap();
22776    cx.run_until_parked();
22777    assert_eq!(
22778        3,
22779        requests_made.load(atomic::Ordering::Acquire),
22780        "Should query for colors once per editor open (1) and once after the language server startup (2)"
22781    );
22782
22783    cx.executor().advance_clock(Duration::from_millis(500));
22784    let save = editor.update_in(cx, |editor, window, cx| {
22785        assert_eq!(
22786            vec![expected_color],
22787            extract_color_inlays(editor, cx),
22788            "Should have an initial inlay"
22789        );
22790
22791        editor.move_to_end(&MoveToEnd, window, cx);
22792        editor.handle_input("dirty", window, cx);
22793        editor.save(
22794            SaveOptions {
22795                format: true,
22796                autosave: true,
22797            },
22798            project.clone(),
22799            window,
22800            cx,
22801        )
22802    });
22803    save.await.unwrap();
22804
22805    color_request_handle.next().await.unwrap();
22806    cx.run_until_parked();
22807    color_request_handle.next().await.unwrap();
22808    cx.run_until_parked();
22809    assert_eq!(
22810        5,
22811        requests_made.load(atomic::Ordering::Acquire),
22812        "Should query for colors once per save and once per formatting after save"
22813    );
22814
22815    drop(editor);
22816    let close = workspace
22817        .update(cx, |workspace, window, cx| {
22818            workspace.active_pane().update(cx, |pane, cx| {
22819                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22820            })
22821        })
22822        .unwrap();
22823    close.await.unwrap();
22824    assert_eq!(
22825        5,
22826        requests_made.load(atomic::Ordering::Acquire),
22827        "After saving and closing the editor, no extra requests should be made"
22828    );
22829
22830    workspace
22831        .update(cx, |workspace, window, cx| {
22832            workspace.active_pane().update(cx, |pane, cx| {
22833                pane.navigate_backward(window, cx);
22834            })
22835        })
22836        .unwrap();
22837    cx.executor().advance_clock(Duration::from_millis(100));
22838    color_request_handle.next().await.unwrap();
22839    cx.run_until_parked();
22840    assert_eq!(
22841        6,
22842        requests_made.load(atomic::Ordering::Acquire),
22843        "After navigating back to an editor and reopening it, another color request should be made"
22844    );
22845    let editor = workspace
22846        .update(cx, |workspace, _, cx| {
22847            workspace
22848                .active_item(cx)
22849                .expect("Should have reopened the editor again after navigating back")
22850                .downcast::<Editor>()
22851                .expect("Should be an editor")
22852        })
22853        .unwrap();
22854    editor.update(cx, |editor, cx| {
22855        assert_eq!(
22856            vec![expected_color],
22857            extract_color_inlays(editor, cx),
22858            "Should have an initial inlay"
22859        );
22860    });
22861}
22862
22863#[gpui::test]
22864async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
22865    init_test(cx, |_| {});
22866    let (editor, cx) = cx.add_window_view(Editor::single_line);
22867    editor.update_in(cx, |editor, window, cx| {
22868        editor.set_text("oops\n\nwow\n", window, cx)
22869    });
22870    cx.run_until_parked();
22871    editor.update(cx, |editor, cx| {
22872        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
22873    });
22874    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
22875    cx.run_until_parked();
22876    editor.update(cx, |editor, cx| {
22877        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
22878    });
22879}
22880
22881#[track_caller]
22882fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22883    editor
22884        .all_inlays(cx)
22885        .into_iter()
22886        .filter_map(|inlay| inlay.get_color())
22887        .map(Rgba::from)
22888        .collect()
22889}